我希望能够解析两个(或任意数量)的表达式,每个表达式都有自己的变量定义或其他上下文。
似乎没有一种明显的方法可以将上下文与pyparsing.ParseExpression.parseString()
的特定调用相关联。最自然的方式似乎是使用某个类的实例方法作为解析操作。这种方法的问题是必须为每个解析上下文重新定义语法(例如,在类的__init__
中),这看起来非常低效。
在规则上使用pyparsing.ParseExpression.copy()
无济于事;单个表达式克隆得很好,但它们组成的子表达式不会以任何明显的方式更新,因此任何嵌套表达式的解析操作都不会被调用。
我能想到的唯一另一种方法就是定义一个语法,该语法返回一个无上下文的抽象解析树,然后在第二步中处理它。即使对于简单的语法,这看起来也很尴尬:在使用无法识别的名称时提出异常会很好,并且它仍然不会解析像C这样的语言,这些语言实际上需要关于之前的内容的上下文来知道匹配的规则。 / p>
是否有另一种方法可以将上下文注入(当然不使用全局变量)到pyparsing表达式的解析操作中?
答案 0 :(得分:3)
我不知道这是否必然会回答您的问题,但这是将解析器自定义为上下文的一种方法:
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)
打印
1200
invalid variable 'z' (at char 0), (line:1, col:1)
答案 1 :(得分:3)
有点晚了,但谷歌搜索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
产量
['#include', 'included_doc', '\n', 'ham', '=', 'None', '\n', 'spam', '=', 'ham']
{'ham': 'None', 'parrot': 'dead', 'spam': 'ham'}
这种方法使Parser
本身完全可重用且可重入。
pyparsing
内部结构通常也是可重入的,只要您不触及ParserElement
的静态字段即可。
唯一的缺点是pyparsing
在每次调用parseString
时重置其packrat缓存,但这可以通过
覆盖SpecStr.__hash__
(使其像object
一样,而不是str
)和一些monkeypatching。在我的数据集中,这根本不是问题,因为性能损失可以忽略不计,这甚至有利于内存使用。
答案 2 :(得分:1)
如何让解析操作成为像你说的实例方法,但只是不重新实现类?相反,当您想要解析另一个翻译单元时,请重置同一个解析器对象中的上下文。
这样的事情:
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”是您的上下文。
如果你试图在不同的线程中进行并行解析,那么这会失败,但是我不知道如何通过共享解析操作以合理的方式工作。
答案 3 :(得分:1)
我遇到了这个确切的限制,并使用threading.local()将解析器上下文信息作为线程本地存储附加。在我的例子中,我保留了一堆被解析的术语,这些术语在解析动作函数中被推送和弹出,但显然你也可以用它来存储对类实例或类似物的引用。
看起来有点像这样:
import threading
__tls = threading.local()
def parse_term(t):
__tls.stack.append(convert_term(t))
def parse_concatenation(t):
rhs = __tls.stack.pop()
lhs = __tls.stack.pop()
__tls.stack.append(convert_concatenation(t, lhs, rhs)
# parse a string s using grammar EXPR, that has parse actions parse_term and
# parse_concatenation for the rules that parse expression terms and concatenations
def parse(s):
__tls.stack = []
parse_result = EXPR.parseString(s)
return __tls.stack.pop()
在我的情况下,所有线程本地存储内容,设置堆栈,解析操作和语法本身都被推送到公共API之外,因此从外部没有人可以看到正在发生的事情或弄乱它。 API中的某个地方只有一个解析方法,它接受一个字符串并返回一个解析后的转换后的查询表示,这是线程安全的,不必为每个解析调用重新创建语法。