Skip to content

Commit 6f00961

Browse files
authored
Print codepoints in generated guards as character literals (#158)
1 parent 1b11bb9 commit 6f00961

3 files changed

Lines changed: 53 additions & 15 deletions

File tree

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ clauses, which are shown below:
6868
defp datetime__0(<<x0, x1, x2, x3, "-", x4, x5, "-", x6, x7, "T",
6969
x8, x9, ":", x10, x11, ":", x12, x13, rest::binary>>,
7070
acc, stack, comb__context, comb__line, comb__column)
71-
when x0 >= 48 and x0 <= 57 and (x1 >= 48 and x1 <= 57) and
72-
(x2 >= 48 and x2 <= 57) and (x3 >= 48 and x3 <= 57) and
73-
(x4 >= 48 and x4 <= 57) and (x5 >= 48 and x5 <= 57) and
74-
(x6 >= 48 and x6 <= 57) and (x7 >= 48 and x7 <= 57) and
75-
(x8 >= 48 and x8 <= 57) and (x9 >= 48 and x9 <= 57) and
76-
(x10 >= 48 and x10 <= 57) and (x11 >= 48 and x11 <= 57) and
77-
(x12 >= 48 and x12 <= 57) and (x13 >= 48 and x13 <= 57) do
71+
when x0 >= ?0 and x0 <= ?9 and (x1 >= ?0 and x1 <= ?9) and
72+
(x2 >= ?0 and x2 <= ?9) and (x3 >= ?0 and x3 <= ?9) and
73+
(x4 >= ?0 and x4 <= ?9) and (x5 >= ?0 and x5 <= ?9) and
74+
(x6 >= ?0 and x6 <= ?9) and (x7 >= ?0 and x7 <= ?9) and
75+
(x8 >= ?0 and x8 <= ?9) and (x9 >= ?0 and x9 <= ?9) and
76+
(x10 >= ?0 and x10 <= ?9) and (x11 >= ?0 and x11 <= ?9) and
77+
(x12 >= ?0 and x12 <= ?9) and (x13 >= ?0 and x13 <= ?9) do
7878
datetime__1(
7979
rest,
8080
[(x13 - 48) * 1 + (x12 - 48) * 10, (x11 - 48) * 1 + (x10 - 48) * 10,

lib/nimble_parsec/compiler.ex

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,31 +1165,48 @@ defmodule NimbleParsec.Compiler do
11651165
defp bin_range_to_guard(var, range) do
11661166
case range do
11671167
min..min//step when abs(step) == 1 ->
1168-
quote(do: unquote(var) === unquote(min))
1168+
quote(do: unquote(var) === unquote(char(min)))
11691169

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

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

11761176
min when is_integer(min) ->
1177-
quote(do: unquote(var) === unquote(min))
1177+
quote(do: unquote(var) === unquote(char(min)))
11781178

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

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

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

11881188
{:not, min} when is_integer(min) ->
1189-
quote(do: unquote(var) !== unquote(min))
1189+
quote(do: unquote(var) !== unquote(char(min)))
11901190
end
11911191
end
11921192

1193+
# `?a` and `97` are the same AST node, so the literal spelling only survives as
1194+
# `:token` metadata, which `Macro.to_string/1` honours when printing.
1195+
defp char(codepoint) do
1196+
token =
1197+
case codepoint do
1198+
?\\ -> "?\\\\"
1199+
?\s -> "?\\s"
1200+
?\n -> "?\\n"
1201+
?\t -> "?\\t"
1202+
?\r -> "?\\r"
1203+
codepoint when codepoint in ?!..?~ -> "?" <> <<codepoint>>
1204+
codepoint -> Integer.to_string(codepoint)
1205+
end
1206+
1207+
{:__block__, [token: token], [codepoint]}
1208+
end
1209+
11931210
defp inspect_bin_range(min..max//_, printable?) do
11941211
{" in the range #{inspect_char(min)} to #{inspect_char(max)}",
11951212
printable? and printable?(min) and printable?(max)}

test/nimble_parsec_test.exs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,27 @@ defmodule NimbleParsecTest do
16381638
end
16391639
end
16401640

1641+
describe "generated guards" do
1642+
test "spell codepoints out as literals" do
1643+
assert guard_source(ascii_char([?a..?f])) =~ "x0 >= ?a and x0 <= ?f"
1644+
assert guard_source(ascii_char([?\s, ?\n])) =~ "x0 === ?\\s or x0 === ?\\n"
1645+
assert guard_source(ascii_char([?\t, ?\r])) =~ "x0 === ?\\t or x0 === ?\\r"
1646+
assert guard_source(ascii_char([?\\, ?~])) =~ "x0 === ?\\\\ or x0 === ?~"
1647+
assert guard_source(ascii_char(not: ?q)) =~ "x0 !== ?q"
1648+
1649+
# Codepoints with no printable spelling stay as plain integers.
1650+
assert guard_source(ascii_char([0x00, 0x1B])) =~ "x0 === 0 or x0 === 27"
1651+
assert guard_source(utf8_char([, ])) =~ "x0 === 233 or x0 === 261"
1652+
assert guard_source(utf8_char([0x1F600, 0x1F601])) =~ "x0 === 128_512 or x0 === 128_513"
1653+
end
1654+
1655+
defp guard_source(combinator) do
1656+
{defs, _inline} = NimbleParsec.Compiler.compile(:literals, combinator, [])
1657+
1658+
Enum.map_join(defs, "\n", fn {_name, _args, guards, _body} -> Macro.to_string(guards) end)
1659+
end
1660+
end
1661+
16411662
describe "continuing parser" do
16421663
defparsecp :digits, [?0..?9] |> ascii_char() |> times(min: 1) |> label("digits")
16431664
defparsecp :chars, [?a..?z] |> ascii_char() |> times(min: 1) |> label("chars")

0 commit comments

Comments
 (0)