Немного поздно, но поиск в 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
урожайность
['#include', 'included_doc', '\n', 'ham', '=', 'None', '\n', 'spam', '=', 'ham']
{'ham': 'None', 'parrot': 'dead', 'spam': 'ham'}
Этот подход делает сам Parser
идеально пригодным для повторного использования и повторного ввода.Внутренние элементы pyparsing
также являются реентерабельными, если вы не касаетесь статических полей ParserElement
.Единственный недостаток заключается в том, что pyparsing
сбрасывает свой кэш-пакет при каждом вызове на parseString
, но это может быть устранено путем переопределения SpecStr.__hash__
(чтобы сделать его хешируемым, как object
, а не str
) и некоторой патчей для обезьян.В моем наборе данных это не проблема, так как снижение производительности незначительно, и это даже способствует использованию памяти.