使用 PLY python 将逗号解析为运算符时出现歧义

ambiguity in parsing comma as a operator using PLY python

我有以下标记以及更多标记,但我想保持我的问题简短,这就是为什么不包括整个代码的原因。

tokens = (
'COMMA',
'OP',
'FUNC1',
'FUNC2'
)

def t_OP(t):
    r'&|-|\||,'
    return t

def t_FUNC1(t):
    r'FUNC1'
    return t

def t_FUNC2(t):
    r'FUNC2'
    return t

其他方法:

def FUNC1(param):
  return {'a','b','c','d'}

def FUNC2(param,expression_result):
  return {'a','b','c','d'}

我在 YACC 中的语法规则是,还有一些,但列出了重要的规则:

'expression : expression OP expression'
'expression : LPAREN expression RPAREN'
'expression : FUNC1 LPAREN PARAM RPAREN'
'expression : FUNC2 LPAREN PARAM COMMA expression RPAREN'
'expression : SET_ITEM'

在我的yacc.py中,以下是与问题相关的方法:

def p_expr_op_expr(p):
    'expression : expression OP expression'
    if p[2] == '|' or p[2]== ',':
        p[0] = p[1] | p[3]
    elif p[2] == '&':
        p[0] = p[1] & p[3]
    elif p[2] == '-':
        p[0] = p[1] - p[3]

def p_expr_func1(p):
    'expression : FUNC1 LPAREN PARAM RPAREN'
    Param = p[3]
    Result = ANY(Param)
    p[0] = Result 

def p_expr_func2(p):
    'expression : FUNC2 LPAREN PARAM COMMA expression RPAREN'
    Param = p[3]
    expression_result = p[5]
    Result = EXPAND(Param,expression_result)
    p[0] = Result

def p_expr_set_item(p):
    'expression : SET_ITEM'
    p[0] = {p[1]}

所以,问题是:

如果我为这个语法给出以下输入表达式:

FUNC1("foo"),bar

-- 它正常工作,并给我结果作为 FUNC1("foo") and bar => {a,b,c,d} | {bar}

返回的 SET 的 UNION

但是,如果我给出下面的输入表达式,它会在 , 和 处给出语法错误): 我将括号定义为标记(对于那些认为括号未在标记中定义的人)

FUNC2("foo", FUNC1("foo"),bar)

根据我的说法,这个表达式符合生产规则 'expression : FUNC2 LPAREN PARAM COMMA expression RPAREN'

所以第一个逗号之后的所有内容都应该被很好地视为表达式,它应该匹配 'expression : expression OP expression' 并在遇到逗号作为运算符时执行并集。

如果是这样,那么它也不适用于 FUNC1("foo"),bar

我知道我可以通过从 t_OP(t) 中删除 ',' 并添加一个生产规则作为 'expression : expression COMMA expression' 来解决这个问题,这个规则的方法将如下所示:

def p_expr_comma_expr(p):
    'expression : expression COMMA expression'
    p[0] = p[1] | p[3]

我不愿意包含这条规则,因为它会引入“4 shift/reduce 冲突”。

我真的很想了解为什么它在一种情况下执行,为什么在另一种情况下不执行,以及将“,”视为运算符的方式是什么?

谢谢

Ply 无法知道您希望给定的 成为词素 COMMA 还是词素 OP。或者,更确切地说,它有一种方法,但它总是会选择相同的方法:OP。这是因为标记函数中的模式在模式变量中的标记之前被测试。

我假设您在程序的某个地方有 t_COMMA = r',' 您没有提供的部分。也有可能你有一个 token 函数来识别 COMMA,在这种情况下,哪个函数先出现就赢。但是不管你怎么做,测试正则表达式的顺序是固定的,所以 总是 COMMA 或者总是 OPSpecification of Tokens.

的 Ply 手册部分对此进行了很好的解释

就我个人而言,我建议从 OP 中删除逗号并修改语法以在 expression 的定义中使用 COMMA。正如您所观察到的,您将遇到 shift-reduce 冲突,因此您必须将其包含在您的优先声明中(您也选择从您的问题中省略)。事实上,您可能希望不同的运算符具有不同的优先级,因此您可能希望将不同的运算符分成不同的标记,因为优先级取决于标记。请参阅 precedence declarations.

的 Ply 手册部分中的解释

再添加一条规则解决了我的问题:

expression:expression COMMA expression

添加是因为正如@rici 所说,在像 FUNC2("hello",FUNC1("ghost")) 这样的表达式中,第一个逗号总是被用作运算符。

并添加优先事项删除 4shift/减少冲突。

precedence = (
    ('left','COMMA'),
    ('left','OP')
)