Skip to content

Lark 解析库:用 Python 轻松构建语言解析器

1. Lark 简介

Lark 是一个现代化的 Python 解析库,它提供了高效、灵活的语言解析能力。无论你是在构建自定义的 DSL(领域专用语言),还是需要解析复杂的文本格式,Lark 都能以简洁优雅的方式满足需求。官方将其定位为一个用户体验优先、性能卓越、模块化设计的解析工具。

1.1 核心特性

Lark 提供了一套完整的解析工具链,主要特性包括:

  1. 多种解析算法支持 - Lark 内置了三种不同的解析算法:Earley(能解析任何上下文无关文法)、LALR(1)(性能最优)和 CYK(较少使用)。用户可以根据需求选择合适的算法平衡性能和表现力。

  2. 先进的语法定义 - 基于 EBNF(扩展巴科斯范式)的语法定义,支持多种便捷的符号表示法。例如,item* 表示零次或多次重复,item+ 表示一次或多次,item? 表示可选项,使得语法定义更加直观。

  3. 自动树构建 - Lark 会根据你定义的语法自动构建解析树,无需手工编写复杂的回调逻辑。这棵树是有注解的、结构化的,能直观地反映输入文本的语法结构。

  4. 高效的词法分析 - 内置快速的 Unicode 词法器,支持正则表达式,具有自动行计数功能,可直接用于错误报告和调试。

  5. 灵活的 Transformer 和 Visitor - 提供了两种遍历和转换解析树的方式,让你能够轻松地从原始的解析树生成最终的数据结构或执行结果。

2. 工作流程与基本概念

使用 Lark 的典型工作流程如下:

  1. 收集或创建输入示例 - 准备展示你的语言关键特性和行为的输入样本。这些样本应该覆盖你想要支持的各种语法构造。

  2. 编写语法规则 - 用 EBNF 格式定义你的语言语法。语法应该是直观的,尽可能模拟你向其他人解释这个语言的方式。

  3. 创建解析器实例 - 使用定义好的语法初始化 Lark 解析器,选择合适的解析算法。

  4. 解析输入文本 - 使用解析器处理输入文本,得到结构化的解析树。

  5. 转换和评估 - 使用 Transformer 或 Visitor 遍历解析树,将其转换为更符合业务需求的数据结构,或直接执行相关计算。

3. 实际示例

3.1 计算器 - 数学表达式解析

让我们从一个经典的计算器示例开始,展示如何用 Lark 解析和计算数学表达式。

python
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.0

3.2 JSON 解析器

JSON 是一个经典的解析案例,展示了如何处理更复杂的递归结构。

python
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 查询解析器。

python
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 支持文法的模块化,你可以在一个文法中导入其他文法的终端和规则,实现代码复用。

python
# 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 特别适用于以下场景:

  1. DSL 开发 - 构建自定义的领域专用语言,如配置语言、查询语言、脚本语言等。

  2. 数据格式解析 - 解析各种自定义的数据格式,如日志格式、配置文件格式等。

  3. 代码解析与转换 - 用于静态分析工具、代码转换工具,甚至编程语言的实现。

  4. 自然语言处理 - 虽然 Lark 设计用于形式语言,但其灵活性也使其能处理某些 NLP 任务。

  5. 测试与验证 - 为特定的文法规范编写解析器用于测试数据的有效性。

6. 与其他工具的比较

特性LarkPLYParsley
学习曲线温和陡峭温和
文法表达力极强(Earley)有限(LALR)中等
性能中等到快中等
易用性中等
维护状态活跃维护中活跃

总结

Lark 是 Python 生态中最用户友好的解析库。它兼顾了表达力、性能和易用性,使得即使是没有编译原理基础的开发者也能快速构建复杂的解析器。无论你是要编写一个配置文件解析器、实现一门小型语言,还是构建代码分析工具,Lark 都是一个值得推荐的选择。

项目地址:https://github.com/lark-parser/lark 官方文档:https://lark-parser.readthedocs.io/