pyparsing中的上下文解析除全局参数外的操作

2024-10-02 22:33:40 发布

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

我希望能够解析两个(或任意数量)表达式,每个表达式都有自己的一组变量定义或其他上下文。在

似乎没有一种明显的方法可以将上下文与pyparsing.ParseExpression.parseString()的特定调用关联起来。最自然的方法似乎是使用某个类的instancemethod作为解析操作。这种方法的问题是必须为每个解析上下文(例如,在类的__init__)重新定义语法,这似乎非常低效。在

在规则上使用pyparsing.ParseExpression.copy()没有帮助;单个表达式可以正常克隆,但是组成它们的子表达式没有以任何明显的方式更新,因此任何嵌套表达式的解析操作都不会被调用。在

我能想到的获得这种效果的唯一另一种方法是定义一个返回无上下文抽象解析树的语法,然后在第二步中处理它。即使对于简单的语法来说,这似乎也很尴尬:在使用未识别的名称时只引发一个异常是很好的,而且它仍然无法解析像C这样的语言,因为这些语言实际上需要上下文来知道哪条规则匹配。在

有没有另一种方法可以将上下文(当然不使用全局变量)注入到pyparsing表达式的解析操作中?在


Tags: 方法语言数量定义init表达式规则方式
3条回答

像您所说的那样,让解析操作成为instancemethods,但不重新实例化类怎么样?相反,当您想解析另一个翻译单元时,请在同一个解析器对象中重置上下文。在

像这样:

from pyparsing import Keyword, Word, OneOrMore, alphas, nums

class Parser:
    def __init__(self):
        ident = Word(alphas)
        identval = Word(alphas).setParseAction(self.identval_act)
        numlit = Word(nums).setParseAction(self.numlit_act)
        expr = identval | numlit
        letstmt = (Keyword("let") + ident + expr).setParseAction(self.letstmt_act)
        printstmt = (Keyword("print") + expr).setParseAction(self.printstmt_act)
        program = OneOrMore(letstmt | printstmt)

        self.symtab = {}
        self.grammar = program

    def identval_act(self, (ident,)):
        return self.symtab[ident]
    def numlit_act(self, (numlit,)):
        return int(numlit)
    def letstmt_act(self, (_, ident, val)):
        self.symtab[ident] = val
    def printstmt_act(self, (_, expr)):
        print expr

    def reset(self):
        self.symtab = {}

    def parse(self, s):
        self.grammar.parseString(s)

P = Parser()
P.parse("""let foo 10
print foo
let bar foo
print bar
""")

print P.symtab
P.parse("print foo") # context is kept.

P.reset()
P.parse("print foo") # but here it is reset and this fails

在本例中,“symtab”是您的上下文。在

如果您尝试在不同的线程中进行并行解析,这将严重失败,但我不明白如何在共享解析操作中正常工作。在

有点晚了,但是google pyparsing reentrancy显示了这个主题,所以我的答案是。
我已经解决了解析器实例重用/可重入的问题,方法是将上下文附加到正在解析的字符串上。 子类str,将上下文放入新str类的属性中, 将它的一个实例传递给pyparsing,并在操作中获取上下文。在

Python 2.7:

from pyparsing import LineStart, LineEnd, Word, alphas, Optional, Regex, Keyword, OneOrMore

# subclass str; note that unicode is not handled
class SpecStr(str):
    context = None  # will be set in spec_string() below
    # override as pyparsing calls str.expandtabs by default
    def expandtabs(self, tabs=8):
        ret = type(self)(super(SpecStr, self).expandtabs(tabs))
        ret.context = self.context
        return ret    

# set context here rather than in the constructor
# to avoid messing with str.__new__ and super()
def spec_string(s, context):
    ret = SpecStr(s)
    ret.context = context
    return ret    

class Actor(object):
    def __init__(self):
        self.namespace = {}

    def pair_parsed(self, instring, loc, tok):
        self.namespace[tok.key] = tok.value

    def include_parsed(self, instring, loc, tok):
        # doc = open(tok.filename.strip()).read()  # would use this line in real life
        doc = included_doc  # included_doc is defined below
        parse(doc, self)  # <<<<< recursion

def make_parser(actor_type):
    def make_action(fun):  # expects fun to be an unbound method of Actor
        def action(instring, loc, tok):
            if isinstance(instring, SpecStr):
                return fun(instring.context, instring, loc, tok)
            return None  # None as a result of parse actions means 
            # the tokens has not been changed

        return action

    # Sample grammar: a sequence of lines, 
    # each line is either 'key=value' pair or '#include filename'
    Ident = Word(alphas)
    RestOfLine = Regex('.*')
    Pair = (Ident('key') + '=' +
            RestOfLine('value')).setParseAction(make_action(actor_type.pair_parsed))
    Include = (Keyword('#include') +
               RestOfLine('filename')).setParseAction(make_action(actor_type.include_parsed))
    Line = (LineStart() + Optional(Pair | Include) + LineEnd())
    Document = OneOrMore(Line)
    return Document

Parser = make_parser(Actor)  

def parse(instring, actor=None):
    if actor is not None:
        instring = spec_string(instring, actor)
    return Parser.parseString(instring)


included_doc = 'parrot=dead'
main_doc = """\
#include included_doc
ham = None
spam = ham"""

# parsing without context is ok
print 'parsed data:', parse(main_doc)

actor = Actor()
parse(main_doc, actor)
print 'resulting namespace:', actor.namespace

收益率

^{pr2}$

这种方法使Parser本身完全可重用和可重入。 只要不接触pyparsing的静态字段,pyparsing内部通常也是可重入的。 唯一的缺点是pyparsing在每次调用parseString时重置其packrat缓存,但这可以通过 重写SpecStr.__hash__(使其像object那样散列,而不是{})和一些monkeypatching。在我的数据集中,这根本不是问题,因为性能损失可以忽略不计,这甚至有利于内存的使用。在

我不知道这是否一定能回答您的问题,但这是根据上下文定制解析器的一种方法:

from pyparsing import Word, alphas, alphanums, nums, oneOf, ParseFatalException

var = Word(alphas+'_', alphanums+'_').setName("identifier")
integer = Word(nums).setName("integer").setParseAction(lambda t:int(t[0]))
operand = integer | var

operator = oneOf("+ - * /")
ops = {'+' : lambda a,b:a+b,
       '-' : lambda a,b:a-b,
       '*' : lambda a,b:a*b,
       '/' : lambda a,b:a/b if b else "inf",
        }

binop = operand + operator + operand

# add parse action that evaluates the binary operator by passing 
# the two operands to the appropriate binary function defined in ops
binop.setParseAction(lambda t: ops[t[1]](t[0],t[2]))

# closure to return a context-specific parse action
def make_var_parseAction(context):
    def pa(s,l,t):
        varname = t[0]
        try:
            return context[varname]
        except KeyError:
            raise ParseFatalException("invalid variable '%s'" % varname)
    return pa

def eval_binop(e, **kwargs):
    var.setParseAction(make_var_parseAction(kwargs))
    try:
        print binop.parseString(e)[0]
    except Exception as pe:
        print pe

eval_binop("m*x", m=100, x=12, b=5)
eval_binop("z*x", m=100, x=12, b=5)

印刷品

^{pr2}$

相关问题 更多 >