const std = @import("std"); const storage = @import("storage.zig"); const CommandType = enum { SET, GET, DEL, UNKNOWN, }; pub const RedisCommand = struct { cmd_type: CommandType, key: []const u8, value: []const u8, }; pub const ParseResult = struct { cmd: RedisCommand, bytes_consumed: usize, }; fn parseInteger(buf: []const u8, start: usize, end: usize) ?usize { if (start >= end) return null; var result: usize = 0; for (buf[start..end]) |c| { if (c < '0' or c > '9') return null; result = result * 10 + (c - '0'); } return result; } fn parseBulkString(buf: []const u8, pos: *usize) ?[]const u8 { if (pos.* >= buf.len or buf[pos.*] != '$') return null; pos.* += 1; const len_end = std.mem.indexOfScalarPos(u8, buf, pos.*, '\r') orelse return null; const len = parseInteger(buf, pos.*, len_end) orelse return null; pos.* = len_end + 2; const str_start = pos.*; const str_end = str_start + len; if (str_end > buf.len) return null; const result = buf[str_start..str_end]; pos.* = str_end + 2; return result; } pub fn parseCommand(buf: []const u8) ?ParseResult { if (buf.len == 0) return null; var pos: usize = 0; if (buf[pos] != '*') return null; pos += 1; const array_len_end = std.mem.indexOfScalarPos(u8, buf, pos, '\r') orelse return null; const array_len = parseInteger(buf, pos, array_len_end) orelse return null; pos = array_len_end + 2; if (array_len < 1 or array_len > 16) return null; var elements: [16][]const u8 = undefined; for (0..array_len) |i| { elements[i] = parseBulkString(buf, &pos) orelse return null; } const cmd_str = elements[0]; var cmd: RedisCommand = undefined; if (cmd_str.len == 3) { const upper: u32 = (@as(u32, cmd_str[0]) & 0xDF) << 16 | (@as(u32, cmd_str[1]) & 0xDF) << 8 | (@as(u32, cmd_str[2]) & 0xDF); if (upper == (@as(u32, 'S') << 16 | @as(u32, 'E') << 8 | @as(u32, 'T'))) { if (array_len < 3) return null; cmd = RedisCommand{ .cmd_type = .SET, .key = elements[1], .value = elements[2], }; } else if (upper == (@as(u32, 'G') << 16 | @as(u32, 'E') << 8 | @as(u32, 'T'))) { if (array_len < 2) return null; cmd = RedisCommand{ .cmd_type = .GET, .key = elements[1], .value = "", }; } else if (upper == (@as(u32, 'D') << 16 | @as(u32, 'E') << 8 | @as(u32, 'L'))) { if (array_len < 2) return null; cmd = RedisCommand{ .cmd_type = .DEL, .key = elements[1], .value = "", }; } else { cmd = RedisCommand{ .cmd_type = .UNKNOWN, .key = "", .value = "", }; } } else { cmd = RedisCommand{ .cmd_type = .UNKNOWN, .key = "", .value = "", }; } return ParseResult{ .cmd = cmd, .bytes_consumed = pos, }; } fn formatSimpleString(buf: []u8, str: []const u8) []const u8 { var pos: usize = 0; buf[pos] = '+'; pos += 1; @memcpy(buf[pos .. pos + str.len], str); pos += str.len; buf[pos] = '\r'; buf[pos + 1] = '\n'; return buf[0 .. pos + 2]; } fn formatBulkString(buf: []u8, str: []const u8) []const u8 { var pos: usize = 0; buf[pos] = '$'; pos += 1; pos += formatInt(buf[pos..], str.len); buf[pos] = '\r'; buf[pos + 1] = '\n'; pos += 2; @memcpy(buf[pos .. pos + str.len], str); pos += str.len; buf[pos] = '\r'; buf[pos + 1] = '\n'; return buf[0 .. pos + 2]; } fn formatInt(buf: []u8, value: usize) usize { if (value == 0) { buf[0] = '0'; return 1; } var v = value; var len: usize = 0; var temp: [20]u8 = undefined; while (v > 0) { temp[len] = @intCast('0' + (v % 10)); v /= 10; len += 1; } var i: usize = 0; while (i < len) : (i += 1) { buf[i] = temp[len - 1 - i]; } return len; } fn formatNullBulkString(buf: []u8) []const u8 { buf[0] = '$'; buf[1] = '-'; buf[2] = '1'; buf[3] = '\r'; buf[4] = '\n'; return buf[0..5]; } fn formatInteger(buf: []u8, value: i64) []const u8 { var pos: usize = 0; buf[pos] = ':'; pos += 1; if (value < 0) { buf[pos] = '-'; pos += 1; pos += formatInt(buf[pos..], @intCast(-value)); } else { pos += formatInt(buf[pos..], @intCast(value)); } buf[pos] = '\r'; buf[pos + 1] = '\n'; return buf[0 .. pos + 2]; } fn formatError(buf: []u8, msg: []const u8) []const u8 { var pos: usize = 0; buf[pos] = '-'; pos += 1; @memcpy(buf[pos .. pos + msg.len], msg); pos += msg.len; buf[pos] = '\r'; buf[pos + 1] = '\n'; return buf[0 .. pos + 2]; } pub fn executeCommand(cmd: RedisCommand, response_buf: []u8) []const u8 { switch (cmd.cmd_type) { .SET => { if (storage.write(cmd.key, cmd.value)) { return formatSimpleString(response_buf, "OK"); } else { return formatError(response_buf, "ERR write failed"); } }, .GET => { if (storage.read(cmd.key)) |value| { return formatBulkString(response_buf, value); } else { return formatNullBulkString(response_buf); } }, .DEL => { const deleted = storage.delete(cmd.key); return formatInteger(response_buf, if (deleted) 1 else 0); }, .UNKNOWN => { return formatError(response_buf, "ERR unknown command"); }, } }