使用 PyParsing 解析带有重要换行符的语言(如 Python)

2024-01-25

我正在实现一种语言,其中换行符有时很重要,就像在 Python 中一样,具有完全相同的规则。

出于我的问题的目的,我们可以采用与赋值、括号以及换行符和分号处理有关的 Python 片段。

例如,可以这样写:

a = 1 + 2 + 3    # ok
b = c

but not

a = 1 + 2 + 3     b = c   # incorrect

因为需要一个换行符来分隔两个语句。

然而我们可以有

a = 1 + 2 + 3;     b = c   # ok

使用分号。

也不允许有

a = 1 + 2 +   # incorrect
3
b = c

因为语句中不能有换行符。

然而,有可能有

a = 1 + 2 + (     # ok
3)
b = c

or

a = 1 + 2 + \     # ok
3
b = c

我一直在尝试执行上述规则,但我陷入困境。

首先,我使用

ParserElement.setDefaultWhitespaceChars(' \t')

所以现在\n是重要的。

我很好地使用换行符作为分隔符

lines = ZeroOrMore(line + OneOrMore(LineEnd()))

这种变化允许有;也作为分隔符。 (我不太能处理继续括号\.)

I use infixNotation界定+, -, /, *.

我遇到的问题是括号内的换行符应该被忽略,就像在这种情况下:

a = 1 + 2 + ( 
3 +
1)

我认为这里可以发挥作用的是使用setWhitespaceChars在括号表达式上(LPAR + term + RPAR)然而, infixNotation 生成的代码不起作用,因为较低的表达式不会继承空白字符。

有人有任何提示吗?

我的问题也可以表达为“如何使用 pyParsing 解析 Python(的片段)?”。我以为我可以找到一些示例项目,但我没有。谷歌搜索,我看到人们引用了 pyParsing 存储库中的示例,但是parsePythonValue.py是关于解析值(我已经可以做到)并且不处理重要的换行符,并且pythongGrammarParsing.py是关于解析 Python 的 BNF 语法,而不是解析 Python。


注意:这不是一个可行的解决方案(至少目前还不是)。它依赖于未发布的 pyparsing 更改,这些更改甚至还没有通过所有单元测试。我发布它只是为了描述解决方案的可能方法。

噢!这比我想象的要困难得多。为了实现,我使用了 pyparsing 的忽略机制,并将解析操作附加到lpar and rpar要忽略的表达式<NL>位于括号内,但不在括号外。这还需要添加清除的能力ignoreExprs通过调用列出expr.ignore(None)。您的代码可能如下所示:

import pyparsing as pp

# works with and without packrat
pp.ParserElement.enablePackrat()

pp.ParserElement.setDefaultWhitespaceChars(' \t')

operand = pp.Word(pp.nums)
var = pp.Word(pp.alphas)

arith_expr = pp.Forward()
arith_expr.ignore(pp.pythonStyleComment)
lpar = pp.Suppress("(")
rpar = pp.Suppress(")")

# code to implement selective ignore of NL's inside ()'s
NL = pp.Suppress("\n")
base_ignore = arith_expr.ignoreExprs[:]
ignore_stack = base_ignore[:]
def lpar_pa():
    ignore_stack.append(NL)
    arith_expr.ignore(NL)
    #~ print('post-push', arith_expr.ignoreExprs)
def rpar_pa():
    ignore_stack.pop(-1)
    arith_expr.ignore(None)
    for e in ignore_stack:
        arith_expr.ignore(e)
    #~ print('post-pop', arith_expr.ignoreExprs)
def reset_stack(*args):
    arith_expr.ignore(None)
    for e in base_ignore:
        arith_expr.ignore(e)
    #~ print('post-reset', arith_expr.ignoreExprs)
lpar.addParseAction(lpar_pa)
rpar.addParseAction(rpar_pa)
arith_expr.setFailAction(reset_stack)
arith_expr.addParseAction(reset_stack)

# now define the infix notation as usual
arith_expr <<= pp.infixNotation(operand | var,
    [
    ("-", 1, pp.opAssoc.RIGHT),
    (pp.oneOf("* /"), 2, pp.opAssoc.LEFT),
    (pp.oneOf("- +"), 2, pp.opAssoc.LEFT),
    ],
    lpar=lpar, rpar=rpar
    )

assignment = var + '=' + arith_expr

# Try it out!
assignment.runTests([
"""a = 1 + 3""",
"""a = (1 + 3)""",
"""a = 1 + 2 + ( 
3 +
1)""",
"""a = 1 + 2 + (( 
3 +
1))""",
"""a = 1 + 2 +   
3""",
], fullDump=False)

Prints:

a = 1 + 3
['a', '=', ['1', '+', '3']]
a = (1 + 3)
['a', '=', ['1', '+', '3']]
a = 1 + 2 + ( 
3 +
1)
['a', '=', ['1', '+', '2', '+', ['3', '+', '1']]]
a = 1 + 2 + (( 
3 +
1))
['a', '=', ['1', '+', '2', '+', ['3', '+', '1']]]
a = 1 + 2 +   
3
a = 1 + 2 +   
          ^
FAIL: Expected end of text, found '+'  (at char 10), (line:1, col:11)>Exit code: 0

因此,这并非不可能,但确实需要一些英勇的努力。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 PyParsing 解析带有重要换行符的语言(如 Python) 的相关文章

随机推荐