Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ clauses, which are shown below:
defp datetime__0(<<x0, x1, x2, x3, "-", x4, x5, "-", x6, x7, "T",
x8, x9, ":", x10, x11, ":", x12, x13, rest::binary>>,
acc, stack, comb__context, comb__line, comb__column)
when x0 >= 48 and x0 <= 57 and (x1 >= 48 and x1 <= 57) and
(x2 >= 48 and x2 <= 57) and (x3 >= 48 and x3 <= 57) and
(x4 >= 48 and x4 <= 57) and (x5 >= 48 and x5 <= 57) and
(x6 >= 48 and x6 <= 57) and (x7 >= 48 and x7 <= 57) and
(x8 >= 48 and x8 <= 57) and (x9 >= 48 and x9 <= 57) and
(x10 >= 48 and x10 <= 57) and (x11 >= 48 and x11 <= 57) and
(x12 >= 48 and x12 <= 57) and (x13 >= 48 and x13 <= 57) do
when x0 >= ?0 and x0 <= ?9 and (x1 >= ?0 and x1 <= ?9) and
(x2 >= ?0 and x2 <= ?9) and (x3 >= ?0 and x3 <= ?9) and
(x4 >= ?0 and x4 <= ?9) and (x5 >= ?0 and x5 <= ?9) and
(x6 >= ?0 and x6 <= ?9) and (x7 >= ?0 and x7 <= ?9) and
(x8 >= ?0 and x8 <= ?9) and (x9 >= ?0 and x9 <= ?9) and
(x10 >= ?0 and x10 <= ?9) and (x11 >= ?0 and x11 <= ?9) and
(x12 >= ?0 and x12 <= ?9) and (x13 >= ?0 and x13 <= ?9) do
datetime__1(
rest,
[(x13 - 48) * 1 + (x12 - 48) * 10, (x11 - 48) * 1 + (x10 - 48) * 10,
Expand Down
33 changes: 25 additions & 8 deletions lib/nimble_parsec/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1165,31 +1165,48 @@ defmodule NimbleParsec.Compiler do
defp bin_range_to_guard(var, range) do
case range do
min..min//step when abs(step) == 1 ->
quote(do: unquote(var) === unquote(min))
quote(do: unquote(var) === unquote(char(min)))

min..max//1 ->
quote(do: unquote(var) >= unquote(min) and unquote(var) <= unquote(max))
quote(do: unquote(var) >= unquote(char(min)) and unquote(var) <= unquote(char(max)))

min..max//-1 ->
quote(do: unquote(var) >= unquote(max) and unquote(var) <= unquote(min))
quote(do: unquote(var) >= unquote(char(max)) and unquote(var) <= unquote(char(min)))

min when is_integer(min) ->
quote(do: unquote(var) === unquote(min))
quote(do: unquote(var) === unquote(char(min)))

{:not, min..min//step} when abs(step) == 1 ->
quote(do: unquote(var) !== unquote(min))
quote(do: unquote(var) !== unquote(char(min)))

{:not, min..max//1} ->
quote(do: unquote(var) < unquote(min) or unquote(var) > unquote(max))
quote(do: unquote(var) < unquote(char(min)) or unquote(var) > unquote(char(max)))

{:not, min..max//-1} ->
quote(do: unquote(var) < unquote(max) or unquote(var) > unquote(min))
quote(do: unquote(var) < unquote(char(max)) or unquote(var) > unquote(char(min)))

{:not, min} when is_integer(min) ->
quote(do: unquote(var) !== unquote(min))
quote(do: unquote(var) !== unquote(char(min)))
end
end

# `?a` and `97` are the same AST node, so the literal spelling only survives as
# `:token` metadata, which `Macro.to_string/1` honours when printing.
defp char(codepoint) do
token =
case codepoint do
?\\ -> "?\\\\"
?\s -> "?\\s"
?\n -> "?\\n"
?\t -> "?\\t"
?\r -> "?\\r"
codepoint when codepoint in ?!..?~ -> "?" <> <<codepoint>>
codepoint -> "0x" <> String.pad_leading(Integer.to_string(codepoint, 16), 2, "0")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just fallback to a regular number in this case?

end

{:__block__, [token: token], [codepoint]}
end

defp inspect_bin_range(min..max//_, printable?) do
{" in the range #{inspect_char(min)} to #{inspect_char(max)}",
printable? and printable?(min) and printable?(max)}
Expand Down
22 changes: 22 additions & 0 deletions test/nimble_parsec_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,28 @@ defmodule NimbleParsecTest do
end
end

describe "generated guards" do
test "spell codepoints out as literals" do
assert guard_source(ascii_char([?a..?f])) =~ "x0 >= ?a and x0 <= ?f"
assert guard_source(ascii_char([?\s, ?\n])) =~ "x0 === ?\\s or x0 === ?\\n"
assert guard_source(ascii_char([?\t, ?\r])) =~ "x0 === ?\\t or x0 === ?\\r"
assert guard_source(ascii_char([?\\, ?~])) =~ "x0 === ?\\\\ or x0 === ?~"
assert guard_source(ascii_char(not: ?q)) =~ "x0 !== ?q"

# Codepoints with no printable spelling are spelled in hex, a byte wide at
# the least so that every byte reads as two digits.
assert guard_source(ascii_char([0x00, 0x1B])) =~ "x0 === 0x00 or x0 === 0x1B"
assert guard_source(utf8_char([?é, ?ą])) =~ "x0 === 0xE9 or x0 === 0x105"
assert guard_source(utf8_char([0x1F600, 0x1F601])) =~ "x0 === 0x1F600 or x0 === 0x1F601"
end

defp guard_source(combinator) do
{defs, _inline} = NimbleParsec.Compiler.compile(:literals, combinator, [])

Enum.map_join(defs, "\n", fn {_name, _args, guards, _body} -> Macro.to_string(guards) end)
end
end

describe "continuing parser" do
defparsecp :digits, [?0..?9] |> ascii_char() |> times(min: 1) |> label("digits")
defparsecp :chars, [?a..?z] |> ascii_char() |> times(min: 1) |> label("chars")
Expand Down
Loading