Skip to content

Commit 05fe4ed

Browse files
committed
feat: structs and special identifiers
1 parent 5f17497 commit 05fe4ed

File tree

3 files changed

+241
-4
lines changed

3 files changed

+241
-4
lines changed

lib/spitfire.ex

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ defmodule Spitfire do
156156
:"(" -> &parse_grouped_expression/1
157157
:"{" -> &parse_tuple_literal/1
158158
:%{} -> &parse_map_literal/1
159+
:% -> &parse_struct_literal/1
159160
nil -> &parse_nil_literal/1
160161
_ -> nil
161162
end
@@ -1097,6 +1098,41 @@ defmodule Spitfire do
10971098
end
10981099
end
10991100

1101+
defp parse_struct_literal(%{current_token: {:%, _}} = parser) do
1102+
meta = current_meta(parser)
1103+
parser = next_token(parser)
1104+
{type, parser} = parse_expression(parser)
1105+
1106+
parser = next_token(parser)
1107+
1108+
brace_meta = current_meta(parser)
1109+
parser = parser |> next_token() |> eat_eol()
1110+
1111+
if current_token(parser) == :"}" do
1112+
closing = current_meta(parser)
1113+
ast = {:%, meta, [type, {:%{}, [{:closing, closing} | brace_meta], []}]}
1114+
{ast, parser}
1115+
else
1116+
{pairs, parser} = parse_comma_list(parser, is_map: true)
1117+
{pairs, _} = Enum.unzip(pairs)
1118+
1119+
parser = eat_at(parser, :eol, 1)
1120+
1121+
parser =
1122+
case peek_token(parser) do
1123+
:"}" ->
1124+
next_token(parser)
1125+
1126+
_ ->
1127+
put_error(parser, {current_meta(parser), "missing closing brace for struct"})
1128+
end
1129+
1130+
closing = current_meta(parser)
1131+
ast = {:%, meta, [type, {:%{}, [{:closing, closing} | brace_meta], pairs}]}
1132+
{ast, parser}
1133+
end
1134+
end
1135+
11001136
defp parse_tuple_literal(%{current_token: {:"{", _}} = parser) do
11011137
meta = current_meta(parser)
11021138
orig_parser = parser
@@ -1267,6 +1303,7 @@ defmodule Spitfire do
12671303
end
12681304

12691305
closing = current_meta(parser)
1306+
12701307
{{token, [{:closing, closing} | meta], List.wrap(pairs)}, parser}
12711308
end
12721309
end
@@ -1319,7 +1356,8 @@ defmodule Spitfire do
13191356
defp parse_identifier(%{current_token: {type, _, token}} = parser) when type in [:identifier, :do_identifier] do
13201357
meta = current_meta(parser)
13211358

1322-
if peek_token(parser) in ([:";", :eol, :eof, :",", :")", :do, :., :"}", :"]"] ++ @operators) do
1359+
if token in [:__MODULE__, :__ENV__, :__DIR__, :__CALLER__] or
1360+
peek_token(parser) in ([:";", :eol, :eof, :",", :")", :do, :., :"}", :"]"] ++ @operators) do
13231361
{{token, meta, Elixir}, parser}
13241362
else
13251363
parser = next_token(parser)
@@ -1351,7 +1389,12 @@ defmodule Spitfire do
13511389

13521390
def tokenize(code, opts) do
13531391
tokens =
1354-
case code |> String.to_charlist() |> :elixir_tokenizer.tokenize(1, Keyword.put(opts, :check_terminators, false)) do
1392+
case code
1393+
|> String.to_charlist()
1394+
|> :elixir_tokenizer.tokenize(
1395+
1,
1396+
opts |> Keyword.put(:check_terminators, false) |> Keyword.put(:cursor_completion, false)
1397+
) do
13551398
{:ok, _, _, _, tokens} ->
13561399
tokens
13571400

@@ -1723,6 +1766,10 @@ defmodule Spitfire do
17231766
ptype in (@operators ++ [:"[", :";", :eol, :eof, :",", :")", :do, :., :"}", :"]", :end])
17241767
end
17251768

1769+
defp valid_peek?(:alias, ptype) when ptype in [:"{"] do
1770+
true
1771+
end
1772+
17261773
defp valid_peek?(_ctype, ptype) do
17271774
ptype in (@operators ++ [:";", :eol, :eof, :",", :")", :do, :., :"}", :"]", :end])
17281775
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defmodule Spitfire.MixProject do
2121
# Run "mix help deps" to learn about dependencies.
2222
defp deps do
2323
[
24-
{:styler, "~> 0.11.2"}
24+
{:styler, "~> 0.11", only: :dev}
2525
# {:dep_from_hexpm, "~> 0.3.0"},
2626
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
2727
]

test/spitfire_test.exs

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,56 @@ defmodule SpitfireTest do
726726
end
727727
end
728728

729+
test "parses structs" do
730+
codes = [
731+
{~s'''
732+
%Foo.Bar{}
733+
''',
734+
{:%, [line: 1, column: 1],
735+
[
736+
{:__aliases__, [last: [line: 1, column: 2], line: 1, column: 2], [:Foo, :Bar]},
737+
{:%{}, [closing: [line: 1, column: 6], line: 1, column: 5], []}
738+
]}},
739+
{~s'%Foo.Bar{name: "alice", height: 73}',
740+
{:%, [line: 1, column: 1],
741+
[
742+
{:__aliases__, [last: [line: 1, column: 6], line: 1, column: 2], [:Foo, :Bar]},
743+
{:%{}, [closing: [line: 1, column: 35], line: 1, column: 9], [name: "alice", height: 73]}
744+
]}},
745+
{
746+
~s'%Foo.Bar{name: name, properties: %Properties{key: key, value: get_value()}}',
747+
{:%, [line: 1, column: 1],
748+
[
749+
{:__aliases__, [last: [line: 1, column: 6], line: 1, column: 2], [:Foo, :Bar]},
750+
{:%{}, [closing: [line: 1, column: 75], line: 1, column: 9],
751+
[
752+
name: {:name, [line: 1, column: 16], Elixir},
753+
properties:
754+
{:%, [line: 1, column: 34],
755+
[
756+
{:__aliases__, [last: [line: 1, column: 35], line: 1, column: 35], [:Properties]},
757+
{:%{}, [closing: [line: 1, column: 74], line: 1, column: 45],
758+
[
759+
key: {:key, [line: 1, column: 51], Elixir},
760+
value: {:get_value, [closing: [line: 1, column: 73], line: 1, column: 63], []}
761+
]}
762+
]}
763+
]}
764+
]}
765+
},
766+
{~S'%__MODULE__{foo: bar}',
767+
{:%, [line: 1, column: 1],
768+
[
769+
{:__MODULE__, [line: 1, column: 2], Elixir},
770+
{:%{}, [closing: [line: 1, column: 21], line: 1, column: 12], [foo: {:bar, [line: 1, column: 18], Elixir}]}
771+
]}}
772+
]
773+
774+
for {code, expected} <- codes do
775+
assert Spitfire.parse(code) == {:ok, expected}
776+
end
777+
end
778+
729779
test "parses operators" do
730780
codes = [
731781
{~s'''
@@ -2592,11 +2642,151 @@ defmodule SpitfireTest do
25922642
assert Spitfire.parse(code) == {:ok, expected}
25932643
end
25942644
end
2645+
2646+
test "parses special keywords" do
2647+
codes = [
2648+
{"__MODULE__", {:__MODULE__, [line: 1, column: 1], Elixir}}
2649+
]
2650+
2651+
for {code, expected} <- codes do
2652+
assert Spitfire.parse(code) == {:ok, expected}
2653+
end
2654+
end
2655+
2656+
test "from nextls test" do
2657+
code = ~S'''
2658+
defmodule Foo do
2659+
defstruct [:foo, bar: "yo"]
2660+
2661+
defmodule State do
2662+
defstruct [:yo]
2663+
2664+
def new(attrs) do
2665+
struct(%__MODULE__{}, attrs)
2666+
end
2667+
end
2668+
2669+
@spec run(any(), any(), any()) :: :something
2670+
def run(foo, bar, baz) do
2671+
:something
2672+
end
2673+
end
2674+
'''
2675+
2676+
assert Spitfire.parse(code) ==
2677+
{:ok,
2678+
{:defmodule, [do: [line: 1, column: 15], end: [line: 16, column: 1], line: 1, column: 1],
2679+
[
2680+
{:__aliases__, [line: 1, column: 11], [:Foo]},
2681+
[
2682+
do:
2683+
{:__block__, [],
2684+
[
2685+
{:defstruct,
2686+
[
2687+
end_of_expression: [newlines: 2, line: 2, column: 30],
2688+
line: 2,
2689+
column: 3
2690+
], [[:foo, {:bar, "yo"}]]},
2691+
{:defmodule,
2692+
[
2693+
end_of_expression: [newlines: 2, line: 10, column: 6],
2694+
do: [line: 4, column: 19],
2695+
end: [line: 10, column: 3],
2696+
line: 4,
2697+
column: 3
2698+
],
2699+
[
2700+
{:__aliases__, [line: 4, column: 13], [:State]},
2701+
[
2702+
do:
2703+
{:__block__, [],
2704+
[
2705+
{:defstruct,
2706+
[
2707+
end_of_expression: [newlines: 2, line: 5, column: 20],
2708+
line: 5,
2709+
column: 5
2710+
], [[:yo]]},
2711+
{:def,
2712+
[
2713+
do: [line: 7, column: 20],
2714+
end: [line: 9, column: 5],
2715+
line: 7,
2716+
column: 5
2717+
],
2718+
[
2719+
{:new, [closing: [line: 7, column: 18], line: 7, column: 9],
2720+
[{:attrs, [line: 7, column: 13], Elixir}]},
2721+
[
2722+
do:
2723+
{:struct, [closing: [line: 8, column: 34], line: 8, column: 7],
2724+
[
2725+
{:%, [line: 8, column: 14],
2726+
[
2727+
{:__MODULE__, [line: 8, column: 15], Elixir},
2728+
{:%{},
2729+
[
2730+
closing: [line: 8, column: 26],
2731+
line: 8,
2732+
column: 25
2733+
], []}
2734+
]},
2735+
{:attrs, [line: 8, column: 29], Elixir}
2736+
]}
2737+
]
2738+
]}
2739+
]}
2740+
]
2741+
]},
2742+
{:@,
2743+
[
2744+
end_of_expression: [newlines: 1, line: 12, column: 47],
2745+
line: 12,
2746+
column: 3
2747+
],
2748+
[
2749+
{:spec, [line: 12, column: 4],
2750+
[
2751+
{:"::", [line: 12, column: 34],
2752+
[
2753+
{:run, [closing: [line: 12, column: 32], line: 12, column: 9],
2754+
[
2755+
{:any, [closing: [line: 12, column: 17], line: 12, column: 13], []},
2756+
{:any, [closing: [line: 12, column: 24], line: 12, column: 20], []},
2757+
{:any, [closing: [line: 12, column: 31], line: 12, column: 27], []}
2758+
]},
2759+
:something
2760+
]}
2761+
]}
2762+
]},
2763+
{:def,
2764+
[
2765+
do: [line: 13, column: 26],
2766+
end: [line: 15, column: 3],
2767+
line: 13,
2768+
column: 3
2769+
],
2770+
[
2771+
{:run, [closing: [line: 13, column: 24], line: 13, column: 7],
2772+
[
2773+
{:foo, [line: 13, column: 11], Elixir},
2774+
{:bar, [line: 13, column: 16], Elixir},
2775+
{:baz, [line: 13, column: 21], Elixir}
2776+
]},
2777+
[do: :something]
2778+
]}
2779+
]}
2780+
]
2781+
]}}
2782+
end
25952783
end
25962784

25972785
describe "code with errors" do
2786+
# TODO: this needs a change to the tokenizer i believe, or a way to splice out the unknown token
2787+
@tag :skip
25982788
test "unknown prefix operator" do
2599-
code = "foo %bar"
2789+
code = "foo $bar, baz"
26002790

26012791
assert Spitfire.parse(code) ==
26022792
{:error, {:foo, [line: 1, column: 1], [{:__error__, [line: 1, column: 5], ["unknown token: %"]}]},

0 commit comments

Comments
 (0)