PLY使用心得

最近在利用python做语法解析,使用了PLY,就是python里的lex+yacc。
一些使用心得记录一下:

  • 以t_打头的lex规则如果出现冲突,并不完全是以出现的位置先后来确定优先级。从使用来看,正则匹配优先级更高,文本匹配优先级较低,比如下面的例子:
t_ID = r'[a-zA-Z_][a-zA-Z0-9_]*'
t_AND = 'and'
t_OR = 'or'

会导致将关键字and和or也解析为ID,这不是我们想要的行为。可以定义保留字字典来解决:

RESERVED = {
  "and": "AND",
  "or": "OR",
  }

def t_ID(t):
    r'[a-zA-Z_][a-zA-Z0-9_]*'
    t.type = RESERVED.get(t.value, "ID")
    return t

但是两个正则匹配之间,还是以出现的位置先后定优先级的,比如下面例子,如果掉个个,就不行了:

def t_FLOATING(t):
    r'\d+\.\d+'
    t.value = float(t.value)
    return t


def t_INTEGER(t):
    r'\d+'
    t.value = int(t.value)
    return t

如果INTEGER规则在先,那么10.0会解析为3个token:
(10, INTEGER) (‘.’, DOT) (0, INTEGER)
这就不是我们想要的了。

  • 终结符用tokens变量表示,是一个tuple
  • 算符优先级使用precedence来定义,这样在写语法规则的时候,可以将规则写得简单很多,比如java语言常用的算符优先级定义如下:
precedence = (
                ('left', 'COMMA'),
                ('right','ASSIGN'),
                ('left','AND','OR'),
                ('left','EQ','NE'),
                ('left','LE','LT', 'GE', 'GT'),
                ('left','PLUS','MINUS'),
                ('left','MULT','DIV', 'MOD'),
                ('right', 'POWER'),
                ('right', 'NOT')
    )

越靠前的,算符优先级越低,同一层的算符优先级相同。当优先级相同的算符连续出现时,就要看是左结合还是右结合(代码里left和right所表示的含义),比如+和-是左结合,那么a + b - c执行顺序是(a+b)-c;再比如赋值是右结合,那么a=b=c的执行顺序是a=(b=c)。

  • 语法是以p_打头的函数,语法规则写在函数的doc里,形如:
def p_var(p):
    """
    var : HASH ID
        | var member_ref ID
    """
    if len(p) == 3:
        p.parser.context.do_sth1(p[2])
    else:
        p.parser.context.do_sth2(p[3])

p是一条规则的节点集合。p.parser就是yacc解析器。我个人的习惯,会做一个自己的ParseContext,放到p.parser里,收集各语法节点信息。
最后,可以用一个类把lex和yacc都封装起来:

class MyInterpreter(object):
    def __init__(self):
        self.lexer = lex.lex()
        self.parser = yacc.yacc()
        # 这里的self.parser就是p.parser,可以把自己的ParseContext塞进去,记录必要的信息
        self.parser.context = ParseContext()

    def parse(self, code):
        self.parser.context.clear()
        # 这里需指定lexer
        self.parser.parse(code, lexer=self.lexer)
        return self.parser.context

一旦yacc.yacc()执行,PLY就会开始分析我们的语法,如果提示里出现shift/reduce conflict,一般要通过增加算符优先级或改变语法规则的写法来解决。例如下面的四则运算规则:

def p_arith_expr(p):
    """
    arith_expr :  expr PLUS expr
        | expr MINUS expr
        | expr MULT expr
        | expr DIV expr
        | expr MOD expr
        | expr POWER expr
    """

如果不设置几个算符的优先级,肯定会出现shift/reduce conflict的。

还有一种情况,也可能出现shift/reduce conflict:

rule1
A : C

rule2
B : C

其实是C在reduce的时候产生了疑惑:到底该用rule1还是rule2做reduce?要解决该问题,可以把C后面的符号纳入规则,这样写:

rule1
A : C D

rule2
B : C E
  • 常量字符串匹配规则。我们选一个较复杂的,字符串既可用单引号也可用双引号括起,如字符串内部还有单引号或双引号,用反斜杠转义。lex规则这么写:
def t_STR(t):
    r""""([^\\"]|\\")*"|'([^\\']|\\')*'"""
    t.value = t.value[1:-1].encode().decode("unicode-escape")
    return t

拿到结果后,要注意做一个string-escape动作,把字符串内的反斜杠清掉。
验证的UT:

def test_lex_str(self):
    self.assertEqual(self.parse_token("'abc'"), [('abc', 'STR')])
    self.assertEqual(self.parse_token('"abc"'), [('abc', 'STR')])
    self.assertEqual(self.parse_token("'ab\\'c'"), [("ab'c", 'STR')])
    self.assertEqual(self.parse_token('"ab\\"c"'), [('ab"c', 'STR')])
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值