PLY - Python词法和语法分析器
PLY是一个纯Python实现的词法分析器(Lex)和语法分析器(Yacc)工具,用于创建编程语言解析器
PLY - Python词法和语法分析器
📝 概述
Python-PLY(Python Lex-Yacc)是一个纯Python实现的词法分析器(Lex)和语法分析器(Yacc)工具。它用于创建编程语言的解析器,可以帮助你定义和解析自定义的语法和文法规则。PLY是基于Unix工具lex和yacc的Python实现,广泛用于编译器构建、DSL(领域特定语言)设计和其他需要解析文本的应用。
🎯 学习目标
- 理解词法分析和语法分析的基本概念
- 掌握PLY的基本使用方法和工作原理
- 学会定义词法规则和语法规则
- 能够构建简单的解析器和计算器
- 了解PLY在实际项目中的应用场景
📋 前置知识
- Python基础语法和正则表达式
- 编译原理基本概念(词法分析、语法分析)
- 上下文无关文法的基本知识
- 函数和装饰器的使用
🔍 详细内容
基本概念
PLY包含两个主要模块:
- lex.py:用于词法分析(Lexical Analysis),将输入文本分解为标记(tokens)
- yacc.py:用于语法分析(Syntax Analysis),将标记流解析为抽象语法树(AST)或其他结构
安装方法
# 使用pip安装PLY
pip install ply
词法分析(Lex)
基本词法分析器定义
import ply.lex as lex
# 定义标记列表
tokens = (
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
)
# 定义标记的正则表达式规则
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# 定义NUMBER标记的正则表达式规则
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
# 定义忽略的字符(如空格和制表符)
t_ignore = ' \t'
# 定义处理换行符的规则
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
# 定义错误处理规则
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
# 构建词法分析器
lexer = lex.lex()
测试词法分析器
# 输入文本
data = '3 + 4 * (10 - 5)'
# 给词法分析器输入数据
lexer.input(data)
# 循环获取标记
while True:
tok = lexer.token()
if not tok:
break
print(tok)
输出结果:
LexToken(NUMBER, 3, 1, 0)
LexToken(PLUS, '+', 1, 2)
LexToken(NUMBER, 4, 1, 4)
LexToken(TIMES, '*', 1, 6)
LexToken(LPAREN, '(', 1, 8)
LexToken(NUMBER, 10, 1, 9)
LexToken(MINUS, '-', 1, 11)
LexToken(NUMBER, 5, 1, 13)
LexToken(RPAREN, ')', 1, 14)
语法分析(Yacc)
基本语法分析器定义
import ply.yacc as yacc
# 定义运算符优先级
precedence = (
('left', 'PLUS', 'MINUS'),
('left', 'TIMES', 'DIVIDE'),
('right', 'UMINUS'),
)
# 定义文法规则
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_divide(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
def p_error(p):
print(f"Syntax error at '{p.value}'")
# 构建语法分析器
parser = yacc.yacc()
语法规则参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
| p[0] | 任意 | 规则左边的非终结符的值 |
| p[1] | 任意 | 规则右边第一个符号的值 |
| p[2] | 任意 | 规则右边第二个符号的值 |
| p[n] | 任意 | 规则右边第n个符号的值 |
优先级说明
| 优先级类型 | 说明 |
|---|---|
| left | 左结合 |
| right | 右结合 |
| nonassoc | 非结合 |
💡 实际应用
基础用法 - 完整计算器
import ply.lex as lex
import ply.yacc as yacc
# Token定义
tokens = (
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
)
# 词法规则
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
t_ignore = ' \t'
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
# 构建词法分析器
lexer = lex.lex()
# 语法规则
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_divide(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
def p_error(p):
print(f"Syntax error at '{p.value}'")
# 定义运算符优先级
precedence = (
('left', 'PLUS', 'MINUS'),
('left', 'TIMES', 'DIVIDE'),
('right', 'UMINUS'),
)
# 构建语法分析器
parser = yacc.yacc()
# 测试
if __name__ == '__main__':
data = '3 + 4 * (10 - 5)'
result = parser.parse(data)
print(f"计算结果: {result}") # 输出: 23
高级用法 - 支持变量和函数
# 添加变量支持
symbol_table = {}
tokens += ('ID', 'EQUALS')
def t_ID(t):
r'[a-zA-Z_][a-zA-Z0-9_]*'
return t
t_EQUALS = r'='
def p_statement_assign(p):
'statement : ID EQUALS expression'
symbol_table[p[1]] = p[3]
def p_statement_expr(p):
'statement : expression'
print(p[1])
def p_expression_id(p):
'expression : ID'
try:
p[0] = symbol_table[p[1]]
except KeyError:
print(f"Undefined name '{p[1]}'")
p[0] = 0
实际案例 - DSL语言解析器
# 简单的配置文件解析器
def create_config_parser():
"""创建配置文件解析器"""
tokens = ('NAME', 'VALUE', 'SECTION', 'LBRACKET', 'RBRACKET', 'EQUALS')
def t_SECTION(t):
r'\[[^\]]+\]'
t.value = t.value[1:-1] # 去掉方括号
return t
def t_NAME(t):
r'[a-zA-Z_][a-zA-Z0-9_]*'
return t
def t_VALUE(t):
r'"[^"]*"|\'[^\']*\'|\S+'
if t.value.startswith('"') or t.value.startswith("'"):
t.value = t.value[1:-1] # 去掉引号
return t
t_EQUALS = r'='
t_ignore = ' \t'
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
return lex.lex()
# JSON解析器示例
def create_json_parser():
"""创建简单的JSON解析器"""
tokens = (
'NUMBER', 'STRING', 'TRUE', 'FALSE', 'NULL',
'LBRACE', 'RBRACE', 'LBRACKET', 'RBRACKET',
'COMMA', 'COLON'
)
def t_NUMBER(t):
r'-?\d+(\.\d+)?([eE][+-]?\d+)?'
if '.' in t.value or 'e' in t.value or 'E' in t.value:
t.value = float(t.value)
else:
t.value = int(t.value)
return t
def t_STRING(t):
r'"([^"\\]|\\.)*"'
t.value = t.value[1:-1] # 去掉引号
return t
t_TRUE = r'true'
t_FALSE = r'false'
t_NULL = r'null'
t_LBRACE = r'\{'
t_RBRACE = r'\}'
t_LBRACKET = r'\['
t_RBRACKET = r'\]'
t_COMMA = r','
t_COLON = r':'
t_ignore = ' \t\n'
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
return lex.lex()
⚠️ 注意事项
- 性能考虑:PLY是纯Python实现,性能可能不如C语言实现的lex和yacc
- 复杂性管理:对于非常复杂的语法,PLY的规则可能会变得难以管理
- 调试困难:语法错误的调试可能比较困难,需要仔细检查文法规则
- 文件生成:PLY会生成parser.out和parsetab.py文件,需要适当管理
- 规则冲突:注意移进/归约冲突和归约/归约冲突的处理
🔗 相关内容
📚 扩展阅读
📎 原始文档(完整迁移)
一、例子
# Example of parsing with PLY
from ply.lex import lex
from ply.yacc import yacc
# Token list
tokens = [ 'NUM', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'LPAREN', 'RPAREN' ]
# Ignored characters
t_ignore = ' \t\n'
# Token specifications (as regexs)
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# Token processing functions
def t_NUM(t):
r'\d+'
t.value = int(t.value)
return t
# Error handler
def t_error(t):
print('Bad character: {!r}'.format(t.value[0]))
t.skip(1)
# Build the lexer
lexer = lex()
# Grammar rules and handler functions
def p_expr(p):
'''
expr : expr PLUS term
| expr MINUS term
'''
if p[2] == '+':
p[0] = p[1] + p[3]
elif p[2] == '-':
p[0] = p[1] - p[3]
def p_expr_term(p):
'''
expr : term
'''
p[0] = p[1]
def p_term(p):
'''
term : term TIMES factor
| term DIVIDE factor
'''
if p[2] == '*':
p[0] = p[1] * p[3]
elif p[2] == '/':
p[0] = p[1] / p[3]
def p_term_factor(p):
'''
term : factor
'''
p[0] = p[1]
def p_factor(p):
'''
factor : NUM
'''
p[0] = p[1]
def p_factor_group(p):
'''
factor : LPAREN expr RPAREN
'''
p[0] = p[2]
def p_error(p):
print('Syntax error')
parser = yacc()
if __name__ == '__main__':
print(parser.parse('2'))
print(parser.parse('2+3'))
print(parser.parse('2+(3+4)*5'))
Python-PLY(Python Lex-Yacc) 是一个纯Python实现的词法分析器(Lex)和语法分析器(Yacc)工具。它用于创建编程语言的解析器,可以帮助你定义和解析自定义的语法和文法规则。PLY 是基于 Unix 工具 lex 和 yacc 的 Python 实现,广泛用于编译器构建、DSL(领域特定语言)设计和其他需要解析文本的应用。
1. PLY 的基本概念
PLY 包含两个主要模块:
- lex.py:用于词法分析(Lexical Analysis),将输入文本分解为标记(tokens)。
- yacc.py:用于语法分析(Syntax Analysis),将标记流解析为抽象语法树(AST)或其他结构。
2. 安装 PLY
你可以通过 pip 安装 PLY:
pip install ply
3. 词法分析(Lex)
词法分析是将输入文本分解为标记(tokens)的过程。以下是一个简单的词法分析器示例:
3.1 定义标记(Tokens)
import ply.lex as lex
# 定义标记列表
tokens = (
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
)
# 定义标记的正则表达式规则
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# 定义 NUMBER 标记的正则表达式规则
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
# 定义忽略的字符(如空格和制表符)
t_ignore = ' \t'
# 定义处理换行符的规则
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
# 定义错误处理规则
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
# 构建词法分析器
lexer = lex.lex()
3.2 测试词法分析器
# 输入文本
data = '3 + 4 * (10 - 5)'
# 给词法分析器输入数据
lexer.input(data)
# 循环获取标记
while True:
tok = lexer.token()
if not tok:
break
print(tok)
输出:
LexToken(NUMBER, 3, 1, 0)
LexToken(PLUS, '+', 1, 2)
LexToken(NUMBER, 4, 1, 4)
LexToken(TIMES, '*', 1, 6)
LexToken(LPAREN, '(', 1, 8)
LexToken(NUMBER, 10, 1, 9)
LexToken(MINUS, '-', 1, 11)
LexToken(NUMBER, 5, 1, 13)
LexToken(RPAREN, ')', 1, 14)
4. 语法分析(Yacc)
语法分析是将标记流解析为抽象语法树(AST)或其他结构的过程。以下是一个简单的语法分析器示例:
4.1 定义文法规则
import ply.yacc as yacc
# 定义运算符优先级
precedence = (
('left', 'PLUS', 'MINUS'),
('left', 'TIMES', 'DIVIDE'),
('right', 'UMINUS'),
)
# 定义文法规则
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_divide(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
def p_error(p):
print(f"Syntax error at '{p.value}'")
# 构建语法分析器
parser = yacc.yacc()
4.2 测试语法分析器
# 解析输入文本
result = parser.parse(data)
print("Result:", result)
输出:
Result: 23
5. 完整示例:计算器
以下是一个完整的计算器示例,支持加减乘除和括号:
import ply.lex as lex
import ply.yacc as yacc
# 定义标记
tokens = (
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
)
# 定义标记的正则表达式规则
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
t_ignore = ' \t'
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
# 构建词法分析器
lexer = lex.lex()
# 定义文法规则
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_divide(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
def p_error(p):
print(f"Syntax error at '{p.value}'")
# 定义运算符优先级
precedence = (
('left', 'PLUS', 'MINUS'),
('left', 'TIMES', 'DIVIDE'),
('right', 'UMINUS'),
)
# 构建语法分析器
parser = yacc.yacc()
# 测试
data = '3 + 4 * (10 - 5)'
result = parser.parse(data)
print("Result:", result)
输出:
Result: 23
6. 高级用法
6.1 处理负数
为了支持负数,你需要添加一个规则来处理一元减号(UMINUS):
def p_factor_minus(p):
'factor : MINUS factor %prec UMINUS'
p[0] = -p[2]
6.2 添加变量和函数
你可以扩展 PLY 来支持变量和函数。例如,添加一个符号表来存储变量:
symbol_table = {}
def p_expression_id(p):
'expression : ID'
p[0] = symbol_table[p[1]]
def p_expression_assign(p):
'expression : ID EQUALS expression'
symbol_table[p[1]] = p[3]
tokens += ('ID', 'EQUALS')
def t_ID(t):
r'[a-zA-Z_][a-zA-Z0-9_]*'
return t
t_EQUALS = r'='
# 更新文法规则
def p_statement_assign(p):
'statement : ID EQUALS expression'
symbol_table[p[1]] = p[3]
def p_statement_expr(p):
'statement : expression'
print(p[1])
7. PLY 的优点
- 纯 Python 实现:无需依赖外部工具,如 lex 和 yacc。
- 易于使用:提供简单的 API 来定义词法和语法规则。
- 灵活性:可以轻松扩展以支持复杂的语法和语义规则。
- 调试支持:提供调试工具来帮助诊断词法和语法错误。
8. PLY 的缺点
- 性能:由于 PLY 是纯 Python 实现,其性能可能不如 C 语言实现的 lex 和 yacc。
- 复杂性:对于非常复杂的语法,PLY 的规则可能会变得难以管理。
9. 实际应用
PLY 广泛用于以下场景:
- 编程语言的解析器。
- DSL(领域特定语言)的设计。
- 配置文件的解析。
- 数据格式的解析(如 JSON、XML 的子集)。
10. 其他工具
除了 PLY,还有其他 Python 解析工具:
- PyParsing:一个纯 Python 的解析库,不需要生成器。
- ANTLR:一个强大的解析器生成器,支持多种语言。
- Lark:一个现代的解析库,支持上下文无关文法(CFG)和 Earley 解析算法。
11. 总结
PLY 是一个强大的工具,可以帮助你构建自定义的解析器。通过定义词法和语法规则,你可以解析和处理各种文本格式。如果你需要构建一个简单的解析器,PLY 是一个不错的选择。如果你有其他问题或需要进一步的帮助,请告诉我!
二、涉及
—–ply—–
参考地址:https://www.cnblogs.com/LiuRunky/p/Python_Ply_Tutorial.html
参考地址:https://blog.csdn.net/feixiaoxing/article/details/79123776
🏷️ 标签
ply 词法分析 语法分析 编译器 DSL 解析器
最后更新: 2024-01-15
作者: Python技术文档工程师
版本: 1.0
讨论与反馈
欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。