在第一章中,我们已经写过一个小巧的四则运算解析器。那只是“冰山一角”。如果你想真正掌握 Lark,就必须深入理解它的 语法规则

本章将带你逐步认识 Lark 的语法系统:

  1. 规则的基本结构
  2. 终结符与非终结符
  3. 正则表达式在 Lark 中的使用
  4. 可选、重复、递归 等语法特性
  5. 解析树的生成与使用

2.1 Lark 语法文件结构

Lark 的语法采用接近 EBNF(扩展巴科斯范式) 的形式定义规则。一个语法文件通常包含以下几个部分:

  1. 起始规则(start):程序入口。
  2. 非终结符:由其他规则组合而成。
  3. 终结符(Token):由正则表达式或具体字符匹配。
  4. 特殊指令:如 %import%ignore

示例

start: expr

?expr: expr "+" term   -> add
     | expr "-" term   -> sub
     | term

?term: term "*" factor -> mul
     | term "/" factor -> div
     | factor

?factor: NUMBER
       | "(" expr ")"

%import common.NUMBER
%import common.WS
%ignore WS

这里我们可以看出:

  • start 是入口。
  • exprtermfactor 是非终结符。
  • NUMBER 是终结符,由 Lark 内置的正则规则导入。
  • %ignore WS 让我们忽略空格。

2.2 终结符(Token)

在 Lark 中,终结符用大写字母命名
它们通常用正则表达式来定义。

示例:定义一个标识符

IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_]*/

示例:定义数字

NUMBER: /\d+(\.\d+)?/

特殊的 %import

Lark 内置了很多常用的 Token,可以直接导入:

%import common.INT      // 整数
%import common.FLOAT    // 浮点数
%import common.WS       // 空格
%import common.CNAME    // 类似 Python 标识符

忽略空格

%ignore WS

这样就不用在语法里到处写空格规则了。


2.3 非终结符

非终结符用小写字母命名
它们是语法的核心,用来组合终结符和其他非终结符。

示例:表达式

expr: term "+" term

可选的问号(?

在规则名前加 ?,表示解析树中是否保留该规则。

?expr: term "+" term

这里的 expr 会在生成的解析树中 自动折叠,简化输出。


2.4 操作符与优先级

复杂语法常常需要处理 运算优先级

在 Lark 中,可以通过规则的递归定义实现优先级。

示例:四则运算

?expr: expr "+" term   -> add
     | expr "-" term   -> sub
     | term

?term: term "*" factor -> mul
     | term "/" factor -> div
     | factor

?factor: NUMBER
       | "(" expr ")"

这里:

  • expr 最高层(处理加减)。
  • term 中层(处理乘除)。
  • factor 最底层(数字或括号)。

因此,1 + 2 * 3 会被正确解析为 1 + (2 * 3)


2.5 正则表达式与 Lark

Lark 支持标准的 Python 正则表达式,用于定义终结符。

常见例子

  • 数字:NUMBER: /\d+/

  • 字母开头的变量:VAR: /[a-zA-Z_][a-zA-Z0-9_]*/

  • 字符串:

    STRING: /"[^"]*"/
          | /'[^']*'/
    

注意事项

  1. 正则写在 /.../ 之间。
  2. . 代表任意字符,* 代表重复。
  3. 避免写过度复杂的正则,解析器效率会下降。

2.6 可选、重复与递归

Lark 提供了 EBNF 风格的语法修饰符,非常适合表达常见规则。

可选(?

signed_number: "-"? NUMBER

表示可以有符号,也可以没有。

零次或多次(*

items: item*

表示可以有零个或多个 item

一次或多次(+

items: item+

表示至少有一个 item

递归(嵌套结构)

list: "[" [elements] "]"
elements: value ("," value)*

可以解析 ["a", "b", "c"] 这种结构。


2.7 示例:解析一个 JSON 子集

现在我们来实现一个 简化版 JSON 解析器

语法

?start: value

?value: STRING
      | NUMBER
      | object
      | array
      | "true"  -> true
      | "false" -> false
      | "null"  -> null

array  : "[" [value ("," value)*] "]"
object : "{" [pair ("," pair)*] "}"
pair   : STRING ":" value

%import common.CNAME
%import common.WS
%import common.NUMBER
%import common.ESCAPED_STRING -> STRING
%ignore WS

解析

from lark import Lark

json_parser = Lark(grammar, parser="lalr")
tree = json_parser.parse('{"name": "Alice", "age": 25, "isStudent": false}')
print(tree.pretty())

输出:

object
  pair
    STRING    "name"
    STRING    "Alice"
  pair
    STRING    "age"
    NUMBER    25
  pair
    STRING    "isStudent"
    false

转换为 Python 对象

from lark import Transformer

class JsonTransformer(Transformer):
    def STRING(self, s):
        return s[1:-1]  # 去掉引号
    def NUMBER(self, n):
        return float(n)
    def pair(self, kv):
        return (kv[0], kv[1])
    def object(self, items):
        return dict(items)
    def array(self, items):
        return list(items)
    def true(self, _):
        return True
    def false(self, _):
        return False
    def null(self, _):
        return None

transformer = JsonTransformer()
result = transformer.transform(tree)
print(result)

输出:

{'name': 'Alice', 'age': 25.0, 'isStudent': False}

这样,我们就写了一个迷你 JSON 解析器


2.8 解析树的调试与可视化

在开发语法时,调试是很重要的环节。

Lark 提供了几种常见调试方式:

  1. tree.pretty()
    直接打印树状结构。

  2. print(tree)
    得到紧凑的表示。

  3. 保存为 Graphviz 格式:

    tree.save("tree.dot")
    

    然后用 dot -Tpng tree.dot -o tree.png 转换为图片。


2.9 常见错误与排查

1. UnexpectedToken

  • 说明输入文本与语法不匹配。
  • 解决:检查正则或规则书写。

2. Infinite Recursion

  • 语法递归写错,导致死循环。
  • 解决:确保规则能“收敛”。

3. 多义性(Ambiguity)

  • 一个输入有多个可能解析树。
  • 解决:加上优先级规则,或使用 lalr 算法。

2.10 小结

在本章中,我们学习了:

  • Lark 语法文件的基本结构。
  • 终结符(Token)的定义与正则表达式用法。
  • 非终结符的定义与可选折叠。
  • EBNF 特性:可选、重复、递归。
  • 一个完整的 JSON 子集解析器示例。
  • 如何调试与排查常见错误。

到这里,你已经能够 自己写一个完整的语法,并将输入文本解析为结构化数据。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐