Skip to content

Commit e761a27

Browse files
committed
feat: Handle atom_safe and kw_identifier_safe tokens
add failing cases
1 parent 157e851 commit e761a27

File tree

2 files changed

+96
-9
lines changed

2 files changed

+96
-9
lines changed

lib/spitfire.ex

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,16 @@ defmodule Spitfire do
248248
:alias -> &parse_alias/1
249249
:"<<" -> &parse_bitstring/1
250250
:kw_identifier when is_list or is_map -> &parse_kw_identifier/1
251+
:kw_identifier_safe when is_list or is_map -> &parse_kw_identifier/1
251252
:kw_identifier_unsafe when is_list or is_map -> &parse_kw_identifier/1
252253
:kw_identifier when not is_list and not is_map -> &parse_bracketless_kw_list/1
254+
:kw_identifier_safe when not is_list and not is_map -> &parse_bracketless_kw_list/1
253255
:kw_identifier_unsafe when not is_list and not is_map -> &parse_bracketless_kw_list/1
254256
:int -> &parse_int/1
255257
:flt -> &parse_float/1
256258
:atom -> &parse_atom/1
257259
:atom_quoted -> &parse_atom/1
260+
:atom_safe -> &parse_atom/1
258261
:atom_unsafe -> &parse_atom/1
259262
true -> &parse_boolean/1
260263
false -> &parse_boolean/1
@@ -535,6 +538,9 @@ defmodule Spitfire do
535538
end
536539
end
537540

541+
defp map_kw_identifier_to_atom_token(:kw_identifier_safe), do: :atom_safe
542+
defp map_kw_identifier_to_atom_token(:kw_identifier_unsafe), do: :atom_unsafe
543+
538544
defp parse_kw_identifier(%{current_token: {:kw_identifier, meta, token}} = parser) do
539545
trace "parse_kw_identifier", trace_meta(parser) do
540546
token = encode_literal(parser, token, meta)
@@ -546,9 +552,10 @@ defmodule Spitfire do
546552
end
547553
end
548554

549-
defp parse_kw_identifier(%{current_token: {:kw_identifier_unsafe, meta, tokens}} = parser) do
550-
trace "parse_kw_identifier (unsafe)", trace_meta(parser) do
551-
{atom, parser} = parse_atom(%{parser | current_token: {:atom_unsafe, meta, tokens}})
555+
defp parse_kw_identifier(%{current_token: {type, meta, tokens}} = parser)
556+
when type in [:kw_identifier_safe, :kw_identifier_unsafe] do
557+
trace "parse_kw_identifier (#{type})", trace_meta(parser) do
558+
{atom, parser} = parse_atom(%{parser | current_token: {map_kw_identifier_to_atom_token(type), meta, tokens}})
552559
parser = parser |> next_token() |> eat_eol()
553560

554561
{expr, parser} = parse_expression(parser, @kw_identifier, false, false, false)
@@ -584,9 +591,10 @@ defmodule Spitfire do
584591
end
585592
end
586593

587-
defp parse_bracketless_kw_list(%{current_token: {:kw_identifier_unsafe, meta, tokens}} = parser) do
588-
trace "parse_bracketless_kw_list (unsafe)", trace_meta(parser) do
589-
{atom, parser} = parse_atom(%{parser | current_token: {:atom_unsafe, meta, tokens}})
594+
defp parse_bracketless_kw_list(%{current_token: {type, meta, tokens}} = parser)
595+
when type in [:kw_identifier_safe, :kw_identifier_unsafe] do
596+
trace "parse_bracketless_kw_list (#{type})", trace_meta(parser) do
597+
{atom, parser} = parse_atom(%{parser | current_token: {map_kw_identifier_to_atom_token(type), meta, tokens}})
590598
parser = parser |> next_token() |> eat_eol()
591599

592600
atom =
@@ -1274,11 +1282,18 @@ defmodule Spitfire do
12741282
end
12751283
end
12761284

1277-
defp parse_atom(%{current_token: {:atom_unsafe, _, tokens}} = parser) do
1278-
trace "parse_atom (unsafe)", trace_meta(parser) do
1285+
defp parse_atom(%{current_token: {type, _, tokens}} = parser) when type in [:atom_safe, :atom_unsafe] do
1286+
trace "parse_atom (#{type})", trace_meta(parser) do
12791287
meta = current_meta(parser)
12801288
{args, parser} = parse_interpolation(parser, tokens)
1281-
{{{:., meta, [:erlang, :binary_to_atom]}, [{:delimiter, ~S'"'} | meta], [{:<<>>, meta, args}, :utf8]}, parser}
1289+
1290+
binary_to_atom_op =
1291+
case type do
1292+
:atom_safe -> :binary_to_existing_atom
1293+
:atom_unsafe -> :binary_to_atom
1294+
end
1295+
1296+
{{{:., meta, [:erlang, binary_to_atom_op]}, [{:delimiter, ~S'"'} | meta], [{:<<>>, meta, args}, :utf8]}, parser}
12821297
end
12831298
end
12841299

test/spitfire_test.exs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,78 @@ defmodule SpitfireTest do
29112911
end
29122912
end
29132913

2914+
describe "safe atoms" do
2915+
test "parses quoted atoms" do
2916+
code = """
2917+
:"abc#{Enum.random(1..10_000)}"
2918+
"""
2919+
2920+
# tokenizer returns error
2921+
assert Spitfire.parse(code, existing_atoms_only: true) == {:ok, {:__block__, [], []}}
2922+
2923+
code = """
2924+
:"abc\#{foo}"
2925+
"""
2926+
2927+
# tokenizer returns atom_safe token
2928+
assert Spitfire.parse(code, existing_atoms_only: true) == s2q(code, existing_atoms_only: true)
2929+
end
2930+
2931+
test "parses quoted kw identifiers" do
2932+
# TODO this code crashes the parser
2933+
# ** (FunctionClauseError) no function clause matching in Spitfire.peek_token/1
2934+
2935+
# The following arguments were given to Spitfire.peek_token/1:
2936+
2937+
# # 1
2938+
# %{tokens: [nil | :eot], literal_encoder: nil, errors: [{[line: 1, column: 1], "missing closing bracket for list"}, {[], "unknown token: eot"}], current_token: {:"]", nil}, fuel: 150, peek_token: nil, nesting: 0}
2939+
2940+
# Attempted function clauses (showing 7 out of 7):
2941+
2942+
# defp peek_token(%{peek_token: {:stab_op, _, token}})
2943+
# defp peek_token(%{peek_token: {type, _, _, _}}) when type === :list_heredoc or type === :bin_heredoc
2944+
# defp peek_token(%{peek_token: {token, _, _}})
2945+
# defp peek_token(%{peek_token: {token, _}})
2946+
# defp peek_token(%{peek_token: {token, _, _, _, _, _, _}})
2947+
# defp peek_token(%{peek_token: :eof})
2948+
# defp peek_token(%{tokens: :eot})
2949+
2950+
# code: assert Spitfire.parse(code, existing_atoms_only: true) == {:ok, {:__block__, [], []}}
2951+
# stacktrace:
2952+
# (spitfire 0.1.5) lib/spitfire.ex:2386: Spitfire.peek_token/1
2953+
# (spitfire 0.1.5) lib/spitfire.ex:318: anonymous fn/7 in Spitfire.parse_expression/6
2954+
# (spitfire 0.1.5) lib/spitfire/while.ex:49: Spitfire.While.do_while/2
2955+
# (spitfire 0.1.5) lib/spitfire.ex:196: anonymous fn/1 in Spitfire.parse_program/1
2956+
# (spitfire 0.1.5) lib/spitfire/while.ex:5: Spitfire.While2.recurse/3
2957+
# (spitfire 0.1.5) lib/spitfire.ex:195: Spitfire.parse_program/1
2958+
# (spitfire 0.1.5) lib/spitfire.ex:140: Spitfire.parse/2
2959+
# test/spitfire_test.exs:2936: (test)
2960+
# code = "[\"abc#{Enum.random(1..10000)}\": 1]"
2961+
2962+
# # tokenizer return error
2963+
# assert Spitfire.parse(code, existing_atoms_only: true) == {:ok, {:__block__, [], []}}
2964+
2965+
# TODO this code errors with a wrong message
2966+
# {
2967+
# :error,
2968+
# {:%{}, [{:closing, []}, {:line, 1}, {:column, 1}], [{:__block__, [error: true], []}]},
2969+
# [{[], "unknown token: eot"}, {[], "missing closing brace for map"}]
2970+
# }
2971+
2972+
# code = "%{\"abc#{Enum.random(1..10000)}\": 1}"
2973+
2974+
# # tokenizer return error
2975+
# assert Spitfire.parse(code, existing_atoms_only: true) == {:ok, {:__block__, [], []}}
2976+
2977+
code = """
2978+
["abc\#{foo}": 1]
2979+
"""
2980+
2981+
# tokenizer returns kw_identifier_safe token
2982+
assert Spitfire.parse(code, existing_atoms_only: true) == s2q(code, existing_atoms_only: true)
2983+
end
2984+
end
2985+
29142986
defp s2q(code, opts \\ []) do
29152987
Code.string_to_quoted(code, Keyword.merge([columns: true, token_metadata: true, emit_warnings: false], opts))
29162988
end

0 commit comments

Comments
 (0)