Lark 解析库:用 Python 轻松构建语言解析器
1. Lark 简介
Lark 是一个现代化的 Python 解析库,它提供了高效、灵活的语言解析能力。无论你是在构建自定义的 DSL(领域专用语言),还是需要解析复杂的文本格式,Lark 都能以简洁优雅的方式满足需求。官方将其定位为一个用户体验优先、性能卓越、模块化设计的解析工具。
1.1 核心特性
Lark 提供了一套完整的解析工具链,主要特性包括:
多种解析算法支持 - Lark 内置了三种不同的解析算法:Earley(能解析任何上下文无关文法)、LALR(1)(性能最优)和 CYK(较少使用)。用户可以根据需求选择合适的算法平衡性能和表现力。
先进的语法定义 - 基于 EBNF(扩展巴科斯范式)的语法定义,支持多种便捷的符号表示法。例如,
item*表示零次或多次重复,item+表示一次或多次,item?表示可选项,使得语法定义更加直观。自动树构建 - Lark 会根据你定义的语法自动构建解析树,无需手工编写复杂的回调逻辑。这棵树是有注解的、结构化的,能直观地反映输入文本的语法结构。
高效的词法分析 - 内置快速的 Unicode 词法器,支持正则表达式,具有自动行计数功能,可直接用于错误报告和调试。
灵活的 Transformer 和 Visitor - 提供了两种遍历和转换解析树的方式,让你能够轻松地从原始的解析树生成最终的数据结构或执行结果。
2. 工作流程与基本概念
使用 Lark 的典型工作流程如下:
收集或创建输入示例 - 准备展示你的语言关键特性和行为的输入样本。这些样本应该覆盖你想要支持的各种语法构造。
编写语法规则 - 用 EBNF 格式定义你的语言语法。语法应该是直观的,尽可能模拟你向其他人解释这个语言的方式。
创建解析器实例 - 使用定义好的语法初始化 Lark 解析器,选择合适的解析算法。
解析输入文本 - 使用解析器处理输入文本,得到结构化的解析树。
转换和评估 - 使用 Transformer 或 Visitor 遍历解析树,将其转换为更符合业务需求的数据结构,或直接执行相关计算。
3. 实际示例
3.1 计算器 - 数学表达式解析
让我们从一个经典的计算器示例开始,展示如何用 Lark 解析和计算数学表达式。
from lark import Lark, Transformer, v_args
# 定义语法
CALC_GRAMMAR = """
?start: sum
?sum: product
| sum "+" product -> add
| sum "-" product -> sub
?product: atom
| product "*" atom -> mul
| product "/" atom -> div
?atom: NUMBER -> number
| "-" atom -> neg
| "(" sum ")"
%import common.NUMBER
%import common.WS
%ignore WS
"""
# 定义转换器来计算表达式
@v_args(inline=True)
class CalcTransformer(Transformer):
def number(self, token):
return float(token)
def add(self, a, b):
return a + b
def sub(self, a, b):
return a - b
def mul(self, a, b):
return a * b
def div(self, a, b):
return a / b
def neg(self, a):
return -a
# 创建解析器
parser = Lark(CALC_GRAMMAR, transformer=CalcTransformer())
# 使用示例
result = parser.parse("2 + 3 * 4")
print(f"2 + 3 * 4 = {result}") # 输出: 2 + 3 * 4 = 14.0
result = parser.parse("(2 + 3) * 4")
print(f"(2 + 3) * 4 = {result}") # 输出: (2 + 3) * 4 = 20.03.2 JSON 解析器
JSON 是一个经典的解析案例,展示了如何处理更复杂的递归结构。
from lark import Lark, Transformer, v_args
import json as json_module
# 定义 JSON 语法
JSON_GRAMMAR = """
?value: object
| array
| string
| SIGNED_NUMBER -> number
| "true" -> true
| "false" -> false
| "null" -> null
array : "[" [value ("," value)*] "]"
object : "{" [pair ("," pair)*] "}"
pair : string ":" value
string : ESCAPED_STRING
%import common.ESCAPED_STRING
%import common.SIGNED_NUMBER
%import common.WS
%ignore WS
"""
# 定义转换器
@v_args(inline=True)
class JSONTransformer(Transformer):
@staticmethod
def string(s):
return json_module.loads(s)
array = list
pair = tuple
object = dict
number = float
@staticmethod
def true():
return True
@staticmethod
def false():
return False
@staticmethod
def null():
return None
# 创建解析器
json_parser = Lark(JSON_GRAMMAR, transformer=JSONTransformer())
# 使用示例
json_string = '''
{
"name": "Alice",
"age": 30,
"active": true,
"skills": ["Python", "JavaScript", "Rust"]
}
'''
result = json_parser.parse(json_string)
print(result)
# 输出: {'name': 'Alice', 'age': 30.0, 'active': True, 'skills': ['Python', 'JavaScript', 'Rust']}3.3 简单的 SQL 查询解析
展示如何构建一个简单的 SQL 查询解析器。
from lark import Lark, Transformer
# 简单的 SQL 语法
SQL_GRAMMAR = """
?start: select_stmt
select_stmt: "SELECT" columns "FROM" table_name [where_clause]
columns: "*" | column_name ("," column_name)*
column_name: CNAME
table_name: CNAME
where_clause: "WHERE" condition
condition: column_name "=" value
value: ESCAPED_STRING | NUMBER
%import common.CNAME
%import common.ESCAPED_STRING
%import common.NUMBER
%import common.WS
%ignore WS
"""
class SQLTransformer(Transformer):
def select_stmt(self, items):
columns, table_name = None, None
where = None
for item in items:
if isinstance(item, list):
columns = item
elif isinstance(item, str):
table_name = item
elif isinstance(item, dict):
where = item
return {
"type": "SELECT",
"columns": columns,
"table": table_name,
"where": where
}
def columns(self, items):
return items
def column_name(self, items):
return str(items[0])
def table_name(self, items):
return str(items[0])
def where_clause(self, items):
return items[0]
def condition(self, items):
return {
"column": str(items[0]),
"value": str(items[1])
}
sql_parser = Lark(SQL_GRAMMAR, transformer=SQLTransformer())
# 使用示例
query = 'SELECT name, age FROM users WHERE age = "30"'
result = sql_parser.parse(query)
print(result)
# 输出: {'type': 'SELECT', 'columns': ['name', 'age'], 'table': 'users', 'where': {'column': 'age', 'value': '"30"'}}4. 高级功能
4.1 SPPF 与歧义文法
Lark 实现了 SPPF(Shared Packed Parse Forest),这是处理歧义文法的一种高效方式。当你的语法存在多种解析方式时,SPPF 能够在保持内存效率的前提下,存储所有可能的解析树。
4.2 文法组合与导入
Lark 支持文法的模块化,你可以在一个文法中导入其他文法的终端和规则,实现代码复用。
# grammar_base.lark
LETTER: "a".."z" | "A".."Z"
DIGIT: "0".."9"
# grammar_main.lark
%import grammar_base.LETTER
%import grammar_base.DIGIT
identifier: LETTER (LETTER | DIGIT)*4.3 性能优化
对于大规模的解析任务,你可以选择 LALR(1) 算法以获得最佳的时间空间复杂度(O(n))。Lark 还支持将解析器生成为独立的 Python 模块,减少依赖并加速启动时间。
5. 应用场景
Lark 特别适用于以下场景:
DSL 开发 - 构建自定义的领域专用语言,如配置语言、查询语言、脚本语言等。
数据格式解析 - 解析各种自定义的数据格式,如日志格式、配置文件格式等。
代码解析与转换 - 用于静态分析工具、代码转换工具,甚至编程语言的实现。
自然语言处理 - 虽然 Lark 设计用于形式语言,但其灵活性也使其能处理某些 NLP 任务。
测试与验证 - 为特定的文法规范编写解析器用于测试数据的有效性。
6. 与其他工具的比较
| 特性 | Lark | PLY | Parsley |
|---|---|---|---|
| 学习曲线 | 温和 | 陡峭 | 温和 |
| 文法表达力 | 极强(Earley) | 有限(LALR) | 中等 |
| 性能 | 中等到快 | 快 | 中等 |
| 易用性 | 高 | 低 | 中等 |
| 维护状态 | 活跃 | 维护中 | 活跃 |
总结
Lark 是 Python 生态中最用户友好的解析库。它兼顾了表达力、性能和易用性,使得即使是没有编译原理基础的开发者也能快速构建复杂的解析器。无论你是要编写一个配置文件解析器、实现一门小型语言,还是构建代码分析工具,Lark 都是一个值得推荐的选择。
项目地址:https://github.com/lark-parser/lark 官方文档:https://lark-parser.readthedocs.io/