From ee2a9f08329324263d17d82b665207ad32e58009 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 28 Oct 2026 14:42:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(lang):=20=E5=AE=BD=E5=BA=A6=E5=90=8E?= =?UTF-8?q?=E7=BC=80=E6=94=AF=E6=8C=81=E4=BB=BB=E6=84=8F=E4=BD=8D=E5=AE=BD?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E5=A4=8D=E6=95=B0=E5=AD=97=E5=88=86=E9=9A=94?= =?UTF-8?q?=E7=AC=A6/=E8=BF=9B=E5=88=B6=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) _read_number 只在 '_' 后接该进制合法数字时吞下划线,'_u8' 等后缀不再被数字循环吃掉;_read_width_suffix 取消 1..64 限制,只保留 int 溢出保护。2) 负的显式宽度字面量在 parser 折叠符号,-64_i7 / -128_i8 不再先按正幅值报越界。3) atoll/atof 支持 0b/0o 前缀与 '_' 分隔符,修复 1_000 被解析成 1、0b1010 被解析成 0 的问题。 --- ast/lexer.vp | 39 ++++++++++++++++++++++++----------- ast/parser.vp | 10 +++++++++ string.vp | 57 ++++++++++++++++++++++++++++----------------------- 3 files changed, 68 insertions(+), 38 deletions(-) diff --git a/ast/lexer.vp b/ast/lexer.vp index cba2c02..db2ee1d 100644 --- a/ast/lexer.vp +++ b/ast/lexer.vp @@ -584,6 +584,20 @@ def _read_name(lx: Lexer*): # Number Handling # ============================================================ +# _digit_sep - 下划线分隔符:仅当后面紧跟该进制的合法数字时才算分隔符。 +# 这样 `128_u8` 的 `_` 会留给 `_read_width_suffix`(宽度后缀),而 `1_000` 仍按分隔符处理。 +def _digit_sep(lx: Lexer*, base: int) -> int: + """1 when src[pos]=='_' and the next char is a digit of base (digit separator).""" + if lx.pos >= lx.len: return 0 + if lx.src[lx.pos] != '_': return 0 + if lx.pos + 1 >= lx.len: return 0 + ch: char = lx.src[lx.pos + 1] + if base == 16: return 1 if _is_hex(ch) != 0 else 0 + if base == 8: return 1 if ch >= '0' and ch <= '7' else 0 + if base == 2: return 1 if ch == '0' or ch == '1' else 0 + return 1 if _is_digit(ch) != 0 else 0 + + def _read_number(lx: Lexer*): start: sizet = lx.pos start_lineno: int = lx.lineno @@ -600,7 +614,7 @@ def _read_number(lx: Lexer*): lx.col += 2 while lx.pos < lx.len: ch: char = lx.src[lx.pos] - if _is_hex(ch) or ch == '_': + if _is_hex(ch) != 0 or _digit_sep(lx, 16) != 0: lx.pos += 1 lx.col += 1 else: @@ -612,10 +626,7 @@ def _read_number(lx: Lexer*): lx.col += 2 while lx.pos < lx.len: ch: char = lx.src[lx.pos] - if ch >= '0' and ch <= '7': - lx.pos += 1 - lx.col += 1 - elif ch == '_': + if (ch >= '0' and ch <= '7') or _digit_sep(lx, 8) != 0: lx.pos += 1 lx.col += 1 else: @@ -627,7 +638,7 @@ def _read_number(lx: Lexer*): lx.col += 2 while lx.pos < lx.len: ch: char = lx.src[lx.pos] - if ch == '0' or ch == '1' or ch == '_': + if ch == '0' or ch == '1' or _digit_sep(lx, 2) != 0: lx.pos += 1 lx.col += 1 else: @@ -638,7 +649,7 @@ def _read_number(lx: Lexer*): # Decimal integer/float while lx.pos < lx.len: ch: char = lx.src[lx.pos] - if _is_digit(ch) or ch == '_': + if _is_digit(ch) != 0 or _digit_sep(lx, 10) != 0: lx.pos += 1 lx.col += 1 else: @@ -651,7 +662,7 @@ def _read_number(lx: Lexer*): lx.col += 1 while lx.pos < lx.len: ch: char = lx.src[lx.pos] - if _is_digit(ch) or ch == '_': + if _is_digit(ch) != 0 or _digit_sep(lx, 10) != 0: lx.pos += 1 lx.col += 1 else: @@ -671,7 +682,7 @@ def _read_number(lx: Lexer*): lx.col += 1 while lx.pos < lx.len: ch: char = lx.src[lx.pos] - if _is_digit(ch) or ch == '_': + if _is_digit(ch) != 0 or _digit_sep(lx, 10) != 0: lx.pos += 1 lx.col += 1 else: @@ -696,7 +707,8 @@ def _suffix_is(lx: Lexer*, p: sizet, s: str, n: int) -> int: # _read_width_suffix - 解析整型字面量的显式宽度后缀(_int/_uint/_i/_u)。 # 命中则推进 lx.pos/col,返回 +N(有符号)/ -N(无符号);未命中返回 0 且不动位置。 -# N 取 1..64(字面量值本身是 64 位);后缀之后不得直接跟标识符字符。 +# 语言层不限制 N(LLVM/MLIR 支持任意位宽);仅防止 int 溢出(超出 int32 的宽度视为无效后缀)。 +# 后缀之后不得直接跟标识符字符。 def _read_width_suffix(lx: Lexer*) -> int: """Explicit integer width suffix: +N signed / -N unsigned / 0 none (advances on hit).""" if lx.pos >= lx.len: return 0 @@ -720,10 +732,13 @@ def _read_width_suffix(lx: Lexer*) -> int: while p < lx.len: ch: char = lx.src[p] if ch < '0' or ch > '9': break - nbits = nbits * 10 + (int(ch) - 48) + d: int = int(ch) - 48 + # Overflow guard only: widths are otherwise unrestricted (LLVM/MLIR support iN for any N). + if nbits > (2147483647 - d) / 10: return 0 + nbits = nbits * 10 + d ndig += 1 p += 1 - if ndig == 0 or nbits <= 0 or nbits > 64: return 0 + if ndig == 0 or nbits <= 0: return 0 if p < lx.len: ch2: char = lx.src[p] if (ch2 >= '0' and ch2 <= '9') or (ch2 >= 'a' and ch2 <= 'z') \ diff --git a/ast/parser.vp b/ast/parser.vp index a743293..6637c8c 100644 --- a/ast/parser.vp +++ b/ast/parser.vp @@ -2279,6 +2279,16 @@ def _parse_factor(ps: Parser*) -> AST*: _record_neg_literal_overflow(_cur_str(ps), lineno, col) ps.error_count += 1 return None + # Explicit-width negative literal: fold the sign into the constant so `-64_i7` / + # `-128_i8` are range-checked as negative values instead of a positive magnitude. + if op_token == TokOp.Minus and _cur_type(ps) == TokenType.Number \ + and ps.cur.is_float == 0 and ps.cur.int_width > 0: + w_tok: Token* = ps.cur + _advance(ps) + neg_node: AST* = _new_const_int(ps.pool, -w_tok.int_val, lineno, col, + w_tok.int_width, w_tok.int_unsigned) + _set_pos(neg_node, lineno, col, 0, 0) + return neg_node operand: AST* = _parse_factor(ps) ast_op: int = OpKind.UAdd if op_token == TokOp.Minus: diff --git a/string.vp b/string.vp index 7325966..07a9045 100644 --- a/string.vp +++ b/string.vp @@ -298,30 +298,31 @@ def atoll(src: str) -> int64: s += 1 elif s[0] == '+': s += 1 - # Hex prefix 0x / 0X - is_hex: int = 0 - if s[0] == '0' and (s[1] == 'x' or s[1] == 'X'): - is_hex = 1 - s += 2 - if is_hex: - for ch in s: - d: int = 0 - if ch >= '0' and ch <= '9': - d = ch - '0' - elif ch >= 'a' and ch <= 'f': - d = ch - 'a' + 10 - elif ch >= 'A' and ch <= 'F': - d = ch - 'A' + 10 - else: - break - digit: int64 = int64(d) - num = num * 16 + digit - else: - for ch in s: - if ch < '0' or ch > '9': break - d: int = ch - '0' - digit: int64 = int64(d) - num = num * 10 + digit + # Radix prefixes: 0x/0X hex, 0b/0B binary, 0o/0O octal (language literals). + base: int64 = 10 + if s[0] == '0': + c1: char = s[1] + if c1 == 'x' or c1 == 'X': + base = 16 + s += 2 + elif c1 == 'b' or c1 == 'B': + base = 2 + s += 2 + elif c1 == 'o' or c1 == 'O': + base = 8 + s += 2 + for ch in s: + if ch == '_': + continue + d: int = -1 + if ch >= '0' and ch <= '9': + d = ch - '0' + elif ch >= 'a' and ch <= 'f': + d = ch - 'a' + 10 + elif ch >= 'A' and ch <= 'F': + d = ch - 'A' + 10 + if d < 0 or int64(d) >= base: break + num = num * base + int64(d) return num * sign @@ -498,6 +499,9 @@ def atof(src: str) -> double: rounded = 1 elif ch == '.': seen_dot = 1 + elif ch == '_': + # Digit separator inside a numeric literal: ignore it. + pass else: break s += 1 @@ -517,8 +521,9 @@ def atof(src: str) -> double: s += 1 elif s[0] == '+': s += 1 - while s[0] >= '0' and s[0] <= '9': - exp = exp * 10 + (s[0] - '0') + while (s[0] >= '0' and s[0] <= '9') or s[0] == '_': + if s[0] != '_': + exp = exp * 10 + (s[0] - '0') s += 1 exp = exp * exp_sign p: int = dexp + exp