Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

post_traverse failure does not fail choice #146

Open
foresttoney opened this issue Feb 26, 2025 · 2 comments
Open

post_traverse failure does not fail choice #146

foresttoney opened this issue Feb 26, 2025 · 2 comments

Comments

@foresttoney
Copy link
Contributor

Let's assume I defined a combinator using post_traverse - something like parse_date.

  def parse_date(combinator \\ P.empty()) do
    combinator
    |> P.post_traverse({__MODULE__, :__parse_date__, []})
  end

  def __parse_date__(rest, acc, context, _line, _offset) do
    with [input] <- acc,
         {:ok, datetime} <- Timex.parse(input, "{YY}{0M}{0D}"),
         date <- Timex.to_date(datetime) do
      {rest, [date], context}
    else
      _ ->
        {:error, "expected date"}
    end
  end

My assumption would be that you can define something like:

bytes(6)
|> choice([
  parse_date(),
  empty() |> replace(nil)
])

However, the failure condition defined in parse_date doesn't actually fail the choice combinator - you just end up with the error message as the result of the combinator.

@josevalim
Copy link
Member

Hi @foresttoney, can you please expand what you mean, for example, what is the output that you get and what you expected? If you can provide code samples, even better, thanks!

@foresttoney
Copy link
Contributor Author

foresttoney commented Feb 26, 2025

Yeah - will try to provide some more context. I have a combinator that parses a string in yymmdd format into a Date.

  def yymmdd(combinator \\ P.empty()) do
    combinator
    |> P.post_traverse({__MODULE__, :__yymmdd__, []})
  end

  def __yymmdd__(rest, acc, context, _line, _offset) do
    with [input] <- acc,
         {:ok, datetime} <- Timex.parse(input, "{YY}{0M}{0D}"),
         date <- Timex.to_date(datetime) do
      {rest, [date], context}
    else
      _ ->
        {:error, "expected YYMMDD date"}
    end
  end

I use the combinator like this:

    ...
    |> P.concat(
      P.bytes(6)
      |> PP.yymmdd()
      |> P.unwrap_and_tag(:available_on)
    )
    ...

This has been sufficient to this point because the entire combinator should fail if yymmdd fails. I have now run into a few scenarios where yymmdd is optional. I could define something like maybe_yymmdd but would prefer to do something like combinator1 <|> combinator2.

This is how I imagine it would work:

  defparsec(
    :test_combinator,
    P.empty()
    |> P.choice([
      P.bytes(6) |> PP.yymmdd(),
      P.bytes(6) |> P.replace(nil)
    ])
  )
describe "test combinator" do
  test "valid date" do
    input = "100501"
    result = test_combinator(input)
    assert result == {:ok, [~D[2010-05-01]], "", %{}, {1, 0}, 6}
  end

  test "invalid date" do
    input = "000000"
    result = test_combinator(input)
    assert result == {:ok, [nil], "", %{}, {1, 0}, 6} 
     # actual result
     # {:error, "expected YYMMDD date", "", %{}, {1, 0}, 6}
  end
end

You can see that using choice here doesn't really matter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants