LIBLR
Parser Generator for LR(1) and LALR
Install / Use
/learn @skywind3000/LIBLRREADME
Preface
类似 Yacc/Bison 的 Parser Generator:
- 给定文法 BNF 生成 parser。
- 支持 LR(1) 和 LALR 两种算法。
- 支持 L 型 SDT 的语义动作。
- 支持对接自定义非规则的 Lexer。
- 基于优先级的冲突处理,类似 Yacc/Bison 的优先级体系。
- 实时解析,支持根据语义分析结果指导接下来的词法/语法分析。
- 通过 C11 的文法测试。
- 单文件实现,没有额外依赖,方便集成。
Quick Start
简单例子
文法格式类似 Yacc/Bison,所有词法元素使用 @ignore 和 @match 定义。
import LIBLR
# 注意这里是 r 字符串,方便后面写正则
# 所有词法规则用 @ 开头,从上到下依次匹配
grammar = r'''
start: WORD ',' WORD '!';
@ignore [ \r\n\t]*
@match WORD \w+
'''
parser = LIBLR.create_parser(grammar)
print(parser('Hello, World !'))
输出:
Node(Symbol('start'), ['Hello', ',', 'World', '!'])
默认没有加 Semantic Action 的话,会返回一颗带注释的语法分析树(annotated parse-tree)。
语义动作
语义动作(Semantic Action)是在生成式中用 {name} 表达的部分,对应 name 的方法会在回调中被调用:
import LIBLR
# 注意这里是 r 字符串,方便后面写正则
grammar = r'''
# 事先声明终结符
%token number
E: E '+' T {add}
| E '-' T {sub}
| T {get1}
;
T: T '*' F {mul}
| T '/' F {div}
| F {get1}
;
F: number {getint}
| '(' E ')' {get2}
;
# 忽略空白
@ignore [ \r\n\t]*
# 词法规则
@match number \d+
'''
# 定义语义动作:各个动作由类成员实现,每个方法的
# 第一个参数 rule 是对应的生成式
# 第二个参数 args 是各个部分的值,类似 yacc/bison 中的 $0-$N
# args[1] 是生成式右边第一个符号的值,以此类推
# args[0] 是继承属性
class SemanticAction:
def add (self, rule, args):
return args[1] + args[3]
def sub (self, rule, args):
return args[1] - args[3]
def mul (self, rule, args):
return args[1] * args[3]
def div (self, rule, args):
return args[1] / args[3]
def get1 (self, rule, args):
return args[1]
def get2 (self, rule, args):
return args[2]
def getint (self, rule, args):
return int(args[1])
parser = LIBLR.create_parser(grammar, SemanticAction())
print(parser('1+2*3'))
输出结果
7
冲突处理
支持书写二义性规则,使用类似 Yacc/Bison 的优先级机制来确定发生冲突时使用哪个规则:
import LIBLR
# 注意这里是 r 字符串,方便后面写正则
grammar = r'''
%token NUMBER
%left '+' '-'
%left '*' '/' '%'
%right UMINUS
expr: expr '+' expr {add}
| expr '-' expr {sub}
| expr '*' expr {mul}
| expr '/' expr {div}
| '(' expr ')' {get2}
| '-' expr %prec UMINUS {negative}
| NUMBER {getint}
;
@ignore [ \r\n\t]*
@match NUMBER \d+
'''
class SemanticAction:
def add (self, rule, args):
return args[1] + args[3]
def sub (self, rule, args):
return args[1] - args[3]
def mul (self, rule, args):
return args[1] * args[3]
def div (self, rule, args):
return args[1] / args[3]
def get1 (self, rule, args):
return args[1]
def get2 (self, rule, args):
return args[2]
def getint (self, rule, args):
return int(args[1])
# 注意,这里对应生成式 expr: '-' expr,因为前面有减号了
# 所以右边 expr 的值对应的是 args[2]
def negative (self, rule, args):
return -(args[2])
parser = LIBLR.create_parser(grammar, SemanticAction())
print(parser('1+2*3+(5-2)*2'))
使用:%left, %right, %noassoc 同时定义优先级和结合方向,写在后面的终结符优先级更高。
默认一个生成式的优先级由最右边的终结符优先级决定,也可以显式的用 %prec 指明。
对比前面的例子,使用二意文法,能让 BNF 的书写精简不少,该程序的输出:
13
Manual
语法规则
<头部> : <符号1> <符号2> <符号3> ';'
例子,同一头部的不同生成式可以展开写:
start: symbol1 symbol2;
start: symbol3 symbol4;
或者也可以用竖线分割合起来写:
start: symbol1 symbol2 | symbol3 symbol4 ;
看起来更精简些
内嵌字符串终结符
if_statement: 'if' condition 'then' result ;
可以直接用内嵌字符串表示终结符,无需额外定义词法规则。
终结符定义
非内嵌字符串的终结符和 YACC 一样需要事先定义一下:
%token NUMBER
然后后面用 @match NAME pattern 来定义正则:
@match NUMBER \d+
词法规则从上到下的顺序进行匹配,除了 @match 外,还可以用 @ignore 来规定忽略哪些东西,比如你想跳过空格时,你可以在最开头写:
@ignore [ \r\n\t]*
否则碰到空格就 unexpected character 掉了。
语义动作
语义动作用 {name} 的形式在文法中声明,可以位于生成式右边的任意位置:
start: {action1} symbol1 {action2} symbol2 {acion3} ;
在 create_parser 的时候,第二个参数传进去一个回调对象:
callback = SemanticAction()
parser = LIBLR.create_parser(grammar, callback)
当动作发生时,callback 对象内部的同名方法会被调用,格式:
def my_action(rule, args):
第一个参数是对应生成式,第二个参数是生成式各个符号的值,其中 args[1] 是第一个符号的值,args[2] 是第二个符号的值,同 Yacc 一样,args[0] 表示继承属性。
语义动作计算好以后,使用 return 返回它的值。之所以不像 Yacc 那样直接把 C 代码写到文法动作里,只写一个名字,再靠外部的 callback 具体实现,就是为了不和某种语言耦合太深,将来可以支持导出其它语言。
Samples
| 文件名 | 说明 | |-|-| | sample_1.py | Hello, World | | sample_2.py | 表达式语法分析树 | | sample_3.py | 表达式计算 | | sample_4.py | 二义文法演示 | | sample_json.py | 带注释的 json 解析,文法见 这里 |
TODO
- [ ] Better error messages.
- [ ] Translate EBNF to BNF.
- [ ] Generate parsers in more languages.
- [ ] Implement GLR.
Related Skills
node-connect
338.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.6kCommit, push, and open a PR
