dashbitco / nimble_parsec

A simple and fast library for text-based parser combinators

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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