当前位置: 首页 > article >正文

基于 LR(1) 和 LALR 的 Parser Generator

最近处理文本比较多,先前想增强下正则,看来不够用了,有同学推荐了我 Pyl 和 Lark,看了两眼,初看还行,但细看有一些不太喜欢的地方,于是刚好春节几天有空,从头写了一个 LR(1) / LALR 的 Generator,只有一个 LIBLR.py 的单文件,没有其它依赖:

  • GitHub - skywind3000/LIBLR: Parser Generator for LR(1) and LALR

用法很简单,给定文法,返回 Parser:

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

Action 可以写在生成式右侧的任意位置,不象 Ply 和 Lark 那样只能写在最后,能用 args[0] 访问继承属性。之所以不像 Yacc 那样直接把代码贴到文法里,仅仅写个名字再由外层提供回调,是应为这样以后可以支持多种语言导出,不和会 Python 耦合太深,同时文法更干净点。

支持类似 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

词法部分也可以自己定义非规则 Lexer,传一个 Token 的 list 或者 generator (可以 for 和 next 的那种 python 对象)到 parser 第一个参数即可。

基本上实现了 Yacc 的主要功能,欢迎尝试。

更多阅读:

  • 使用 LIBLR 解析带注释的 JSON
  • 56 行代码用 Python 实现一个 Flex/Lex

http://www.kler.cn/a/380746.html

相关文章:

  • Scala的属性访问权限(一)默认访问权限
  • 【Java SE语法】重载(overload)和重写(override)一样吗?它们的区别是什么?
  • contenteditable实现需要一个像文本域一样的可编辑框
  • Redis 初学者指南
  • 智能交易模型的探索与发展
  • Docker 镜像体积优化实践:从基础镜像重建到层压缩的全流程指南
  • (九)JavaWeb后端开发——Servlet
  • 关于read/write 网络IO、硬盘IO的区别
  • PHP的线程安全与非线程安全版本的区别
  • 检索增强生成(RAG)
  • 【ArcGIS】绘制各省碳排放分布的中国地图
  • JAVA题目笔记(十三) 爬虫
  • python源码实例之根据出生日期判断你的星座-001
  • 1105--面试代码题
  • 网络安全渗透实际案例
  • 青少年编程与数学 02-003 Go语言网络编程 09课题、Cookie
  • 机器人技术革新:人工智能的强力驱动
  • Android AndroidManifest 文件内标签及属性
  • HTMLCSS:打造酷炫下载安装模拟按钮
  • 信通院大会:上海斯歌主题演讲《流程自动化到运营自主化》实录分享
  • 【Python】【数据可视化】【商务智能方法与应用】课程 作业一 飞桨AI Studio
  • 商业数据库 - oracle -表空间
  • 华为HarmonyOS借助AR引擎帮助应用实现虚拟与现实交互的能力5-识别平面语义
  • 聊一聊Spring中的@Transactional注解【中】【事务传播特性】
  • 【保姆级教程】使用 oh-my-posh 和 clink 打造个性化 PowerShell 和 CMD
  • vue 使用docx-preview 预览替换文档内的特定变量