带 pyparsing 的串联三元运算符
Concatenated ternary operators with pyparsing
使用 pyparsing,我希望能够解析以下语法:
1?1:0?1:0
应该理解为一个标准的三元运算符condition ? true_part : false_part
,其中两个简单的串联起来,使得第一个的结果成为第二个的条件
到目前为止我有以下代码(简化):
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?", ":"), 3, pp.opAssoc.LEFT),
])
TERNARY_INFIX.parseString("1?1:0?1:0", parseAll=True)
产生:
ParseException: Expected end of text (at char 5), (line:1, col:6)
除非我在两个三元表达式之一周围添加括号,因此例如 "(1?1:0)?1:0"
和 "1?1:(0?1:0)"
有效。
但是我怎样才能让它在没有括号的情况下工作,基本上只是按照严格的左关联方式从左到右阅读?
编辑:
关于三元运算符的关联性如何工作的好读物:Ternary operator left associativity - 结果是 left-assoc 没有多大意义。然而,我试图模仿的语言实际上是从左到右处理这些表达式的。
你可以尝试把每个算子分开写
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?"), 2, pp.opAssoc.LEFT),
((":"), 2, pp.opAssoc.LEFT)
])
TERNARY_INFIX.parseString("1?1:0?1:0", parseAll=True)
输出:
[[[1, '?', 1], ':', [0, '?', 1], ':', 0]]
我认为这个运算符实际上是右结合的,而不是左结合的。如果我将您的代码更改为:
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?", ":"), 3, pp.opAssoc.RIGHT),
])
TERNARY_INFIX.runTests("""\
1?1:(0?1:0)
(1?1:0)?1:0
1?1:0?1:0
""", fullDump=False)
然后我得到了合理的输出,没有parens的输入没有错误:
1?1:(0?1:0)
[[1, '?', 1, ':', [0, '?', 1, ':', 0]]]
(1?1:0)?1:0
[[[1, '?', 1, ':', 0], '?', 1, ':', 0]]
1?1:0?1:0
[[1, '?', 1, ':', [0, '?', 1, ':', 0]]]
这是一个更大的表达式,用于评估 3 个变量中的最大变量(来自此 C 教程:http://cprogramming.language-tutorial.com/2012/01/biggest-of-3-numbers-using-ternary.html):
TERNARY = pp.infixNotation(
pp.Char("abc"), [
(pp.oneOf("> <"), 2, pp.opAssoc.LEFT),
(("?", ":"), 3, pp.opAssoc.RIGHT),
])
TERNARY.runTests("""\
(a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)
a > b ? a > c ? a : c : b > c ? b : c
""", fullDump=False)
给出:
(a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)
[[['a', '>', 'b'], '?', [['a', '>', 'c'], '?', 'a', ':', 'c'], ':', [['b', '>', 'c'], '?', 'b', ':', 'c']]]
a > b ? a > c ? a : c : b > c ? b : c
[[['a', '>', 'b'], '?', [['a', '>', 'c'], '?', 'a', ':', 'c'], ':', [['b', '>', 'c'], '?', 'b', ':', 'c']]]
编辑:我现在看到这与重复二元运算符的情况类似,例如“1 + 2 + 3”。左关联,pyparsing 不将它们解析为 [['1' '+' '2'] '+' '3']
,而只是 ['1' '+' '2' '+' '3']
,并且由 evaulator 进行重复的从左到右的评估。
当我添加三元运算符时,我没有预想像您正在解析的那样的链式形式。对 infixNotation
的一行更改将 使用左关联性成功解析您的表达式,但就像链接的二元运算符一样给出未分组的结果:
[1, '?', 1, ':', 0, '?', 1, ':', 0]
像重复加法的例子,由求值者做从左到右的连续求值,比如:
def eval_ternary(tokens):
operands = tokens[0]
ret = bool(operands[0])
i = 1
while i < len(operands):
ret = bool(operands[i+1]) if ret else bool(operands[i+3])
i += 4
return ret
如果您想手动修补您的 pyparsing 代码,请更改:
elif arity == 3:
matchExpr = _FB(
lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
) + Group(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr)
至:
elif arity == 3:
matchExpr = _FB(
lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))
^^^^^^^^^^
在 pyparsing.py 中进行此更改,或将 infxNotation
的定义复制到您自己的代码中并在那里进行更改。
我将在下一个 pyparsing 版本中进行此更改。
编辑 - 已在 pyparsing 2.4.6 中修复,刚刚发布。
使用 pyparsing,我希望能够解析以下语法:
1?1:0?1:0
应该理解为一个标准的三元运算符condition ? true_part : false_part
,其中两个简单的串联起来,使得第一个的结果成为第二个的条件
到目前为止我有以下代码(简化):
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?", ":"), 3, pp.opAssoc.LEFT),
])
TERNARY_INFIX.parseString("1?1:0?1:0", parseAll=True)
产生:
ParseException: Expected end of text (at char 5), (line:1, col:6)
除非我在两个三元表达式之一周围添加括号,因此例如 "(1?1:0)?1:0"
和 "1?1:(0?1:0)"
有效。
但是我怎样才能让它在没有括号的情况下工作,基本上只是按照严格的左关联方式从左到右阅读?
编辑:
关于三元运算符的关联性如何工作的好读物:Ternary operator left associativity - 结果是 left-assoc 没有多大意义。然而,我试图模仿的语言实际上是从左到右处理这些表达式的。
你可以尝试把每个算子分开写
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?"), 2, pp.opAssoc.LEFT),
((":"), 2, pp.opAssoc.LEFT)
])
TERNARY_INFIX.parseString("1?1:0?1:0", parseAll=True)
输出:
[[[1, '?', 1], ':', [0, '?', 1], ':', 0]]
我认为这个运算符实际上是右结合的,而不是左结合的。如果我将您的代码更改为:
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?", ":"), 3, pp.opAssoc.RIGHT),
])
TERNARY_INFIX.runTests("""\
1?1:(0?1:0)
(1?1:0)?1:0
1?1:0?1:0
""", fullDump=False)
然后我得到了合理的输出,没有parens的输入没有错误:
1?1:(0?1:0)
[[1, '?', 1, ':', [0, '?', 1, ':', 0]]]
(1?1:0)?1:0
[[[1, '?', 1, ':', 0], '?', 1, ':', 0]]
1?1:0?1:0
[[1, '?', 1, ':', [0, '?', 1, ':', 0]]]
这是一个更大的表达式,用于评估 3 个变量中的最大变量(来自此 C 教程:http://cprogramming.language-tutorial.com/2012/01/biggest-of-3-numbers-using-ternary.html):
TERNARY = pp.infixNotation(
pp.Char("abc"), [
(pp.oneOf("> <"), 2, pp.opAssoc.LEFT),
(("?", ":"), 3, pp.opAssoc.RIGHT),
])
TERNARY.runTests("""\
(a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)
a > b ? a > c ? a : c : b > c ? b : c
""", fullDump=False)
给出:
(a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)
[[['a', '>', 'b'], '?', [['a', '>', 'c'], '?', 'a', ':', 'c'], ':', [['b', '>', 'c'], '?', 'b', ':', 'c']]]
a > b ? a > c ? a : c : b > c ? b : c
[[['a', '>', 'b'], '?', [['a', '>', 'c'], '?', 'a', ':', 'c'], ':', [['b', '>', 'c'], '?', 'b', ':', 'c']]]
编辑:我现在看到这与重复二元运算符的情况类似,例如“1 + 2 + 3”。左关联,pyparsing 不将它们解析为 [['1' '+' '2'] '+' '3']
,而只是 ['1' '+' '2' '+' '3']
,并且由 evaulator 进行重复的从左到右的评估。
当我添加三元运算符时,我没有预想像您正在解析的那样的链式形式。对 infixNotation
的一行更改将 使用左关联性成功解析您的表达式,但就像链接的二元运算符一样给出未分组的结果:
[1, '?', 1, ':', 0, '?', 1, ':', 0]
像重复加法的例子,由求值者做从左到右的连续求值,比如:
def eval_ternary(tokens):
operands = tokens[0]
ret = bool(operands[0])
i = 1
while i < len(operands):
ret = bool(operands[i+1]) if ret else bool(operands[i+3])
i += 4
return ret
如果您想手动修补您的 pyparsing 代码,请更改:
elif arity == 3:
matchExpr = _FB(
lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
) + Group(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr)
至:
elif arity == 3:
matchExpr = _FB(
lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))
^^^^^^^^^^
在 pyparsing.py 中进行此更改,或将 infxNotation
的定义复制到您自己的代码中并在那里进行更改。
我将在下一个 pyparsing 版本中进行此更改。
编辑 - 已在 pyparsing 2.4.6 中修复,刚刚发布。