ply的简化api(python lex&yacc)。
ox-parser的Python项目详细描述
ox是一个简单的“编译器的编译器”框架,基于优秀的PLY 图书馆。
为什么是牛?
ply是一个很好的库,它是一个相当有效的纯python 实施yacc/bison。不过,我们认为它的api有点尴尬 做了很多奇怪的魔术。牛包主要层功能 变成一个更加功能化和简单的api,目的是在 仍然更容易使用。
ply是为yacc/bison设计的python替代品,不提供 作为构建编译器的通用框架的任何功能。牛 是一个极简的框架,提供了一些额外的功能(但是 它永远不会是python的替代品,比如llvm)。
ox已经足够成熟,可以用于生产代码,但是就像ply一样,它是 作为编译器入门课程的工具创建的。一个明确的教学法 ox的目标是使不同编译阶段的边界非常 明确且易于相互插入。这种方法有利于 教学,但它不会导致最有效或最有力的 真正编译器的实现。作为大多数编译器生成器,ox对于 快速试验,但性能有限,而且 重要的是,ox解析器通常无法为 语法错误。
名字呢?
ply是yacc的pythonic实现/解释。最广泛的 yacc的实现当然是gnu-bison。我们决定养牛 活的主题并称之为ox。
概念
编译通常分为几个步骤:
- 标记化/词法分析:一个源代码串被分解成 令牌列表。ox lexers是任何接收源字符串的函数 编码并返回令牌列表(或任何iterable)。
- 解析:将标记列表转换为语法树。在ox中,解析器 从BNF形式的语法派生而来。它接收一个令牌列表 输出任意解析树。
- 语义分析:对解析树进行语义错误扫描(例如 无效的变量名、无效的类型签名等)。解析树可以 在此过程中转换为不同的表示。
- 代码优化:为了生成 有效的内部陈述。这在很大程度上取决于目标 语言和运行时,它往往是一个真正的编译器的最大部分。
- 代码生成:中间表示用于在 目标语言。目标语言通常是低级语言,如 装配或机器代码。没有什么能阻止我们对Python或 然而,javascript。
ox主要关注步骤1和2。图书馆的支持非常有限 步骤3继续,但一般来说,它们往往非常针对具体的应用程序 像ox这样的通用工具几乎帮不上什么忙。
用法
ox可以通过简单地提供令牌名称列表来构建lexer函数。 与相应的正则表达式关联:
importoxlexer=ox.make_lexer([('NUMBER',r'\d+(\.\d*)?'),('PLUS',r'\+'),('MINUS',r'\-'),('MUL',r'\*'),('DIV',r'\/'),])
它声明了一个令牌赋予器函数,该函数接收一个源代码字符串,并且 返回令牌列表:
>>> lexer('21 + 21') [NUMBER('21'), PLUS('+'), NUMBER('21')]
当然,下一步是将这个令牌列表传递给解析器,以便 生成解析树。我们可以从映射中轻松地在ox中声明解析器 处理函数的语法规则。
每个处理程序函数从其对应的 语法规则并返回ast节点。在下面的示例中,我们返回元组 将我们的ast构建为类似lisp的s表达式。
binop=lambdax,op,y:(op,x,y)identity=lambdax:x
现在规则是:
parser=ox.make_parser([('expr : expr PLUS term',binop),('expr : expr MINUS term',binop),('expr : term',identity),('term : term MUL atom',binop),('term : term DIV atom',binop),('term : atom',identity),('atom : NUMBER',float),])
解析器获取一个令牌列表并将其转换为ast:
>>> parser(lexer('2 + 2 * 20')) ('+', 2.0, ('*', 2.0, 20.0))
ast使分析和计算表达式变得容易。我们可以 写一个简单的ev计算如下:
importoperatorasopoperations={'+':op.add,'-':op.sub,'*':op.mul,'/':op.truediv}defeval(node):ifisinstance(node,tuple):head,*tail=nodefunc=operations[head]args=(eval(x)forxintail)returnfunc(*args)else:returnnode
eval函数接收一个ast,但是我们可以很容易地将它与另一个 函数以接受字符串输入。(ox函数理解sidekick的 管道操作员。箭头运算符>>通过传递 将每个函数输出到箭头后面管道中的函数 方向)。
>>> eval_input = lexer >> parser >> eval >>> eval_input('2 + 2 * 20') 42.0
我们可以在一个循环中调用这个函数,让一个漂亮的计算器只用 几行python代码!
defeval_loop():expr=input('expr: ')print('result:',eval_input(expr))