diff options
author | jjanzen <jjanzen@jjanzen.ca> | 2025-02-27 17:05:40 -0600 |
---|---|---|
committer | jjanzen <jjanzen@jjanzen.ca> | 2025-02-27 17:05:40 -0600 |
commit | a316be56e292baa2599cc6f08f616196d44c08a6 (patch) | |
tree | 7b207cca3ad426e20854af30b5cc6b0e467e18e6 /src | |
parent | 49dc6a9b2b5a4d304c5caf637289c84ff6227e24 (diff) |
fix some edge cases
Diffstat (limited to 'src')
-rw-r--r-- | src/parser.zig | 242 |
1 files changed, 209 insertions, 33 deletions
diff --git a/src/parser.zig b/src/parser.zig index eaa9c51..9445fee 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -270,10 +270,10 @@ pub const Parser = struct { } /// Get the number associated with a given symbol - fn handleSymbol(self: *Parser, symbol: []const u8) !NumberValue { + fn handleSymbol(self: *Parser, symbol: []const u8) SymbolError!NumberValue { // TODO: handle xH, XF, xB const n = self.symbols.get(symbol); - if (n == null) return error.UndefinedSymbol; + if (n == null) return SymbolError.UnexpectedSymbol; return n.?; } @@ -285,20 +285,31 @@ pub const Parser = struct { none, }; + const ExpressionError = SymbolError || ConstantError || error{ + NoEndingDelimiter, + UnaryOperationOnString, + BinaryOperationOnString, + InvalidOperationOnRegister, + DivisionByZero, + FractionalDivisionOversizedNumerator, + }; + /// Determine whether the cursor points at a valid expression /// Move the cursor past the expression, evaluate it, and return its value - fn identifyExpression(self: *Parser) anyerror!ExpressionResult { + fn identifyExpression(self: *Parser) ExpressionError!ExpressionResult { var result: u64 = 0; var last_op = WeakOp.none; var done = false; + var register_result: ?u8 = null; + var string_result: ?[]const u8 = null; while (!done) { + if (string_result != null) return ExpressionError.BinaryOperationOnString; + if (register_result != null) return ExpressionError.InvalidOperationOnRegister; + const term = try self.identifyTerm(); switch (term) { - .string => { - if (last_op == WeakOp.none) return term; - return error.NoExpression; - }, + .string => string_result = term.string, .number => |nv| { switch (nv) { .pure => |n| { @@ -310,9 +321,7 @@ pub const Parser = struct { WeakOp.none => n, }; }, - .register => { - return error.NoExpression; - }, + .register => register_result = nv.register, } }, } @@ -331,6 +340,14 @@ pub const Parser = struct { self.ch_pos += 1; } } + + if (string_result != null) { + return ExpressionResult{ .string = string_result.? }; + } + if (register_result != null) { + return ExpressionResult{ .number = NumberValue{ .register = register_result.? } }; + } + return ExpressionResult{ .number = NumberValue{ .pure = result } }; } @@ -347,17 +364,26 @@ pub const Parser = struct { /// Determine whether the cursor points at a valid term /// Move the cursor past the term, evaluate it, and return its value - fn identifyTerm(self: *Parser) anyerror!ExpressionResult { + fn identifyTerm(self: *Parser) ExpressionError!ExpressionResult { var result: u64 = 0; var last_op = StrongOp.none; + var register_result: ?u8 = null; + var string_result: ?[]const u8 = null; + var started = false; var done = false; while (!done) { + if (string_result != null) return ExpressionError.BinaryOperationOnString; + if (register_result != null) return ExpressionError.InvalidOperationOnRegister; + const primary = try self.identifyPrimary(); switch (primary) { .string => { - if (last_op == StrongOp.none) return primary; - return error.NoTerm; + if (!started) { + string_result = primary.string; + } else { + return ExpressionError.BinaryOperationOnString; + } }, .number => |nv| { switch (nv) { @@ -365,18 +391,18 @@ pub const Parser = struct { result = switch (last_op) { StrongOp.mult => result *% n, StrongOp.div => div: { - if (n == 0) return error.NoTerm; + if (n == 0) return ExpressionError.DivisionByZero; break :div result / n; }, StrongOp.frac_div => frac_div: { - if (n == 0 or result >= n) return error.NoTerm; - const shifted: u128 = (@as(u128, result)) << 8; - const divided: u128 = shifted / n; - if (divided >= 1 << 64) return error.NoTerm; + if (result >= n) return ExpressionError.FractionalDivisionOversizedNumerator; + if (n == 0) return ExpressionError.DivisionByZero; + const shifted: u128 = (@as(u128, result)) << 64; + const divided: u128 = (shifted / n) % (1 << 64); break :frac_div @as(u64, @intCast(divided)); }, StrongOp.rem => rem: { - if (n == 0) return error.NoTerm; + if (n == 0) return ExpressionError.DivisionByZero; break :rem result % n; }, StrongOp.lshift => if (n >= 64) 0 else result << @as(u6, @intCast(n)), @@ -385,9 +411,12 @@ pub const Parser = struct { StrongOp.none => n, }; }, - else => { - if (last_op == StrongOp.none) return primary; - return error.NoTerm; + .register => { + if (!started) { + register_result = nv.register; + } else { + return ExpressionError.InvalidOperationOnRegister; + } }, } }, @@ -431,6 +460,17 @@ pub const Parser = struct { } else { self.ch_pos += 1; } + + started = true; + } + + if (register_result != null) { + result = result % (1 << 8); + return ExpressionResult{ .number = NumberValue{ .register = register_result.? } }; + } + + if (string_result != null) { + return ExpressionResult{ .string = string_result.? }; } return ExpressionResult{ .number = NumberValue{ .pure = result } }; @@ -438,13 +478,13 @@ pub const Parser = struct { /// Determine whether the cursor points at a valid primary /// Move the cursor past the primary, evaluate it, and return its value - fn identifyPrimary(self: *Parser) anyerror!ExpressionResult { + fn identifyPrimary(self: *Parser) ExpressionError!ExpressionResult { if (isDecimal(self.getByte(self.ch_pos)) and (self.getByte(self.ch_pos + 1) == 'H' or self.getByte(self.ch_pos + 1) == 'F' or self.getByte(self.ch_pos + 1) == 'B')) { const symbol = try self.identifySymbol(); - if (symbol.len != 2) return error.UndefinedSymbol; - if (symbol[1] == 'H') return error.UndefinedSymbol; + if (symbol.len != 2) return SymbolError.UnexpectedSymbol; + if (symbol[1] == 'H') return SymbolError.UnexpectedSymbol; const symbol_val = try self.handleSymbol(symbol); return ExpressionResult{ .number = symbol_val }; } @@ -454,7 +494,7 @@ pub const Parser = struct { '(' => { self.ch_pos += 1; const expr = try self.identifyExpression(); - if (self.getByte(self.ch_pos) != ')') return error.NoPrimary; + if (self.getByte(self.ch_pos) != ')') return error.NoEndingDelimiter; self.ch_pos += 1; return expr; }, @@ -470,7 +510,7 @@ pub const Parser = struct { const primary = try self.identifyPrimary(); switch (primary) { .number => return primary, - else => return error.NoPimary, + else => return ExpressionError.UnaryOperationOnString, } }, '-' => { @@ -482,7 +522,7 @@ pub const Parser = struct { .register, .pure => |n| return ExpressionResult{ .number = NumberValue{ .pure = 0 -% n } }, } }, - else => return error.NoPrimary, + else => return ExpressionError.UnaryOperationOnString, } }, '~' => { @@ -494,7 +534,7 @@ pub const Parser = struct { .register, .pure => |n| return ExpressionResult{ .number = NumberValue{ .pure = ~n } }, } }, - else => return error.NoPrimary, + else => return ExpressionError.UnaryOperationOnString, } }, '$' => { @@ -504,13 +544,13 @@ pub const Parser = struct { .number => |nv| { switch (nv) { .register, .pure => |n| { - if (n >= 256) return error.NoPrimary; + if (n >= 256) return ExpressionError.Overflow; const n8 = @as(u8, @intCast(n)); return ExpressionResult{ .number = NumberValue{ .register = n8 } }; }, } }, - else => return error.NoPrimary, + else => return ExpressionError.UnaryOperationOnString, } }, else => { @@ -923,12 +963,148 @@ test "invalid primaries are detected" { const test_cases = [_][]const u8{ "$256", "$~0", + "~\"hello\"", + }; + + const expected = [_]Parser.ExpressionError{ + Parser.ExpressionError.Overflow, + Parser.ExpressionError.Overflow, + Parser.ExpressionError.UnaryOperationOnString, + }; + + for (0..test_cases.len) |i| { + var parser = Parser.init(std.testing.allocator, test_cases[i]); + defer parser.deinit(); + const symbol = parser.identifyPrimary(); + try std.testing.expectEqual(expected[i], symbol); + } +} + +test "valid terms are recognized" { + const test_cases = [_][]const u8{ + "12*34", + "23/2", + "2//3", + "3%2", + "1<<2", + "4>>2", + "#FF&#AA", + "$12", + "\"hello\"", + }; + + const expected = [_]ExpressionResult{ + ExpressionResult{ .number = NumberValue{ .pure = 408 } }, + ExpressionResult{ .number = NumberValue{ .pure = 11 } }, + ExpressionResult{ .number = NumberValue{ .pure = 12297829382473034410 } }, + ExpressionResult{ .number = NumberValue{ .pure = 1 } }, + ExpressionResult{ .number = NumberValue{ .pure = 4 } }, + ExpressionResult{ .number = NumberValue{ .pure = 1 } }, + ExpressionResult{ .number = NumberValue{ .pure = 0xAA } }, + ExpressionResult{ .number = NumberValue{ .register = 12 } }, + ExpressionResult{ .string = "hello" }, + }; + + for (0..test_cases.len) |i| { + var parser = Parser.init(std.testing.allocator, test_cases[i]); + defer parser.deinit(); + const symbol = try parser.identifyTerm(); + + switch (symbol) { + .number => try std.testing.expectEqual(expected[i], symbol), + .string => try std.testing.expect(std.mem.eql(u8, expected[i].string, symbol.string)), + } + } +} + +test "terms compute the entire chain" { + const test_cases = [_][]const u8{ + "1*2*3*4*5", + "2*6/4", + "3*3%8<<2>>1", + }; + + const expected = [_]ExpressionResult{ + ExpressionResult{ .number = NumberValue{ .pure = 120 } }, + ExpressionResult{ .number = NumberValue{ .pure = 3 } }, + ExpressionResult{ .number = NumberValue{ .pure = 2 } }, + }; + + for (0..test_cases.len) |i| { + var parser = Parser.init(std.testing.allocator, test_cases[i]); + defer parser.deinit(); + const symbol = try parser.identifyTerm(); + try std.testing.expectEqual(expected[i], symbol); + } +} + +test "strong operations do not work on registers" { + const test_cases = [_][]const u8{ + "$2*3", + "1/$1", + "$1%$1", + "$1<<1", + "1>>$1", + "$1&$1", }; for (test_cases) |case| { var parser = Parser.init(std.testing.allocator, case); defer parser.deinit(); - const symbol = parser.identifyPrimary(); - try std.testing.expectEqual(error.NoPrimary, symbol); + const symbol = parser.identifyTerm(); + try std.testing.expectEqual(Parser.ExpressionError.InvalidOperationOnRegister, symbol); + } +} + +test "strong operations do not work on strings" { + const test_cases = [_][]const u8{ + "\"hello\"*1", + "1*\"hello\"", + }; + + for (test_cases) |case| { + var parser = Parser.init(std.testing.allocator, case); + defer parser.deinit(); + const symbol = parser.identifyTerm(); + try std.testing.expectEqual(Parser.ExpressionError.BinaryOperationOnString, symbol); + } +} + +test "expressions are recognized" { + const test_cases = [_][]const u8{ + "1+2", + "3-2", + "2-3", + "#AA|#00", + "#AA^#FF", + "5*5+5", + "5*5+5*5", + "#ab<<32+#cdef00&~(#cdef00-1)", + "\"hello\"", + "$12", + }; + + const expected = [_]ExpressionResult{ + ExpressionResult{ .number = NumberValue{ .pure = 3 } }, + ExpressionResult{ .number = NumberValue{ .pure = 1 } }, + ExpressionResult{ .number = NumberValue{ .pure = 0xFFFFFFFFFFFFFFFF } }, + ExpressionResult{ .number = NumberValue{ .pure = 0xAA } }, + ExpressionResult{ .number = NumberValue{ .pure = 0x55 } }, + ExpressionResult{ .number = NumberValue{ .pure = 30 } }, + ExpressionResult{ .number = NumberValue{ .pure = 50 } }, + ExpressionResult{ .number = NumberValue{ .pure = 0xab00000100 } }, + ExpressionResult{ .string = "hello" }, + ExpressionResult{ .number = NumberValue{ .register = 12 } }, + }; + + for (0..test_cases.len) |i| { + var parser = Parser.init(std.testing.allocator, test_cases[i]); + defer parser.deinit(); + const symbol = try parser.identifyExpression(); + + switch (symbol) { + .number => try std.testing.expectEqual(expected[i], symbol), + .string => try std.testing.expect(std.mem.eql(u8, expected[i].string, symbol.string)), + } } } |