Repository: RedHatInsights/insights-core

Issue:
parsr exceptions are verbose and hard to understand
Exceptions raised by parsr expressions are really verbose and hard to understand.


Discussion:

Resolving patch:
diff --git a/insights/parsr/__init__.py b/insights/parsr/__init__.py
--- a/insights/parsr/__init__.py
+++ b/insights/parsr/__init__.py
@@ -120,16 +120,16 @@ def inner(self, pos, data, ctx):
         if self._debug:
             line = ctx.line(pos) + 1
             col = ctx.col(pos) + 1
-            log.debug("Trying {} at line {} col {}".format(self, line, col))
+            log.debug("Trying {0} at line {1} col {2}".format(self, line, col))
         try:
             res = func(self, pos, data, ctx)
             if self._debug:
-                log.debug("Result: {}".format(res[1]))
+                log.debug("Result: {0}".format(res[1]))
             return res
         except:
             if self._debug:
                 ps = "-> ".join([str(p) for p in ctx.parser_stack])
-                log.debug("Failed: {}".format(ps))
+                log.debug("Failed: {0}".format(ps))
             raise
         finally:
             ctx.parser_stack.pop()
@@ -323,11 +323,13 @@ def __call__(self, data, src=None, Ctx=Context):
 
         lineno = ctx.line(ctx.pos) + 1
         colno = ctx.col(ctx.pos) + 1
-        msg = "At line {} column {}:"
+        msg = "At line {0} column {1}:"
         print(msg.format(lineno, colno, ctx.lines), file=err)
         for parsers, msg in ctx.errors:
-            ps = "-> ".join([str(p) for p in parsers])
-            print("{}: Got {!r}. {}".format(ps, data[ctx.pos], msg), file=err)
+            names = " -> ".join([p.name for p in parsers if p.name])
+            v = data[ctx.pos] or "EOF"
+            print(names, file=err)
+            print("    {0} Got {1!r}.".format(msg, v), file=err)
         err.seek(0)
         raise Exception(err.read())
 
@@ -363,12 +365,14 @@ def __init__(self, char):
     def process(self, pos, data, ctx):
         if data[pos] == self.char:
             return (pos + 1, self.char)
-        msg = "Expected {!r}.".format(self.char)
+        msg = "Expected {0}.".format(self.char)
         ctx.set(pos, msg)
         raise Exception(msg)
 
     def __repr__(self):
-        return "Char({!r})".format(self.char)
+        if self.name is None:
+            return "Char({0})".format(self.char)
+        return self.name
 
 
 class InSet(Parser):
@@ -395,13 +399,13 @@ def process(self, pos, data, ctx):
         c = data[pos]
         if c in self.values:
             return (pos + 1, c)
-        msg = "Expected {}.".format(self)
+        msg = "Expected {0}.".format(self)
         ctx.set(pos, msg)
         raise Exception(msg)
 
     def __repr__(self):
-        if not self.name:
-            return "InSet({!r})".format(sorted(self.values))
+        if self.name is None:
+            return "InSet({0!r})".format(sorted(self.values))
         return super(InSet, self).__repr__()
 
 
@@ -441,7 +445,7 @@ def process(self, pos, data, ctx):
                 break
             p = data[pos]
         if len(results) < self.min_length:
-            msg = "Expected {} of {}.".format(self.min_length, sorted(self.chars))
+            msg = "Expected {0} of {1}.".format(self.min_length, sorted(self.chars))
             ctx.set(old, msg)
             raise Exception(msg)
         return pos, "".join(results)
@@ -488,6 +492,7 @@ def __init__(self, chars, value=_NULL, ignore_case=False):
         self.chars = chars if not ignore_case else chars.lower()
         self.value = value
         self.ignore_case = ignore_case
+        self.name = "Literal{0!r}".format(self.chars)
 
     def process(self, pos, data, ctx):
         old = pos
@@ -496,7 +501,7 @@ def process(self, pos, data, ctx):
                 if data[pos] == c:
                     pos += 1
                 else:
-                    msg = "Expected {!r}.".format(self.chars)
+                    msg = "Expected {0!r}.".format(self.chars)
                     ctx.set(old, msg)
                     raise Exception(msg)
             return pos, (self.chars if self.value is self._NULL else self.value)
@@ -507,7 +512,7 @@ def process(self, pos, data, ctx):
                     result.append(data[pos])
                     pos += 1
                 else:
-                    msg = "Expected case insensitive {!r}.".format(self.chars)
+                    msg = "Expected case insensitive {0!r}.".format(self.chars)
                     ctx.set(old, msg)
                     raise Exception(msg)
             return pos, ("".join(result) if self.value is self._NULL else self.value)
@@ -689,7 +694,7 @@ def process(self, pos, data, ctx):
                 break
         if len(results) < self.lower:
             child = self.children[0]
-            msg = "Expected at least {} of {}.".format(self.lower, child)
+            msg = "Expected at least {0} of {1}.".format(self.lower, child)
             ctx.set(orig, msg)
             raise Exception()
 
@@ -697,7 +702,7 @@ def process(self, pos, data, ctx):
 
     def __repr__(self):
         if not self.name:
-            return "Many({}, lower={})".format(self.children[0], self.lower)
+            return "Many({0}, lower={1})".format(self.children[0], self.lower)
         return super(Many, self).__repr__()
 
 
@@ -801,7 +806,7 @@ def process(self, pos, data, ctx):
         except Exception:
             return new, res
         else:
-            msg = "{} can't follow {}".format(right, left)
+            msg = "{0} can't follow {1}".format(right, left)
             ctx.set(new, msg)
             raise Exception()
 
@@ -916,7 +921,7 @@ def process(self, pos, data, ctx):
 
     def __repr__(self):
         if not self.name:
-            return "Map({}({}))".format(self.func.__name__, self.children[0])
+            return "Map({0}({1}))".format(self.func.__name__, self.children[0])
         return super(Map, self).__repr__()
 
 
@@ -1155,7 +1160,7 @@ def process(self, pos, data, ctx):
             e = expect.lower()
 
         if r != e:
-            msg = "Expected {!r}. Got {!r}.".format(expect, res)
+            msg = "Expected {0!r}. Got {1!r}.".format(expect, res)
             ctx.set(pos, msg)
             raise Exception(msg)
         return pos, res
@@ -1167,13 +1172,10 @@ def _make_number(sign, int_part, frac_part):
 
 
 def skip_none(x):
-    """
-    Filters ``None`` values from a list. Often used with map.
-    """
     return [i for i in x if i is not None]
 
 
-EOF = EOF()
+EOF = EOF() % "EOF"
 EOL = InSet("\n\r") % "EOL"
 LineEnd = Wrapper(EOL | EOF) % "LineEnd"
 EQ = Char("=")
@@ -1189,15 +1191,15 @@ def skip_none(x):
 Colon = Char(":")
 SemiColon = Char(";")
 Comma = Char(",")
-AnyChar = AnyChar()
-NonZeroDigit = InSet(set(string.digits) - set("0"))
-Digit = InSet(string.digits) % "Digit"
-Digits = String(string.digits) % "Digits"
-Letter = InSet(string.ascii_letters)
-Letters = String(string.ascii_letters)
-WSChar = InSet(set(string.whitespace) - set("\n\r")) % "Whitespace w/o EOL"
-WS = Many(InSet(string.whitespace) % "WS") % "Whitespace"
-Number = (Lift(_make_number) * Opt(Char("-"), "") * Digits * Opt(Char(".") + Digits)) % "Number"
+AnyChar = AnyChar() % "any character"
+NonZeroDigit = InSet(set(string.digits) - set("0")) % "non zero digit"
+Digit = InSet(string.digits) % "digit"
+Digits = String(string.digits) % "digits"
+Letter = InSet(string.ascii_letters) % "ASCII letter"
+Letters = String(string.ascii_letters) % "ASCII letters"
+WSChar = InSet(set(string.whitespace) - set("\n\r")) % "whitespace w/o EOL"
+WS = Many(InSet(string.whitespace) % "any whitespace")
+Number = (Lift(_make_number) * Opt(Char("-"), "") * Digits * Opt(Char(".") + Digits)) % "number"
 SingleQuotedString = Char("'") >> String(set(string.printable) - set("'"), "'") << Char("'")
 DoubleQuotedString = Char('"') >> String(set(string.printable) - set('"'), '"') << Char('"')
-QuotedString = Wrapper(DoubleQuotedString | SingleQuotedString) % "Quoted String"
+QuotedString = Wrapper(DoubleQuotedString | SingleQuotedString) % "quoted string"
diff --git a/insights/parsr/examples/arith.py b/insights/parsr/examples/arith.py
--- a/insights/parsr/examples/arith.py
+++ b/insights/parsr/examples/arith.py
@@ -7,7 +7,7 @@
 
 
 def evaluate(e):
-    return Top(e)[0]
+    return Top(e)
 
 
 def op(args):
@@ -40,11 +40,11 @@ def op(args):
 # at the end.
 
 # We have to declare expr before its definition since it's used recursively.
-expr = Forward() % "expr forward"
+expr = Forward()
 
 
 # A factor is a simple number or a subexpression between parentheses
-factor = WS >> (Number % "Number" | (LeftParen >> expr << RightParen)) << WS
+factor = (WS >> (Number | (LeftParen >> expr << RightParen)) << WS) % "factor"
 
 # A term handles strings of multiplication and division. As written, it would
 # convert "1 + 2 - 3 + 4" into [1, [['+', 2], ['-', 3], ['+', 4]]]. The first
@@ -59,4 +59,4 @@ def op(args):
 expr <= (term + Many(LowOps + term)).map(op) % "expr"
 
 # Top returns [result, None] on success and raises an Exception on failure.
-Top = (expr + EOF) % "Top"
+Top = expr << EOF


Test patch: