diff --git a/README.md b/README.md index 0f429ac..e291044 100644 --- a/README.md +++ b/README.md @@ -68,13 +68,13 @@ clauses, which are shown below: defp datetime__0(<>, 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, diff --git a/lib/nimble_parsec/compiler.ex b/lib/nimble_parsec/compiler.ex index 1b2869d..9f29caf 100644 --- a/lib/nimble_parsec/compiler.ex +++ b/lib/nimble_parsec/compiler.ex @@ -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 -> Integer.to_string(codepoint) + 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)} diff --git a/test/nimble_parsec_test.exs b/test/nimble_parsec_test.exs index e14de9a..945d6e8 100644 --- a/test/nimble_parsec_test.exs +++ b/test/nimble_parsec_test.exs @@ -1638,6 +1638,27 @@ 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 stay as plain integers. + assert guard_source(ascii_char([0x00, 0x1B])) =~ "x0 === 0 or x0 === 27" + assert guard_source(utf8_char([?é, ?ą])) =~ "x0 === 233 or x0 === 261" + assert guard_source(utf8_char([0x1F600, 0x1F601])) =~ "x0 === 128_512 or x0 === 128_513" + 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")