Mercurial > lbo > hg > pcombinators
changeset 66:b9ed614fc42f draft
Add Peek and Lazy parsers and fix bugs in Repeat/Sequence
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 25 May 2019 14:46:01 +0200 |
parents | c4d5d0fe5a4c |
children | 2253177294ab |
files | pcombinators/arith_test.py pcombinators/combinators.py |
diffstat | 2 files changed, 78 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- a/pcombinators/arith_test.py Sat May 25 14:45:49 2019 +0200 +++ b/pcombinators/arith_test.py Sat May 25 14:46:01 2019 +0200 @@ -10,6 +10,10 @@ from pcombinators.combinators import * from pcombinators.primitives import * +def Operator(set): + """An operator or parenthesis.""" + return OneOf(set) + def Parens(): """Parentheses contain a term.""" return (Operator('(') + Term() + Operator(')')) >> (lambda l: l[1]) @@ -22,11 +26,29 @@ """An atom is a variable or a float or a parentheses term.""" return (Variable() | Parens() | Float()) -atom = Atom() +def Product(): + return OptimisticSequence(Power(), Operator('*/') + Lazy(Product)) >> operator_result_to_tuple + +class Power(Parser): + ops = Operator('^') + + def __init__(self): + self.p = OptimisticSequence(Lazy(Atom), self.ops + self) >> operator_result_to_tuple + + def parse(self, st): + return self.p.parse(st) -def Operator(set): - """An operator or parenthesis.""" - return OneOf(set) +class Term(Parser): + ops = Operator('+-') + + def __init__(self): + self.p = OptimisticSequence(Product(), self.ops + self) >> operator_result_to_tuple + + def parse(self, st): + # Try to parse a product, then a sum operator, then another term. + # OptimisticSequence will just return a product if there is no sum operator. + return self.p.parse(st) + def operator_result_to_tuple(l): if len(l) == 1: @@ -37,33 +59,6 @@ # Parse failed if not either 1 or 3. raise Exception("Parse failed: Missing operand") -class Power(Parser): - ops = Operator('^') - p = OptimisticSequence(atom, Power.ops + power) >> operator_result_to_tuple - def parse(self, st): - return self.p.parse(st) - -power = Power() - -class Product(Parser): - ops = Operator('*/') - p = OptimisticSequence(power, Product.ops + product) >> operator_result_to_tuple - def parse(self, st): - # Try to parse an atom, a product operator, and another product. - return self.p.parse(st) - -product = Product() - -class Term(Parser): - ops = Operator('+-') - p = OptimisticSequence(product, Term.ops + term) >> operator_result_to_tuple - def parse(self, st): - # Try to parse a product, then a sum operator, then another term. - # OptimisticSequence will just return a product if there is no sum operator. - return self.p.parse(st) - -term = Term() - def pretty_print(tpl): # tpl is a (left, op, right) tuple or a scalar. if not isinstance(tpl, tuple):
--- a/pcombinators/combinators.py Sat May 25 14:45:49 2019 +0200 +++ b/pcombinators/combinators.py Sat May 25 14:46:01 2019 +0200 @@ -55,6 +55,9 @@ parsed by the supplied next parser.""" return Last(AtomicSequence(self, next)) + def then_skip(self, next): + return Last(AtomicSequence(self, Skip(next))) + # Combinators class _Transform(Parser): @@ -98,6 +101,8 @@ def parse(self, st): results = [] hold = st.hold() if self._atomic else None + if st.finished(): + return None, st for p in self._parsers: result, st2 = p.parse(st) if result is None: @@ -105,7 +110,7 @@ st.reset(hold) return None, st break - if result is not SKIP_MARKER: + if result is not SKIP_MARKER and result is not PEEK_SUCCESS_MARKER: results.append(result) st = st2 if self._atomic: @@ -150,7 +155,8 @@ # come back here, i.e. if this is a strict repeat. hold = st.hold() if self._strict else None i = 0 - + if st.finished(): + return None, st while i < self._times or self._times < 0: r, st2 = self._parser.parse(st) if r == None: @@ -161,11 +167,13 @@ if len(results) == 0: return SKIP_MARKER, st2 return results, st2 - if r is not SKIP_MARKER: + if r is not SKIP_MARKER and r is not PEEK_SUCCESS_MARKER: results.append(r) st = st2 i += 1 st.release(hold) + if len(results) == 0: + return None, st return results, st class StrictRepeat(_Repeat): @@ -258,4 +266,44 @@ else: r.append(e) return r - return p >> flatten \ No newline at end of file + return p >> flatten + +# Parse result of Peek. This is ignored by Sequence and Repeat combinators. +PEEK_SUCCESS_MARKER = [] + +class Peek(Parser): + """Look ahead and succeed only if the given parser would parse. + Returns [], but is ignored in Sequences (Atomic/Optimistic/+) and Repeats. + + Warning: Likely slow. + """ + def __init__(self, p): + self._parser = p + + def parse(self, st): + hold = st.hold() + r, st2 = self._parser.parse(st) + if r is None: + st.release(hold) + return None, st + st.reset(hold) + return PEEK_SUCCESS_MARKER, st2 + +class Lazy(Parser): + """A transparent wrapper for avoiding mutual recursion and definition order trouble, which + can occur if your syntax is infinitely recursive. + + Takes a function that returns a parser, but only calls it when a parser is needed. + The obtained parser is then cached. + """ + def __init__(self, f): + self._f = f + self._parser = None + + def parser(self): + if self._parser is None: + self._parser = self._f() + return self._parser + + def parse(self, st): + return self.parser().parse(st) \ No newline at end of file