1. 词法分析器框架搭建

词法分析器框架搭建 学习卡片

任务目标

实现一个词法分析器框架,支持以正则表达式定义词法规则,对输入源码进行扫描,输出带类型与位置信息的词法单元流。本案例聚焦框架核心:规则注册、最长匹配、单元生成与错误报告。

环境依赖

Python 3.8+,仅使用标准库 re,无第三方依赖。

核心实现

import re
from dataclasses import dataclass
from typing import List, Tuple, Optional

@dataclass
class :
    type: str
    value: str
    line: int
    col: int

class Lexer:
    def __init__(self):
        self.rules = []  # (type, compiled_pattern)

    def add_rule(self, _type: str, pattern: str):
        """注册词法规则,pattern为正则表达式字符串"""
        compiled = re.compile(pattern)
        self.rules.append((_type, compiled))

    def tokenize(self, code: str) -> Tuple[List[], List[str]]:
        tokens = []
        errors = []
        pos = 0
        line = 1
        line_start = 0
        n = len(code)

        while pos < n:
            # 处理换行
            if code[pos] == '\n':
                line += 1
                line_start = pos + 1
                pos += 1
                continue

            # 跳过空白(空格、制表符、回车)
            if code[pos] in ' \t\r':
                pos += 1
                continue

            best_match = None
            best_type = None
            best_len = 0

            # 遍历所有规则,找最长匹配
            for _type, pattern in self.rules:
                m = pattern.match(code, pos)
                if m:
                    length = m.end() - m.start()
                    if length > best_len:
                        best_len = length
                        best_match = m
                        best_type = _type

            if best_match is None:
                # 无法匹配任何规则
                col = pos - line_start + 1
                errors.append(f"第{line}行第{col}列: 无法识别的字符 '{code[pos]}'")
                pos += 1
            else:
                value = best_match.group(0)
                col = pos - line_start + 1
                tokens.append((best_type, value, line, col))
                pos += best_len

        return tokens, errors

# 使用示例
if __name__ == "__main__":
    lexer = Lexer()
    lexer.add_rule("NUMBER", r'\d+')
    lexer.add_rule("IDENT", r'[A-Za-z_]\w*')
    lexer.add_rule("OP", r'[+\-*/=]')
    lexer.add_rule("LPAREN", r'\(')
    lexer.add_rule("RPAREN", r'\)')

    source = "x = 42 + total * 3"
    tokens, errors = lexer.tokenize(source)

    for tok in tokens:
        print(f"{tok.type:<8} {tok.value!r:<12} 行{tok.line} 列{tok.col}")
    if errors:
        print("\n错误:")
        for e in errors:
            print(e)

运行方式

python lexer.py

验证方法

运行后观察输出:应得到6个词法单元,类型依次为 IDENT、OP、NUMBER、IDENT、OP、NUMBER,且每个单元的行列位置与源码实际位置一致。若输入包含无法匹配的字符(如 @),框架应报告包含行号、列号及具体字符的错误信息,并跳过该字符继续扫描。

验证要点

  • 最长匹配:输入 abc123 时,若同时注册了 IDENT([A-Za-z_]\w*)和 NUMBER(\d+),应整体识别为 IDENT,而非先识别 abc 再识别 123。
  • 位置追踪:多行输入时,换行后行号递增,列号从1重新计算。
  • 错误恢复:遇到非法字符时报告错误并继续,不中断整个扫描过程。