编译原理实战:基于 Python 3.11 实现简易四则运算词法分析器(附完整代码)
2026/7/13 23:17:42 网站建设 项目流程

编译原理实战:基于Python 3.11构建四则运算词法分析器

词法分析作为编译器的第一道工序,其核心任务是将字符流转换为有意义的单词序列。本文将以Python 3.11为开发环境,从零实现一个能够识别四则运算表达式(包含数字、运算符和括号)的词法分析器。通过这个实践项目,您将深入理解有限自动机(DFA)的理论如何转化为实际代码。

1. 词法分析器设计基础

词法分析器的本质是模式识别引擎,需要处理以下核心问题:

  • 如何定义各类词法单元(token)的模式
  • 如何高效扫描输入字符串
  • 如何处理边界条件和错误情况

在四则运算场景中,我们需要识别的token类型包括:

  • 整数(如42
  • 浮点数(如3.14
  • 运算符(+,-,*,/
  • 括号((,)
  • 空白字符(通常被忽略)

1.1 有限自动机的Python实现策略

确定有限自动机(DFA)的实现有两种典型方式:

  1. 状态转移表法:用二维表格显式表示状态转移
  2. 条件分支法:用代码逻辑隐式表达状态转移

我们将采用第二种方式,因为它更符合Python的动态特性,代码也更具可读性。以下是关键状态定义:

from enum import Enum, auto class State(Enum): START = auto() IN_INTEGER = auto() IN_FLOAT = auto() IN_OPERATOR = auto() IN_PAREN = auto() ERROR = auto()

1.2 正则表达式与手工实现的权衡

虽然Python的re模块可以快速实现词法分析,但手工实现DFA更有教学价值:

  • 更直观展示状态转移过程
  • 更容易添加自定义错误处理
  • 性能通常更优(避免正则引擎开销)

下表对比两种实现方式的特性:

特性正则表达式实现手工DFA实现
开发速度
运行效率中等
可调试性
错误处理灵活性有限完全可控
教学价值

2. 核心实现解析

2.1 Token类型定义

首先定义词法单元的数据结构:

from dataclasses import dataclass from typing import Literal @dataclass class Token: type: Literal["INTEGER", "FLOAT", "OPERATOR", "PAREN"] value: str position: int # 在源字符串中的起始位置

2.2 词法分析器类框架

构建词法分析器的主框架:

class Lexer: def __init__(self, text: str): self.text = text self.pos = 0 self.current_char = self.text[self.pos] if self.text else None def advance(self): """移动到下一个字符""" self.pos += 1 if self.pos < len(self.text): self.current_char = self.text[self.pos] else: self.current_char = None def skip_whitespace(self): """跳过空白字符""" while self.current_char is not None and self.current_char.isspace(): self.advance() def get_next_token(self) -> Token: """获取下一个token""" while self.current_char is not None: if self.current_char.isspace(): self.skip_whitespace() continue # 整数和浮点数识别 if self.current_char.isdigit(): return self.handle_number() # 运算符识别 if self.current_char in '+-*/': token = Token('OPERATOR', self.current_char, self.pos) self.advance() return token # 括号识别 if self.current_char in '()': token = Token('PAREN', self.current_char, self.pos) self.advance() return token raise Exception(f"Invalid character '{self.current_char}' at position {self.pos}") return None # 输入结束

2.3 数字识别算法

数字识别是词法分析中最复杂的部分,需要处理以下情况:

  • 纯整数(123
  • 浮点数(3.14
  • 科学计数法(本例暂不实现)
def handle_number(self) -> Token: """处理数字字面量(整数或浮点数)""" start_pos = self.pos num_str = '' has_decimal = False while self.current_char is not None and (self.current_char.isdigit() or self.current_char == '.'): if self.current_char == '.': if has_decimal: raise Exception(f"Multiple decimal points in number at position {self.pos}") has_decimal = True num_str += self.current_char self.advance() # 检查数字有效性 if num_str.endswith('.'): raise Exception(f"Trailing decimal point at position {self.pos}") return Token('FLOAT' if has_decimal else 'INTEGER', num_str, start_pos)

3. 高级功能扩展

3.1 错误恢复机制

健壮的词法分析器应该能够从错误中恢复,而不是遇到第一个错误就崩溃。我们添加错误恢复逻辑:

def get_next_token(self) -> Token: try: # 原有识别逻辑... except Exception as e: print(f"Lexical error: {e}") # 跳过当前字符尝试恢复 self.advance() return self.get_next_token() # 递归调用(注意实际项目中可能需要限制递归深度)

3.2 位置跟踪增强

为支持更好的错误报告,增强位置跟踪功能:

@dataclass class Position: line: int column: int @dataclass class Token: type: str value: str position: Position # 替换原来的整数位置 class Lexer: def __init__(self, text: str): self.line = 1 self.column = 1 # 其余初始化... def advance(self): if self.current_char == '\n': self.line += 1 self.column = 1 else: self.column += 1 # 其余advance逻辑...

4. 完整实现与测试

4.1 完整词法分析器代码

from dataclasses import dataclass from typing import Literal, Optional @dataclass class Position: line: int column: int @dataclass class Token: type: Literal["INTEGER", "FLOAT", "OPERATOR", "PAREN", "EOF"] value: str position: Position class Lexer: def __init__(self, text: str): self.text = text self.pos = 0 self.line = 1 self.column = 1 self.current_char = self.text[self.pos] if self.text else None def advance(self): if self.current_char == '\n': self.line += 1 self.column = 1 else: self.column += 1 self.pos += 1 if self.pos < len(self.text): self.current_char = self.text[self.pos] else: self.current_char = None def skip_whitespace(self): while self.current_char is not None and self.current_char.isspace(): self.advance() def handle_number(self) -> Token: start_pos = Position(self.line, self.column) num_str = '' has_decimal = False while self.current_char is not None and (self.current_char.isdigit() or self.current_char == '.'): if self.current_char == '.': if has_decimal: raise Exception(f"Multiple decimal points at {self.line}:{self.column}") has_decimal = True num_str += self.current_char self.advance() if num_str.endswith('.'): raise Exception(f"Trailing decimal point at {self.line}:{self.column}") return Token('FLOAT' if has_decimal else 'INTEGER', num_str, start_pos) def get_next_token(self) -> Optional[Token]: try: while self.current_char is not None: if self.current_char.isspace(): self.skip_whitespace() continue if self.current_char.isdigit(): return self.handle_number() if self.current_char in '+-*/': token = Token('OPERATOR', self.current_char, Position(self.line, self.column)) self.advance() return token if self.current_char in '()': token = Token('PAREN', self.current_char, Position(self.line, self.column)) self.advance() return token raise Exception(f"Unexpected character '{self.current_char}' at {self.line}:{self.column}") return Token('EOF', '', Position(self.line, self.column)) except Exception as e: print(f"Error: {e}") self.advance() return self.get_next_token() def tokenize(text: str) -> list[Token]: lexer = Lexer(text) tokens = [] while (token := lexer.get_next_token()) is not None: if token.type != 'EOF': tokens.append(token) return tokens

4.2 测试案例

验证词法分析器处理各种情况的能力:

# 测试混合表达式 expression = "3.14 * (42 + 6.28) / 0.5" tokens = tokenize(expression) for token in tokens: print(f"{token.type:8} {token.value:6} at {token.position.line}:{token.position.column}") # 预期输出: # FLOAT 3.14 at 1:1 # OPERATOR * at 1:6 # PAREN ( at 1:8 # INTEGER 42 at 1:9 # OPERATOR + at 1:12 # FLOAT 6.28 at 1:14 # PAREN ) at 1:18 # OPERATOR / at 1:20 # FLOAT 0.5 at 1:22

5. 性能优化与生产级考量

5.1 内存效率优化

对于大型源代码文件,可以优化内存使用:

  • 使用生成器逐步产生token而非构建完整列表
  • 避免不必要的字符串拼接

改进的token生成方法:

def tokenize_generator(text: str): lexer = Lexer(text) while (token := lexer.get_next_token()) is not None: if token.type != 'EOF': yield token

5.2 运算符扩展

实际编译器需要支持更多运算符类型:

OPERATORS = { '+': 'PLUS', '-': 'MINUS', '*': 'MUL', '/': 'DIV', '^': 'POW', '%': 'MOD', '=': 'ASSIGN', # 可以继续添加更多运算符 } # 在get_next_token方法中修改运算符识别: if self.current_char in OPERATORS: op = self.current_char # 处理多字符运算符(如==, +=等) self.advance() if op + (self.current_char or '') in {'==', '+=', '-=', '*=', '/='}: op += self.current_char self.advance() return Token('OPERATOR', op, Position(self.line, self.column))

5.3 与语法分析器的集成

词法分析器通常与语法分析器协同工作,可以通过以下方式改进接口:

class Parser: def __init__(self, lexer: Lexer): self.lexer = lexer self.current_token = self.lexer.get_next_token() def eat(self, token_type): if self.current_token.type == token_type: self.current_token = self.lexer.get_next_token() else: raise Exception(f"Expected {token_type}, got {self.current_token.type}") def parse(self): # 解析逻辑... pass

通过这个实践项目,我们不仅实现了四则运算的词法分析器,还展示了如何将编译原理中的DFA理论转化为实际代码。这种实现方式虽然比使用正则表达式更繁琐,但提供了更好的可扩展性和更深入的学习体验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询