aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/parser.zig242
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)),
+ }
}
}