also looking at this
fix(parser): accept prefixed and suffixed numeric literals in match patterns
#927
2 files
+103
-6
| @@ -5003,6 +5003,48 @@ pub const Parser = struct { | ||
| 5003 | 5003 | return ast.Stmt{ .MatchStmt = stmt }; |
| 5004 | 5004 | } |
| 5005 | 5005 | |
| 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 | ||
| 5006 | 5048 | /// Parse a pattern for match statements |
| 5007 | 5049 | fn parsePattern(self: *Parser) !*ast.Pattern { |
| 5008 | 5050 | const pattern = try self.allocator.create(ast.Pattern); |
| @@ -5011,8 +5053,7 @@ pub const Parser = struct { | ||
| 5011 | 5053 | // Negative integer pattern: -N |
| 5012 | 5054 | if (self.match(&.{.Minus})) { |
| 5013 | 5055 | 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()); | |
| 5016 | 5057 | pattern.* = ast.Pattern{ .IntLiteral = -pos_value }; |
| 5017 | 5058 | return pattern; |
| 5018 | 5059 | } |
| @@ -5023,7 +5064,7 @@ pub const Parser = struct { | ||
| 5023 | 5064 | // Integer literal pattern (or range pattern) |
| 5024 | 5065 | if (self.match(&.{.Integer})) { |
| 5025 | 5066 | const token = self.previous(); |
| 5026 | const start_value = try std.fmt.parseInt(i64, token.lexeme, 10); | |
| 5067 | const start_value = try self.patternIntValue(token); | |
| 5027 | 5068 | |
| 5028 | 5069 | // Check for range pattern: N..M (exclusive) or N..=M (inclusive). |
| 5029 | 5070 | // The lexer emits DotDotEqual as a single token, so we must |
| @@ -5033,7 +5074,7 @@ pub const Parser = struct { | ||
| 5033 | 5074 | if (self.match(&.{ .DotDot, .DotDotEqual })) { |
| 5034 | 5075 | const inclusive = self.previous().type == .DotDotEqual; |
| 5035 | 5076 | 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); | |
| 5037 | 5078 | |
| 5038 | 5079 | if (start_value > end_value) { |
| 5039 | 5080 | std.debug.print( |
| @@ -5060,8 +5101,7 @@ pub const Parser = struct { | ||
| 5060 | 5101 | // Float literal pattern — reject infinity/NaN which would |
| 5061 | 5102 | // silently never match any runtime value. |
| 5062 | 5103 | 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()); | |
| 5065 | 5105 | if (std.math.isInf(value) or std.math.isNan(value)) { |
| 5066 | 5106 | try self.reportError("float pattern overflows to infinity or NaN and will never match"); |
| 5067 | 5107 | return error.UnexpectedToken; |