ReviewOS

also looking at this

home-lang/home

fix(parser): accept prefixed and suffixed numeric literals in match patterns

#927
Open mkantautas wants to merge fix/match-pattern-int-literals into main
2 files +103 -6
packages/parser/src/parser.zigmodified+46-6
Changes to packages/parser/src/parser.zig
@@ -5003,6 +5003,48 @@ pub const Parser = struct {
50035003 return ast.Stmt{ .MatchStmt = stmt };
50045004 }
50055005
5006 /// Digits of a numeric literal token used in a match pattern, without
5007 /// `_` separators or a type suffix (`i<N>`/`u<N>` for integers, `f<N>`
5008 /// for floats). Expression literals accept the same spellings.
5009 fn patternLiteralDigits(self: *Parser, lexeme: []const u8, suffix_starts: []const u8) !std.ArrayList(u8) {
5010 var end = lexeme.len;
5011 for (lexeme, 0..) |c, i| {
5012 if (i > 0 and i + 1 < lexeme.len and std.mem.indexOfScalar(u8, suffix_starts, c) != null and std.ascii.isDigit(lexeme[i + 1])) {
5013 end = i;
5014 break;
5015 }
5016 }
5017
5018 var digits = std.ArrayList(u8).empty;
5019 errdefer digits.deinit(self.allocator);
5020 for (lexeme[0..end]) |c| {
5021 if (c != '_') try digits.append(self.allocator, c);
5022 }
5023 return digits;
5024 }
5025
5026 /// Value of an integer token used in a match pattern. Accepts `0x`/`0b`/`0o`
5027 /// prefixes, `_` separators and a type suffix.
5028 fn patternIntValue(self: *Parser, token: Token) !i64 {
5029 var digits = try self.patternLiteralDigits(token.lexeme, "iu");
5030 defer digits.deinit(self.allocator);
5031 return std.fmt.parseInt(i64, digits.items, 0) catch {
5032 try self.reportError("Invalid integer literal in pattern");
5033 return error.UnexpectedToken;
5034 };
5035 }
5036
5037 /// Value of a float token used in a match pattern. Accepts `_` separators
5038 /// and a type suffix.
5039 fn patternFloatValue(self: *Parser, token: Token) !f64 {
5040 var digits = try self.patternLiteralDigits(token.lexeme, "f");
5041 defer digits.deinit(self.allocator);
5042 return std.fmt.parseFloat(f64, digits.items) catch {
5043 try self.reportError("Invalid float literal in pattern");
5044 return error.UnexpectedToken;
5045 };
5046 }
5047
50065048 /// Parse a pattern for match statements
50075049 fn parsePattern(self: *Parser) !*ast.Pattern {
50085050 const pattern = try self.allocator.create(ast.Pattern);
@@ -5011,8 +5053,7 @@ pub const Parser = struct {
50115053 // Negative integer pattern: -N
50125054 if (self.match(&.{.Minus})) {
50135055 if (self.match(&.{.Integer})) {
5014 const token = self.previous();
5015 const pos_value = try std.fmt.parseInt(i64, token.lexeme, 10);
5056 const pos_value = try self.patternIntValue(self.previous());
50165057 pattern.* = ast.Pattern{ .IntLiteral = -pos_value };
50175058 return pattern;
50185059 }
@@ -5023,7 +5064,7 @@ pub const Parser = struct {
50235064 // Integer literal pattern (or range pattern)
50245065 if (self.match(&.{.Integer})) {
50255066 const token = self.previous();
5026 const start_value = try std.fmt.parseInt(i64, token.lexeme, 10);
5067 const start_value = try self.patternIntValue(token);
50275068
50285069 // Check for range pattern: N..M (exclusive) or N..=M (inclusive).
50295070 // The lexer emits DotDotEqual as a single token, so we must
@@ -5033,7 +5074,7 @@ pub const Parser = struct {
50335074 if (self.match(&.{ .DotDot, .DotDotEqual })) {
50345075 const inclusive = self.previous().type == .DotDotEqual;
50355076 const end_token = try self.expect(.Integer, "Expected end value in range pattern");
5036 const end_value = try std.fmt.parseInt(i64, end_token.lexeme, 10);
5077 const end_value = try self.patternIntValue(end_token);
50375078
50385079 if (start_value > end_value) {
50395080 std.debug.print(
@@ -5060,8 +5101,7 @@ pub const Parser = struct {
50605101 // Float literal pattern — reject infinity/NaN which would
50615102 // silently never match any runtime value.
50625103 if (self.match(&.{.Float})) {
5063 const token = self.previous();
5064 const value = try std.fmt.parseFloat(f64, token.lexeme);
5104 const value = try self.patternFloatValue(self.previous());
50655105 if (std.math.isInf(value) or std.math.isNan(value)) {
50665106 try self.reportError("float pattern overflows to infinity or NaN and will never match");
50675107 return error.UnexpectedToken;