1. 程式人生 > >Arcee:又一個 Parser Generator 輪子

Arcee:又一個 Parser Generator 輪子

sig 方法 install let expr example npm 教材 user

項目地址:tdkihrr/arcee

為什麽要做這樣一個東西呢?不是有Antlr嗎,Python下不是也有相應的bind嗎?人類為什麽又要再做一遍已經成熟了的東西呢?

答案是不爽!

之前刷 EOPL ,想用 Python 改寫其中的玩具語言,重寫了三四個後,感覺很別扭。教材裏自帶了一個parser,所以不用考慮解釋器前端的東西,但我用Python改寫時,由於沒有可口的前端,寫起來很不爽,每次寫完後端,都只能自己用 Python 手敲一遍AST,真的很麻煩,所以我就萌生了自己寫一個 parser generator 的想法。

所以,就有 Arcee 。

使用方法:

Install

$ pip install Arcee

Example

It‘s really readable.

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))))))))))

Now, you can use this ast to do what you like.

這個輪子目前還有一點小問題,不過自己用的話還是沒問題。由於工作緣故,估計是要去學 JavaScript 了,這個東西估計不會再更新了(也許哪天還會的。。。),到時估計就是重寫一個 npm 包吧,這個再說。

Arcee:又一個 Parser Generator 輪子