Only last element from choice is reported in error reason
fuelen opened this issue · comments
In order to lean NimbleParsec I'm trying to implement a module for working with maths expressions.
Here is the module
defmodule Math do
import NimbleParsec
integer =
optional(string("-") |> replace(-1))
|> integer(min: 1)
|> reduce({Enum, :reduce, [&*/2]})
|> unwrap_and_tag(:integer)
|> label("integer")
whitespace = ignore(ascii_string(' ', min: 1)) |> label("space")
operator =
whitespace
|> ascii_char([?+, ?-, ?*, ?/, ?^])
|> reduce(:charlist_to_atom)
|> unwrap_and_tag(:operator)
|> concat(whitespace)
|> label("operator")
left_parenthesis = string("(")
|> replace(:left)
|> unwrap_and_tag(:parenthesis)
|> label("left parenthesis")
right_parenthesis = string(")")
|> replace(:right)
|> unwrap_and_tag(:parenthesis)
|> label("right parenthesis")
expression_in_parentheses =
left_parenthesis
|> parsec(:expression)
|> concat(right_parenthesis)
|> label("expression in parentheses")
term = choice([expression_in_parentheses, integer])
defparsecp(
:expression,
choice([
term |> lookahead(choice([right_parenthesis, eos()])),
term |> concat(operator) |> parsec(:expression) |> label("binary operation")
])
|> label("expression")
)
defparsec(:parse, parsec(:expression))
defp charlist_to_atom(charlist) do
charlist |> to_string() |> String.to_atom()
end
end
> Math.parse("(")
{:error, "expected integer while processing binary operation inside expression",
"(", %{}, {1, 0}, 0}
In the error above I'd expect something like expected integer or expression in parentheses...
, but or
part is not there. If I swap elements in term
combinator, then label expression in parentheses
is reported and label integer
is hidden.
Is this a bug in a library or I'm doing something wrong?
If we pick a choice, then we only show what is inside the choice. The issue is that you moved the term inside the choice and we are trying the second choice in your expression. Do this instead:
defparsecp(
:expression,
term |>
choice([
lookahead(choice([right_parenthesis, eos()])),
operator |> parsec(:expression) |> label("binary operation")
])
)
Or alternatively keep it inside but then remove the label("expression"), because that is replacing the "or" by your own label.
@josevalim I've replaced defparsecp :expression
with your suggestion and still have similar wording
> Math.parse("(")
{:error,
"expected integer while processing expression in parentheses or integer", "(",
%{}, {1, 0}, 0}
Which seems to be correct? It is saying you should have: "(integer while processing expression in parentheses) or (integer)"
To be clear, you either put an integer inside the parens or you remove the parens and have an integer. It is no wonder this is hard to express as a written language. :)
Ah, now I understood what is written. Thanks for your time