import t, c from stdint import * import memhub import string from .tokens import ( Token, TokenType, TokOp, _init_tables, _kw_lookup, _op_head, OpEntry, new_token, token_set_str, token_set_str_literal, ) # 缩进栈最大深度 LEXER_MAX_INDENT: t.CDefine = 256 # ============================================================ # Lexer 状态结构体 # ============================================================ class Lexer: src: str # 源代码(NUL 结尾) pos: t.CSizeT # 当前位置 len: t.CSizeT # 源代码长度 lineno: t.CInt # 当前行号(从 1 开始) col: t.CInt # 当前列号(从 0 开始) pool: memhub.MemManager | t.CPtr # 内存池 indent_stack: t.CInt | t.CPtr # 缩进栈(int 数组,从 pool 分配) indent_top: t.CInt # 栈顶索引(-1 表示空栈) paren_depth: t.CInt # 括号深度(>0 时不产生 TokenType.NewLine) tokens_head: Token | t.CPtr # token 链表头 tokens_tail: Token | t.CPtr # token 链表尾 at_line_start: t.CInt # 1=行首(需处理缩进) error_count: t.CInt # 错误计数 def __new__(self, pool: memhub.MemManager | t.CPtr): ptr: Lexer | t.CPtr = pool.alloc(Lexer.__sizeof__()) if ptr: string.memset(ptr, 0, Lexer.__sizeof__()) return ptr def new_lexer(pool: memhub.MemManager | t.CPtr) -> Lexer | t.CPtr: """从 pool 分配并返回 Lexer(跨模块安全包装)""" return Lexer(pool) def _lexer_init(lx: Lexer | t.CPtr, src: str, pool: memhub.MemManager | t.CPtr): """初始化 Lexer""" if lx == None: return lx.src = src lx.pos = 0 lx.len = string.strlen(src) if src != None else 0 lx.lineno = 1 lx.col = 0 lx.pool = pool # 分配缩进栈 lx.indent_stack = pool.alloc(LEXER_MAX_INDENT * 8) if lx.indent_stack != None: string.memset(lx.indent_stack, 0, LEXER_MAX_INDENT * 8) lx.indent_top = -1 # 栈底压入 0(基础缩进) if lx.indent_stack != None: lx.indent_stack[0] = 0 lx.indent_top = 0 lx.paren_depth = 0 lx.tokens_head = None lx.tokens_tail = None lx.at_line_start = 1 lx.error_count = 0 _init_tables(pool) # ============================================================ # 辅助函数 # ============================================================ def _peek(lx: Lexer | t.CPtr, offset: t.CSizeT) -> t.CChar: """查看 pos+offset 处字符,越界返回 '\0'""" p: t.CSizeT = lx.pos + offset if p >= lx.len: return '\0' return lx.src[p] def _peek_cur(lx: Lexer | t.CPtr) -> t.CChar: if lx.pos >= lx.len: return '\0' return lx.src[lx.pos] def _advance(lx: Lexer | t.CPtr) -> t.CChar: """前进一个字符,返回该字符""" if lx.pos >= lx.len: return '\0' ch: t.CChar = lx.src[lx.pos] lx.pos += 1 if ch == '\n': lx.lineno += 1 lx.col = 0 else: lx.col += 1 return ch def _is_alpha(ch: t.CChar) -> t.CInt: if ch >= 'a' and ch <= 'z': return 1 if ch >= 'A' and ch <= 'Z': return 1 if ch == '_': return 1 return 0 def _is_digit(ch: t.CChar) -> t.CInt: if ch >= '0' and ch <= '9': return 1 return 0 def _is_alnum(ch: t.CChar) -> t.CInt: if _is_alpha(ch): return 1 if _is_digit(ch): return 1 return 0 def _is_hex(ch: t.CChar) -> t.CInt: if ch >= '0' and ch <= '9': return 1 if ch >= 'a' and ch <= 'f': return 1 if ch >= 'A' and ch <= 'F': return 1 return 0 def _is_space(ch: t.CChar) -> t.CInt: if ch == ' ': return 1 if ch == '\t': return 1 if ch == '\f': return 1 if ch == '\v': return 1 if ch == '\r': return 1 return 0 def _emit(lx: Lexer | t.CPtr, tok: Token | t.CPtr): """将 token 追加到链表""" if tok == None: return if lx.tokens_head == None: lx.tokens_head = tok lx.tokens_tail = tok else: lx.tokens_tail.next = tok lx.tokens_tail = tok def _make_op_token(lx: Lexer | t.CPtr, op_id: t.CInt, length: t.CInt, lineno: t.CInt, col: t.CInt) -> Token | t.CPtr: tok: Token | t.CPtr = new_token(lx.pool, TokenType.Op, lineno, col) if tok == None: return None tok.op_subtype = op_id tok.end_col_offset = col + length return tok # ============================================================ # 缩进处理 # ============================================================ def _handle_indent(lx: Lexer | t.CPtr): """处理行首缩进,生成 TokenType.Indent/TokenType.Dedent。 跳过空行和注释行(不产生缩进变化)。""" # 先跳过空行和注释行 while lx.pos < lx.len: # 记住行首位置,用于非空行回退 line_start_pos: t.CSizeT = lx.pos line_start_col: t.CInt = lx.col # 跳过空白(不跨行) while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if ch == ' ' or ch == '\t' or ch == '\f' or ch == '\v': lx.pos += 1 lx.col += 1 elif ch == '\r': # CRLF 行尾符的 \r,跳过(让 \n 触发空行处理) lx.pos += 1 else: break if lx.pos >= lx.len: break ch = lx.src[lx.pos] if ch == '\n': # 空行,跳过 lx.pos += 1 lx.lineno += 1 lx.col = 0 continue if ch == '\r': # CRLF 空行(\r 后跟 \n),跳过 \r lx.pos += 1 if lx.pos < lx.len and lx.src[lx.pos] == '\n': lx.pos += 1 lx.lineno += 1 lx.col = 0 continue if ch == '#': # 注释行,跳过到行尾 while lx.pos < lx.len and lx.src[lx.pos] != '\n': lx.pos += 1 continue # 非空行:回退到行首,让后续缩进计算能读到前导空格 lx.pos = line_start_pos lx.col = line_start_col break if lx.pos >= lx.len: # 文件末尾,不再处理缩进 lx.at_line_start = 0 return # 计算当前行缩进 indent: t.CInt = 0 save_pos: t.CSizeT = lx.pos save_col: t.CInt = lx.col while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if ch == ' ': indent += 1 lx.pos += 1 lx.col += 1 elif ch == '\t': indent += 8 - (indent % 8) lx.pos += 1 lx.col += 1 else: break # 如果行尾(空行),不处理 if lx.pos < lx.len: ch = lx.src[lx.pos] if ch == '\n' or ch == '\r' or ch == '#': # 空行或注释行,回退位置让主循环处理 lx.pos = save_pos lx.col = save_col lx.at_line_start = 0 return # 与缩进栈比较 top_indent: t.CInt = lx.indent_stack[lx.indent_top] if indent > top_indent: # TokenType.Indent if lx.indent_top + 1 >= LEXER_MAX_INDENT: lx.error_count += 1 return lx.indent_top += 1 lx.indent_stack[lx.indent_top] = indent tok: Token | t.CPtr = new_token(lx.pool, TokenType.Indent, lx.lineno, 0) _emit(lx, tok) else: while indent < lx.indent_stack[lx.indent_top] and lx.indent_top > 0: lx.indent_top -= 1 tok: Token | t.CPtr = new_token(lx.pool, TokenType.Dedent, lx.lineno, 0) _emit(lx, tok) lx.at_line_start = 0 # ============================================================ # 标识符/关键字 # ============================================================ def _read_name(lx: Lexer | t.CPtr): start: t.CSizeT = lx.pos start_lineno: t.CInt = lx.lineno start_col: t.CInt = lx.col while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if _is_alnum(ch): lx.pos += 1 lx.col += 1 else: break length: t.CSizeT = lx.pos - start tok: Token | t.CPtr = new_token(lx.pool, TokenType.Name, start_lineno, start_col) if tok == None: return token_set_str(lx.pool, tok, lx.src, start, length) tok.end_lineno = lx.lineno tok.end_col_offset = start_col + t.CInt(length) # 查找关键字 kw_id: t.CInt = _kw_lookup(tok.str_val) if kw_id: tok.kw_subtype = kw_id _emit(lx, tok) # ============================================================ # 数字 # ============================================================ def _read_number(lx: Lexer | t.CPtr): start: t.CSizeT = lx.pos start_lineno: t.CInt = lx.lineno start_col: t.CInt = lx.col is_float: t.CInt = 0 is_complex: t.CInt = 0 ch0: t.CChar = lx.src[lx.pos] # 处理 0x/0o/0b 前缀 if ch0 == '0' and lx.pos + 1 < lx.len: ch1: t.CChar = lx.src[lx.pos + 1] if ch1 == 'x' or ch1 == 'X': lx.pos += 2 lx.col += 2 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if _is_hex(ch) or ch == '_': lx.pos += 1 lx.col += 1 else: break _emit_number(lx, start, start_lineno, start_col, 0, 0, 0) return if ch1 == 'o' or ch1 == 'O': lx.pos += 2 lx.col += 2 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if ch >= '0' and ch <= '7': lx.pos += 1 lx.col += 1 elif ch == '_': lx.pos += 1 lx.col += 1 else: break _emit_number(lx, start, start_lineno, start_col, 0, 0, 0) return if ch1 == 'b' or ch1 == 'B': lx.pos += 2 lx.col += 2 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if ch == '0' or ch == '1' or ch == '_': lx.pos += 1 lx.col += 1 else: break _emit_number(lx, start, start_lineno, start_col, 0, 0, 0) return # 十进制整数/浮点 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if _is_digit(ch) or ch == '_': lx.pos += 1 lx.col += 1 else: break # 小数部分 if lx.pos < lx.len and lx.src[lx.pos] == '.': is_float = 1 lx.pos += 1 lx.col += 1 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if _is_digit(ch) or ch == '_': lx.pos += 1 lx.col += 1 else: break # 指数部分 if lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if ch == 'e' or ch == 'E': is_float = 1 lx.pos += 1 lx.col += 1 if lx.pos < lx.len: ch = lx.src[lx.pos] if ch == '+' or ch == '-': lx.pos += 1 lx.col += 1 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if _is_digit(ch) or ch == '_': lx.pos += 1 lx.col += 1 else: break # 虚数后缀 if lx.pos < lx.len and lx.src[lx.pos] == 'j': is_complex = 1 is_float = 1 lx.pos += 1 lx.col += 1 _emit_number(lx, start, start_lineno, start_col, is_float, is_complex, 0) def _emit_number(lx: Lexer | t.CPtr, start: t.CSizeT, start_lineno: t.CInt, start_col: t.CInt, is_float: t.CInt, is_complex: t.CInt, is_hex: t.CInt): length: t.CSizeT = lx.pos - start tok: Token | t.CPtr = new_token(lx.pool, TokenType.Number, start_lineno, start_col) if tok == None: return token_set_str(lx.pool, tok, lx.src, start, length) tok.is_float = is_float tok.is_complex = is_complex tok.end_lineno = lx.lineno tok.end_col_offset = start_col + t.CInt(length) # 解析数值 if is_float: tok.float_val = string.atof(tok.str_val) else: tok.int_val = string.atoll(tok.str_val) _emit(lx, tok) # ============================================================ # 字符串 # ============================================================ def _read_string(lx: Lexer | t.CPtr): """读取字符串字面量。 支持前缀: r, b, u, f, rb, br, rf, fr(大小写不敏感) 支持引号: ', ", ''', """ start_lineno: t.CInt = lx.lineno start_col: t.CInt = lx.col prefix_start: t.CSizeT = lx.pos # 读取前缀(r/b/u/f) is_raw: t.CInt = 0 is_bytes: t.CInt = 0 is_fstring: t.CInt = 0 while lx.pos < lx.len: ch: t.CChar = lx.src[lx.pos] if ch == 'r' or ch == 'R': is_raw = 1 lx.pos += 1 lx.col += 1 elif ch == 'b' or ch == 'B': is_bytes = 1 lx.pos += 1 lx.col += 1 elif ch == 'u' or ch == 'U': lx.pos += 1 lx.col += 1 elif ch == 'f' or ch == 'F': is_fstring = 1 lx.pos += 1 lx.col += 1 else: break if lx.pos >= lx.len: lx.error_count += 1 return # 确定引号 quote: t.CChar = lx.src[lx.pos] if quote != '\'' and quote != '"': lx.error_count += 1 return # 检查三引号 is_triple: t.CInt = 0 if lx.pos + 2 < lx.len: if lx.src[lx.pos] == quote and lx.src[lx.pos + 1] == quote and lx.src[lx.pos + 2] == quote: is_triple = 1 content_start: t.CSizeT = lx.pos if is_triple: lx.pos += 3 lx.col += 3 else: lx.pos += 1 lx.col += 1 # 读取字符串内容 content_buf_start: t.CSizeT = lx.pos while lx.pos < lx.len: ch = lx.src[lx.pos] if is_triple: if ch == quote: if lx.pos + 2 < lx.len: if lx.src[lx.pos + 1] == quote and lx.src[lx.pos + 2] == quote: break elif lx.pos + 2 == lx.len: break if ch == '\\': lx.pos += 1 lx.col += 1 if lx.pos < lx.len: if lx.src[lx.pos] == '\n': lx.lineno += 1 lx.col = 0 lx.pos += 1 lx.col += 1 continue if ch == '\n': lx.lineno += 1 lx.col = 0 lx.pos += 1 lx.col += 1 else: if ch == quote: break if ch == '\n': # 单引号字符串不能跨行(除非续行) lx.error_count += 1 return if ch == '\\' and not is_raw: lx.pos += 1 lx.col += 1 if lx.pos < lx.len: lx.pos += 1 lx.col += 1 continue lx.pos += 1 lx.col += 1 content_end: t.CSizeT = lx.pos # 跳过结束引号 if is_triple: if lx.pos + 2 < lx.len: lx.pos += 3 lx.col += 3 else: lx.pos = lx.len else: if lx.pos < lx.len: lx.pos += 1 lx.col += 1 # 创建 token tok: Token | t.CPtr = new_token(lx.pool, TokenType.String, start_lineno, start_col) if tok == None: return content_len: t.CSizeT = content_end - content_buf_start # 处理转义序列(非 raw 字符串) if is_raw == 0 and content_len > 0: # 分配转换缓冲区(最大情况:每个字符都是普通字符,无需扩展) conv_buf: str = lx.pool.alloc(content_len + 1) if conv_buf == None: token_set_str(lx.pool, tok, lx.src, content_buf_start, content_len) else: wpos: t.CSizeT = 0 rpos: t.CSizeT = 0 while rpos < content_len: ch: t.CChar = lx.src[content_buf_start + rpos] if ch == '\\' and rpos + 1 < content_len: rpos += 1 next_ch: t.CChar = lx.src[content_buf_start + rpos] if next_ch == 'n': conv_buf[wpos] = '\n' elif next_ch == 't': conv_buf[wpos] = '\t' elif next_ch == 'r': conv_buf[wpos] = '\r' elif next_ch == '\\': conv_buf[wpos] = '\\' elif next_ch == '\'': conv_buf[wpos] = '\'' elif next_ch == '"': conv_buf[wpos] = '"' elif next_ch == '0': conv_buf[wpos] = '\0' else: # 未知转义,保留原样(包括 \xNN 等以后再处理) conv_buf[wpos] = '\\' wpos += 1 conv_buf[wpos] = next_ch wpos += 1 rpos += 1 else: conv_buf[wpos] = ch wpos += 1 rpos += 1 conv_buf[wpos] = '\0' token_set_str_literal(lx.pool, tok, conv_buf) else: token_set_str(lx.pool, tok, lx.src, content_buf_start, content_len) tok.end_lineno = lx.lineno tok.end_col_offset = lx.col # flags 字段复用:bit0=raw, bit1=bytes, bit2=fstring, bit3=triple if is_raw: tok.kw_subtype = tok.kw_subtype | 1 if is_bytes: tok.kw_subtype = tok.kw_subtype | 2 if is_fstring: tok.kw_subtype = tok.kw_subtype | 4 if is_triple: tok.kw_subtype = tok.kw_subtype | 8 _emit(lx, tok) # ============================================================ # 运算符 # ============================================================ def _read_operator(lx: Lexer | t.CPtr): """贪婪匹配运算符""" start_lineno: t.CInt = lx.lineno start_col: t.CInt = lx.col remaining: t.CSizeT = lx.len - lx.pos # 遍历运算符表,找最长匹配 best_entry: OpEntry | t.CPtr = None best_len: t.CInt = 0 cur: OpEntry | t.CPtr = _op_head while cur != None: if cur.length <= t.CInt(remaining): # 比较 cur.name 与 src[pos:pos+cur.length] match: t.CInt = 1 i: t.CSizeT = 0 while i < t.CSizeT(cur.length): if lx.src[lx.pos + i] != cur.name[i]: match = 0 break i += 1 if match: if cur.length > best_len: best_len = cur.length best_entry = cur cur = cur.next if best_entry == None: # 未知字符,报错并跳过 lx.error_count += 1 lx.pos += 1 lx.col += 1 return # 更新括号深度 if best_entry.op_id == TokOp.LPar or best_entry.op_id == TokOp.Lsqb or best_entry.op_id == TokOp.LBrace: lx.paren_depth += 1 elif best_entry.op_id == TokOp.RPar or best_entry.op_id == TokOp.Rsqb or best_entry.op_id == TokOp.RBrace: if lx.paren_depth > 0: lx.paren_depth -= 1 tok: Token | t.CPtr = _make_op_token(lx, best_entry.op_id, best_entry.length, start_lineno, start_col) if tok == None: return token_set_str(lx.pool, tok, lx.src, lx.pos, t.CSizeT(best_len)) lx.pos += t.CSizeT(best_len) lx.col += best_len _emit(lx, tok) # ============================================================ # 主词法分析入口 # ============================================================ def tokenize(lx: Lexer | t.CPtr) -> Token | t.CPtr: """执行词法分析,返回 token 链表头""" if lx == None: return None while lx.pos < lx.len: # 行首缩进处理 if lx.at_line_start and lx.paren_depth == 0: _handle_indent(lx) if lx.pos >= lx.len: break ch: t.CChar = _peek_cur(lx) if ch == '\0': break # 换行 if ch == '\n': if lx.paren_depth > 0: # 括号内,产生 TokenType.Nl tok: Token | t.CPtr = new_token(lx.pool, TokenType.Nl, lx.lineno, lx.col) _emit(lx, tok) else: # 逻辑换行 tok: Token | t.CPtr = new_token(lx.pool, TokenType.NewLine, lx.lineno, lx.col) _emit(lx, tok) lx.at_line_start = 1 lx.pos += 1 lx.lineno += 1 lx.col = 0 continue # 反斜线续行 if ch == '\\' and lx.pos + 1 < lx.len and lx.src[lx.pos + 1] == '\n': lx.pos += 2 lx.lineno += 1 lx.col = 0 continue # 空白 if _is_space(ch): lx.pos += 1 lx.col += 1 continue # 注释 if ch == '#': while lx.pos < lx.len and lx.src[lx.pos] != '\n': lx.pos += 1 continue # 字符串前缀(如 r"...")或字符串 if ch == '\'' or ch == '"': _read_string(lx) continue # 字符串前缀字母后跟引号(必须在 _is_alpha 之前检测,否则 f/r/b/u 会被当作 TokenType.Name) if (ch == 'r' or ch == 'R' or ch == 'b' or ch == 'B' or ch == 'u' or ch == 'U' or ch == 'f' or ch == 'F'): if lx.pos + 1 < lx.len: ch1: t.CChar = lx.src[lx.pos + 1] if ch1 == '\'' or ch1 == '"': _read_string(lx) continue # 双前缀如 rb, br, rf, fr if lx.pos + 2 < lx.len: ch2: t.CChar = lx.src[lx.pos + 2] if ((ch1 == 'r' or ch1 == 'R' or ch1 == 'b' or ch1 == 'B' or ch1 == 'f' or ch1 == 'F' or ch1 == 'u' or ch1 == 'U') and (ch2 == '\'' or ch2 == '"')): _read_string(lx) continue # 标识符/关键字 if _is_alpha(ch): _read_name(lx) continue # 数字 if _is_digit(ch): _read_number(lx) continue # 运算符 _read_operator(lx) # 文件结尾:产生 TokenType.NewLine(如果有内容) if lx.tokens_tail != None: if lx.tokens_tail.type != TokenType.NewLine and lx.tokens_tail.type != TokenType.Nl: tok: Token | t.CPtr = new_token(lx.pool, TokenType.NewLine, lx.lineno, lx.col) _emit(lx, tok) # 产生剩余 TokenType.Dedent while lx.indent_top > 0: lx.indent_top -= 1 tok: Token | t.CPtr = new_token(lx.pool, TokenType.Dedent, lx.lineno, 0) _emit(lx, tok) # TokenType.EndMarker tok: Token | t.CPtr = new_token(lx.pool, TokenType.EndMarker, lx.lineno, 0) _emit(lx, tok) return lx.tokens_head