项目地址:https://github.com/gcnyin/arcee
为什么要做这样一个东西呢?不是有Antlr吗,Python下不是也有相应的bind吗?人类为什么又要再做一遍已经成熟了的东西呢?
答案是不爽!
之前刷 EOPL ,想用 Python 改写其中的玩具语言,重写了三四个后,感觉很别扭。教材里自带了一个parser,所以不用考虑解释器前端的东西,但我用Python改写时,由于没有可口的前端,写起来很不爽,每次写完后端,都只能自己用 Python 手敲一遍AST,真的很麻烦,所以我就萌生了自己写一个 parser generator 的想法。
所以,就有 Arcee 。
使用方法:
Install
$ pip install Arcee
Example
首先创建grammar文件:
KEYWORDS : let, if, zero, -
NUMBER : \d+(\.\d*)?
ASSIGN : =
SUBTRACTION : -
RIGHT_BRACKET : (
COLON : ,
LETF_BRACKET : )
ID : [A-Za-z]+
SKIP : [ \\t]+
program : expression ;
expression : zeroexp
| diffexp
| ifexp
| varexp
| letexp
| constexp
;
constexp : $NUMBER ;
diffexp : '-' '(' expression ',' expression ')' ;
zeroexp : 'zero' '(' expression ')' ;
ifexp : 'if' expression 'then' expression 'else' expression ;
varexp : $ID ;
letexp : 'let' $ID '=' expression 'in' expression ;
在命令行里执行:
$ arcee grammar > result.py
result.py has three parts:
- Token
from collections import namedtuple
Token = namedtuple('Token', ['type', 'value', 'line', 'column'])
Program = namedtuple('Program', ['expression'])
# ...
- Lexer
import re
def tokenize(code):
pass # ...
- Parser
class Parser:
def __init__(self, token_list):
pass
# ...
def parse_expression(self):
if xxx:
self.parse_constexp()
elif yyy:
self.parse_diffexp()
#...
def parse_constexp(self):
pass
def parse_diffexp(self):
pass
def parse_zeroexp(self):
pass
def parse_ifexp(self):
pass
def parse_varexp(self):
pass
def parse_letexp(self):
pass
You can parse input such as:
input = '''let a = 0 in if zero(a) then -(a, 1) else -(a, 2)'''
tokens = list(tokenize(input))
parser = Parser(tokens)
parser.parse_program()
result is:
result = Program(
expression=Expression(
nonterminal=Letexp(
ID=Token(type='ID', value='a', line=2, column=4),
expression1=Expression(
nonterminal=Constexp(
NUMBER=Token(type='NUMBER', value='0', line=2, column=8))),
expression2=Expression(
nonterminal=Ifexp(
expression1=Expression(
nonterminal=Zeroexp(
expression=Expression(
nonterminal=Varexp(
ID=Token(type='ID', value='a', line=2, column=21))))),
expression2=Expression(
nonterminal=Diffexp(
expression1=Expression(
nonterminal=Varexp(
ID=Token(type='ID', value='a', line=2, column=31))),
expression2=Expression(
nonterminal=Constexp(
NUMBER=Token(type='NUMBER', value='1', line=2,
column=34))))),
expression3=Expression(
nonterminal=Diffexp(
expression1=Expression(
nonterminal=Varexp(
ID=Token(type='ID', value='a', line=2, column=44))),
expression2=Expression(
nonterminal=Constexp(
NUMBER=Token(type='NUMBER', value='2', line=2,
column=47))))))))))
这样就获得了AST。
这个轮子目前还有一点小问题,不过自己用的话还是没问题。由于工作缘故,估计是要去学 JavaScript 了,这个东西估计不会再更新了(也许哪天还会的。。。),到时估计就是重写一个 npm 包吧,这个再说。