Commits

Anonymous committed 0f1f0d4

merged with original PyMeta. this adds better error handling and improves efficiency

  • Participants
  • Parent commits a5dfa8b

Comments (0)

Files changed (5)

pymeta/builder.py

         @param expr: A list of lines of Python code.
         """
         
-        subwriter = PythonWriter(expr)
+        subwriter = self.__class__(expr)
         flines  = subwriter._generate(retrn=True)
         fname = self._gensym(name)
         self._writeFunction(fname, (),  flines)
         if ruleName == 'super':
             return self._expr('apply', 'self.superApply("%s", %s)' % (codeName,
                                                               ', '.join(args)))
-        return self._expr('apply', 'self.apply("%s", %s)' % (ruleName,
+        return self._expr('apply', 'self._apply(self.rule_%s, "%s", [%s])' % (ruleName,
+                                                                              ruleName,
                                                              ', '.join(args)))
 
     def generate_Exactly(self, literal):
     def generate_Rule(self, name, expr):
         rulelines = ["_locals = {'self': self}",
                      "self.locals[%r] = _locals" % (name,)]
-        subwriter = PythonWriter(expr)
+        subwriter = self.__class__(expr)
         flines  = subwriter._generate(retrn=True)
         rulelines.extend(flines)
         self._writeFunction("rule_" + name, ("self",), rulelines)

pymeta/runtime.py

 # -*- test-case-name: pymeta.test.test_runtime -*-
-
 """
 Code needed to run a grammar after it has been compiled.
 """
+import operator
 class ParseError(Exception):
     """
     ?Redo from start
     """
+
+    @property
+    def position(self):
+        return self.args[0]
+
+    @property
+    def error(self):
+        return self.args[1]
+
     def __init__(self, *a):
         Exception.__init__(self, *a)
-        self.position = a[0]
-        self.error = a[1]
         if len(a) > 2:
             self.message = a[2]
 
         if other.__class__ == self.__class__:
             return (self.position, self.error) == (other.position, other.error)
 
-    def __repr__(self):
-        return 'Parse error%s at char %d: %s' % (self.error and ' (%s)' % self.error or '', self.position, self.message)
 
-    def __str__(self):
-        return repr(self)
+    def formatReason(self):
+        if len(self.error) == 1:
+            if self.error[0][2] == None:
+                return 'expected a ' + self.error[0][1]
+            else:
+                return 'expected the %s %s' % (self.error[0][1], self.error[0][2])
+        else:
+            bits = []
+            for s in self.error:
+                if s[2] is None:
+                    desc = "a " + s[1]
+                else:
+                    desc = repr(s[2])
+                    if s[1] is not None:
+                        desc = "%s %s" % (s[1], desc)
+                bits.append(desc)
+
+            return "expected one of %s, or %s" % (', '.join(bits[:-1]), bits[-1])
+
+    def formatError(self, input):
+        """
+        Return a pretty string containing error info about string parsing failure.
+        """
+        lines = input.split('\n')
+        counter = 0
+        lineNo = 1
+        columnNo = 0
+        for line in lines:
+            newCounter = counter + len(line)
+            if newCounter > self.position:
+                columnNo = self.position - counter
+                break
+            else:
+                counter += len(line) + 1
+                lineNo += 1
+        reason = self.formatReason()
+        return ('\n' + line + '\n' + (' ' * columnNo + '^') +
+                "\nParse error at line %s, column %s: %s\n" % (lineNo,
+                                                               columnNo,
+                                                               reason))
 
 class EOFError(ParseError):
     def __init__(self, position):
         ParseError.__init__(self, position, eof())
 
 
-def expected(val):
+def expected(typ, val=None):
     """
     Return an indication of expected input and the position where it was
     expected and not encountered.
     """
 
-    return [("expected", val)]
+    return [("expected", typ, val)]
 
-def expectedOneOf(vals):
-    """
-    Return an indication of multiple possible expected inputs.
-    """
-
-    return [("expected", x) for x in vals]
 
 def eof():
     """
     """
     return [("message", "end of input")]
 
-
 def joinErrors(errors):
     """
     Return the error from the branch that matched the most of the input.
     """
-    errors.sort(reverse=True, key=lambda x: x.position)
-    results = []
-    pos = errors[0].position
+    errors.sort(reverse=True, key=operator.itemgetter(0))
+    results = set()
+    pos = errors[0][0]
     for err in errors:
-        if pos == err.position:
-            e = err.error
+        if pos == err[0]:
+            e = err[1]
             if e is not None:
                 for item in e:
-                    if item not in results:
-                        results.append(item)
+                        results.add(item)
         else:
             break
 
-    return ParseError(pos, results)
+    return [pos, list(results)]
 
 
 class character(str):
     def head(self):
         if self.position >= len(self.data):
             raise EOFError(self.position)
-        return self.data[self.position], ParseError(self.position, None)
+        return self.data[self.position], [self.position, None]
 
     def nullError(self):
-        return ParseError(self.position, None)
+        return [self.position, None]
 
     def tail(self):
         if self.tl is None:
         return self.parent
 
 
+
+    def nullError(self):
+        return self.parent.nullError()
+
+
     def getMemo(self, name):
         """
         Returns the memo record for the named rule.
         self.currentError = self.input.nullError()
 
     def considerError(self, error):
-        if error is not None:
-            self.currentError = joinErrors([error, self.currentError])
+        if error and  error[0] > self.currentError[0]:
+            self.currentError = error
 
 
     def superApply(self, ruleName, *args):
         """
         r = getattr(self, "rule_"+ruleName, None)
         if r is not None:
-            return self._apply(r, ruleName, args)
+            val, err = self._apply(r, ruleName, args)
+            return val, ParseError(*err)
 
         else:
             raise NameError("No rule named '%s'" %(ruleName,))
             return val, p
         else:
             self.input = i
-            raise ParseError(p.position, expected(wanted))
+            raise ParseError(p[0], expected(None, wanted))
 
     rule_exactly = exactly
 
             except ParseError, e:
                 errors.append(e)
                 self.input = m
-        raise joinErrors(errors)
+        raise ParseError(*joinErrors(errors))
 
 
     def _not(self, fn):
             self.input = m
             return True, self.input.nullError()
         else:
-            raise self.input.nullError()
+            raise ParseError(*self.input.nullError())
 
     def eatWhitespace(self):
         """
         """
         val, e = expr()
         if not val:
-            raise e
+            raise ParseError(*e)
         else:
             return True, e
 
             self.input = InputStream.fromIterable(v)
         except TypeError:
             e = self.input.nullError()
-            e.error = expected("an iterable")
-            raise e
+            e[1] = expected("an iterable")
+            raise ParseError(*e)
         expr()
         self.end()
         self.input = oldInput
             for c in tok:
                 v, e = self.exactly(c)
             return tok, e
-        except ParseError:
+        except ParseError, e:
             self.input = m
-            raise
+            raise ParseError(e[0], expected("string", tok))
     rule_match_string = match_string
 
     def token(self, tok):
             for c in tok:
                 v, e = self.exactly(c)
             return tok, e
-        except ParseError:
+        except ParseError, e:
             self.input = m
-            raise
+            
+            raise ParseError(e[0], expected("token", tok))
 
     rule_token = token
 
         if x.isalpha():
             return x, e
         else:
-            e.error = expected("letter")
-            raise e
+            e[1] = expected("letter")
+            raise ParseError(*e)
 
     rule_letter = letter
 
         if x.isalnum() or x == '_':
             return x, e
         else:
-            e.error = expected("letter or digit")
-            raise e
+            e[1] = expected("letter or digit")
+            raise ParseError(*e)
 
     rule_letterOrDigit = letterOrDigit
 
         if x.isdigit():
             return x, e
         else:
-            e.error = expected("digit")
-            raise e
+            e[1] = expected("digit")
+            raise ParseError(*e)
 
     rule_digit = digit
 
         delimiters = { "(": ")", "[": "]", "{": "}"}
         stack = []
         expr = []
+        lastc = None
+        endchar = None
         while True:
             try:
                 c, e = self.rule_anything()
                     while True:
                         strc, stre = self.rule_anything()
                         expr.append(strc)
-                        if strc == c:
+                        slashcount = 0
+                        while strc == '\\':
+                            strc, stre = self.rule_anything()
+                            expr.append(strc)
+                            slashcount += 1
+                        if strc == c and slashcount % 2 == 0:
                             break
+            
         if len(stack) > 0:
             raise ParseError(self.input.position, expected("Python expression"))
         return (''.join(expr).strip(), endchar), e

test/test_builder.py

                             self.considerError(lastError)
                             _G_python_2, lastError = eval('x', self.globals, _locals), None
                             self.considerError(lastError)
-                            _G_apply_3, lastError = self.apply("foo", _G_python_1, _G_python_2)
+                            _G_apply_3, lastError = self._apply(self.rule_foo, "foo", [_G_python_1, _G_python_2])
                             self.considerError(lastError)
                             _G_apply_3
                             """))

test/test_pymeta.py

 from textwrap import dedent
 from twisted.trial import unittest
-from pymeta.runtime import ParseError, OMetaBase, EOFError
+from pymeta.runtime import ParseError, OMetaBase, EOFError, expected
 from pymeta.grammar import OMetaGrammar
 from pymeta.builder import TreeBuilder, moduleFromGrammar
 
         rule.
         @param: Rule name.
         """
-        def doIt(str):
+        def doIt(s):
             """
-            @param str: The string to be parsed by the wrapped grammar.
+            @param s: The string to be parsed by the wrapped grammar.
             """
-            obj = self.klass(str)
+            obj = self.klass(s)
             ret, err = obj.apply(name)
             try:
-                extra, err = obj.input.head()
+                extra, _ = obj.input.head()
             except EOFError:
                 try:
                     return ''.join(ret)
                 except TypeError:
                     return ret
             else:
-                raise ParseError(err.args[0], err.args[1], "trailing garbage in input: %r" % (extra,))
+                raise err
         return doIt
 
 
                           aLetter = 'a'
                           """)
         self.assertEqual(g.digit("1"), "1")
-        self.assertRaises(ParseError, g.digit, "4")        
+        self.assertRaises(ParseError, g.digit, "4")
 
 
     def test_escapedLiterals(self):
         """)
         self.assertEqual(g.broken('ab'), 'ab')
 
+
+
 class PyExtractorTest(unittest.TestCase):
     """
     Tests for finding Python expressions in OMeta grammars.

test/test_runtime.py

 
 
 from twisted.trial import unittest
-from pymeta.runtime import OMetaBase, ParseError, expected, expectedOneOf, eof
+from pymeta.runtime import OMetaBase, ParseError, expected, eof
 
 class RuntimeTests(unittest.TestCase):
     """
 
         for i, c in enumerate(data):
             v, e = o.rule_anything()
-            self.assertEqual((c, i), (v, e.position))
+            self.assertEqual((c, i), (v, e[0]))
 
 
     def test_exactly(self):
         o = OMetaBase(data)
         v, e = o.rule_exactly("f")
         self.assertEqual(v, "f")
-        self.assertEqual(e.position, 0)
+        self.assertEqual(e[0], 0)
 
     def test_exactlyFail(self):
         """
         data = "foo"
         o = OMetaBase(data)
         e = self.assertRaises(ParseError, o.rule_exactly, "g")
-        self.assertEquals(e.error, expected("g"))
-        self.assertEquals(e.position, 0)
+        self.assertEquals(e[1], expected(None, "g"))
+        self.assertEquals(e[0], 0)
 
 
 
         o = OMetaBase(data)
         v, e = o.rule_token("foo")
         self.assertEqual(v, "foo")
-        self.assertEqual(e.position, 4)
+        self.assertEqual(e[0], 4)
         v, e = o.rule_token("bar")
         self.assertEqual(v, "bar")
-        self.assertEqual(e.position, 8)
+        self.assertEqual(e[0], 8)
 
 
     def test_tokenFailed(self):
         data = "foozle"
         o = OMetaBase(data)
         e = self.assertRaises(ParseError, o.rule_token, "fog")
-        self.assertEqual(e.position, 2)
-        self.assertEqual(e.error, expected("g"))
+        self.assertEqual(e[0], 2)
+        self.assertEqual(e[1], expected("token", "fog"))
 
 
     def test_many(self):
 
         data = "ooops"
         o  = OMetaBase(data)
-        self.assertEqual(o.many(lambda: o.rule_exactly('o')), (['o'] * 3, ParseError(3, expected('o'))))
+        self.assertEqual(o.many(lambda: o.rule_exactly('o')), (['o'] * 3, ParseError(3, expected(None, 'o'))))
 
 
     def test_or(self):
         v, e = o._or(matchers)
         self.assertEqual(called, [True, True, False])
         self.assertEqual(v, 'a')
-        self.assertEqual(e.position, 0)
+        self.assertEqual(e[0], 0)
 
 
     def test_orSimpleFailure(self):
                               [lambda: o.token("fog"),
                                lambda: o.token("foozik"),
                                lambda: o.token("woozle")])
-        self.assertEqual(e.position, 4)
-        self.assertEqual(e.error, expected("i"))
+        self.assertEqual(e[0], 4)
+        self.assertEqual(e[1], expected("token",  "foozik"))
 
 
     def test_orFalseSuccess(self):
         v, e = o._or( [lambda: o.token("fog"),
                                lambda: o.token("foozik"),
                                lambda: o.token("f")])
-        self.assertEqual(e.position, 4)
-        self.assertEqual(e.error, expected("i"))
+        self.assertEqual(e[0], 4)
+        self.assertEqual(e[1], expected("token", "foozik"))
 
     def test_orErrorTie(self):
         """
         v, e = o._or( [lambda: o.token("fog"),
                                lambda: o.token("foz"),
                                lambda: o.token("f")])
-        self.assertEqual(e.position, 2)
-        self.assertEqual(e.error, expectedOneOf(["g", "z"]))
+        self.assertEqual(e[0], 2)
+        self.assertEqual(e[1], [expected("token", "fog")[0], expected("token", "foz")[0]])
 
 
     def test_notError(self):
         data = "xy"
         o = OMetaBase(data)
         e = self.assertRaises(ParseError, o._not, lambda: o.exactly("x"))
-        self.assertEqual(e.position, 1)
-        self.assertEqual(e.error, None)
+        self.assertEqual(e[0], 1)
+        self.assertEqual(e[1], None)
 
 
     def test_spaces(self):
         o = OMetaBase(data)
         v, e = o.rule_spaces()
 
-        self.assertEqual(e.position, 2)
+        self.assertEqual(e[0], 2)
 
     def test_predSuccess(self):
         """
         e = self.assertRaises(ParseError, o.rule_end)
         self.assertEqual(e, ParseError(1, None))
         o.many(o.rule_anything)
-        self.assertEqual(o.rule_end(), (True, ParseError(3, None)))
+        self.assertEqual(o.rule_end(), (True, [3, None]))
 
 
     def test_letter(self):
 
         o = OMetaBase("a1")
         v, e = o.rule_letter()
-        self.assertEqual((v, e), ("a", ParseError(0, None)))
+        self.assertEqual((v, e), ("a", [0, None]))
         e = self.assertRaises(ParseError, o.rule_letter)
         self.assertEqual(e, ParseError(1, expected("letter")))
 
         """
         o = OMetaBase("a1@")
         v, e = o.rule_letterOrDigit()
-        self.assertEqual((v, e), ("a", ParseError(0, None)))
+        self.assertEqual((v, e), ("a", [0, None]))
         v, e = o.rule_letterOrDigit()
-        self.assertEqual((v, e), ("1", ParseError(1, None)))
+        self.assertEqual((v, e), ("1", [1, None]))
         e = self.assertRaises(ParseError, o.rule_letterOrDigit)
         self.assertEqual(e, ParseError(2, expected("letter or digit")))
 
         """
         o = OMetaBase("1a")
         v, e = o.rule_digit()
-        self.assertEqual((v, e), ("1", ParseError(0, None)))
+        self.assertEqual((v, e), ("1", [0, None]))
         e = self.assertRaises(ParseError, o.rule_digit)
         self.assertEqual(e, ParseError(1, expected("digit")))
 
         """
         o = OMetaBase([["a"]])
         v, e = o.listpattern(lambda: o.exactly("a"))
-        self.assertEqual((v, e), (["a"], ParseError(0, None)))
+        self.assertEqual((v, e), (["a"], [0, None]))