From f73834b333169e5914efce020b27a03bac8be682 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 18 Jul 2026 08:05:36 -0700 Subject: [PATCH] allow commas in numbers --- engine/src/evaluator.zig | 10 ++++++++++ engine/src/tokenizer.zig | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/engine/src/evaluator.zig b/engine/src/evaluator.zig index 43da003..5766be6 100644 --- a/engine/src/evaluator.zig +++ b/engine/src/evaluator.zig @@ -486,6 +486,16 @@ test "eval number with underscores" { try testing.expectEqual(@as(f64, 1_000_001.0), result); } +test "eval number with commas" { + const result = try testEval("1,000 * 2.3"); + try testing.expectApproxEqAbs(@as(f64, 2300.0), result, 1e-10); +} + +test "eval commas not confused with function args" { + const result = try testEval("max(1,000, 500)"); + try testing.expectEqual(@as(f64, 1000.0), result); +} + test "eval bitwise not in standard mode" { const result = try testEval("~0"); // ~0 as i64 = -1 diff --git a/engine/src/tokenizer.zig b/engine/src/tokenizer.zig index 6737ea4..1493967 100644 --- a/engine/src/tokenizer.zig +++ b/engine/src/tokenizer.zig @@ -93,7 +93,7 @@ pub fn parseNumber(token_text: []const u8) !NumberValue { var buf: [128]u8 = undefined; var buf_len: usize = 0; for (token_text) |c| { - if (c != '_') { + if (c != '_' and c != ',') { if (buf_len >= buf.len) return error.InvalidNumber; buf[buf_len] = c; buf_len += 1; @@ -367,6 +367,14 @@ pub const Tokenizer = struct { } else if (ch == '_') { // Digit separator self.pos += 1; + } else if (ch == ',') { + // Comma as digit separator, but only if followed by a digit + // (to avoid eating commas in function args like max(1, 2)) + if (self.pos + 1 < self.source.len and predicate(self.source[self.pos + 1])) { + self.pos += 1; + } else { + break; + } } else { break; } @@ -474,6 +482,24 @@ test "tokenize hex with underscores" { try testing.expectEqualStrings("0xFF_FF", t.text("0xFF_FF")); } +test "tokenize number with commas" { + var tok = Tokenizer.init("1,000,000", .standard); + const t = tok.next(); + try testing.expectEqual(TokenKind.number, t.kind); + try testing.expectEqualStrings("1,000,000", t.text("1,000,000")); + try testing.expectEqual(TokenKind.eof, tok.next().kind); +} + +test "tokenize comma not eaten in function args" { + var tok = Tokenizer.init("max(1, 2)", .standard); + try testing.expectEqual(TokenKind.identifier, tok.next().kind); // max + try testing.expectEqual(TokenKind.left_paren, tok.next().kind); // ( + try testing.expectEqual(TokenKind.number, tok.next().kind); // 1 + try testing.expectEqual(TokenKind.comma, tok.next().kind); // , + try testing.expectEqual(TokenKind.number, tok.next().kind); // 2 + try testing.expectEqual(TokenKind.right_paren, tok.next().kind); // ) +} + test "tokenize function call" { var tok = Tokenizer.init("sin(3.14)", .standard); try testing.expectEqual(TokenKind.identifier, tok.next().kind); @@ -584,6 +610,12 @@ test "parseNumber hex with underscores" { try testing.expectEqual(Base.hex, result.base); } +test "parseNumber with commas" { + const result = try parseNumber("1,000,000"); + try testing.expectEqual(@as(?u64, 1_000_000), result.int_value); + try testing.expectEqual(Base.decimal, result.base); +} + // -- ImplicitMulStream tests -- test "implicit mul: number followed by identifier (2pi)" {