用pyparsing解析包含类似Python的if块的模板

2024-04-19 18:11:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我尝试用pyparsing解析一种简单的模板语言,它看起来像这样:

if <expr>: <statements>
elif <expr>:
    if <expr>:
        <statements>
    else:
        <statements>
<statements>

其中语句要么是具有有效Python代码的if块,要么是其他任何文本。如果一个字符串以“If”关键字开头,这意味着它马上就是一个If块。实际上,文本行也可以包含{variable}类型的插值,但仅此而已。在

此语法的完整有效示例:

^{pr2}$

为了检测Python表达式,我将pyparsing.Token子类化,它似乎可以工作:

import ast
from pyparsing import *

class PythonExpression(Token):
    name = 'PythonExpression'

    def parseImpl(self, s, loc, doActions=True):
        max_loc = s.find('\n') if '\n' in s else len(s)
        best_loc = None
        for n in range(loc + 1, max_loc + 1):
            try:
                tree = ast.parse(s[loc:n])
            except:
                continue
            if isinstance(tree, ast.Module):
                if len(tree.body) is 1:
                    if isinstance(tree.body[0], ast.Expr):
                        best_loc = n
        if best_loc is not None:
            return best_loc, s[loc:best_loc]
        raise ParseException(s, loc, 'invalid Python expression')

expr = 'if foo[1 : "bar:baz"] == 1   :   passqwe'
print (Keyword('if') + PythonExpression()).parseString(expr).asList()

结果

['if', 'foo[1 : "bar:baz"] == 1   ']

但是,我对pyparsing.indentedBlock的用法有点迷茫,似乎无法使它解析整个语法。我的最后一次尝试是这样(注意,它只包含if语句实现;还有可选的elifelse块):

ParserElement.setDefaultWhitespaceChars(' \t')

colon = Literal(':').suppress()
if_clause = PythonExpression() + colon
if_statement = Group(Keyword('if') + if_clause)
non_white = Regex(r'\S+')
anything = Combine(non_white + restOfLine)
statement = Forward()
indent_stack = [1]
if_block = Group(if_statement + ((anything) | indentedBlock(statement, indent_stack)))
other = ~Keyword('if') + anything
statement << (if_block | other)
parser = OneOrMore(indentedBlock(statement, indent_stack, False))

data = """\
if foo[1:2]:
  bar
  baz
if foo[3]:
  bar : baz
  if foo[4]: bar
  baz
foo
"""
pprint.pprint(parser.parseString(data).asList())

它开始正确解析,但随后停止:

[[[[['if', 'foo[1:2]'], [['bar'], ['baz']]]]]]

我也尝试过显式地将+ lineEnd.suppress()添加到anything中,但似乎没有什么帮助。我肯定我在这里做了些蠢事,可能和新词有关,但我真的搞不清楚。在

顺便说一句,在上面的例子中,我如何检测anything中的插值模式(如果上面的例子可以工作的话),以便foo {bar} baz被解析为一个['foo',Var('bar'),'baz']?检测{var}很容易,但是对于纯文本来说,什么是正确的表达式呢?如果没有足够的贪婪来消耗所有的内容,并且不会扰乱if/elif/else逻辑(我尝试使用SkipTo,但这变得相当麻烦)?在

编辑:添加用于解析插值的单独语法,示例如下:

class Substitution(object):
    def __init__(self, s, l, t):
        self.name = t[0]
    def __repr__(self):
        return 'Substitution(%r)' % self.name

ParserElement.setDefaultWhitespaceChars(' \t')
lbrace = Literal('{').suppress()
rbrace = Literal('}').suppress()
name = Word(alphas, alphanums + '_')
substitution = Combine(lbrace + name + rbrace).setParseAction(Substitution)
text = SkipTo(substitution | lineEnd.suppress(), include=True).leaveWhitespace()
parser = OneOrMore(text | substitution)
parser.parseString('hello \n {world} {invalid.sub} \n foo {bar} baz ').asList()

输出

['hello ',
 [' ', Substitution('world')],
 ' {invalid.sub} ',
 [' foo ', Substitution('bar')],
 ' baz ']

如您所见,在本例中,解析器在将行分组到一起时并不十分正确(它不允许text跟在substitution后面),但它显示了这一点。这些Substitution对象随后将在运行时进行处理。在


Tags: nameselfiffoobarbazpyparsingloc