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