instaparse

Instaparse: there's an error but it is not reported


I am trying to build up a grammar with Instaparse. Quite often I find that this code fails the first assertion, emitting "Empty list":

(defn parse-it []
  (let [parser (insta/parser ebnf)
        res (insta/parses parser input)
        _ (assert (seq res) (str "Empty list"))
        choices (count res)
        _ (assert (= choices 1))]
    (first res)))

I always resolve the problem, but it involves trial and error. Is there any way that the error can be pinpointed?

An example of fixing the problem is removing trailing spaces from the file that becomes input in the above code.

Edit

Based on Stefan's answer I've changed the code:

(defn parse-it []
  (let [my-parser (insta/parser ebnf)
        xs (insta/parses my-parser input)
        num-choices (count xs)
        msg (cond
              (zero? num-choices) "insta/parses might be able to be corrected"
              (> num-choices 1) (str "insta/parses shows more than one way to parse: " num-choices)
              (= 1 num-choices) "insta/parses shows one choice, so all good")
        res (cond
              ;; Just fix there being more than one rather than show them all
              ;(> num-choices 1) xs
              (= num-choices 1) (first xs)
              (zero? num-choices) (insta/parse my-parser input))
        _ (assert res (str "No result. Num of choices is: " num-choices))]
    [msg res]))

The above code does the trick: always get a pinpointed answer. To me it is not that obvious that after insta/parses returns an empty list, insta/parse needs to be called to get the error information. Using the parse errors documentation will result in better code than the above. It shows how the error information is actually there in the metadata, and how to retrieve it - the answer to this question is already in the documentation!


Solution

  • When you use parser itself on the input instead of going through insta/parses, it prints a pretty exact message of the error at the REPL.

    An example:

    (def ebnf
      "expr = A DOT
       A    = 'A'
       DOT  = '.'")
    
    user> ((insta/parser ebnf) "A.")
    [:expr [:A "A"] [:DOT "."]]
    user> ((insta/parser ebnf) "B.")
    Parse error at line 1, column 1:
    B.
    ^
    Expected:
    "A"