Skip to content

Fix a few semicolon issues #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
26 changes: 18 additions & 8 deletions lib/spitfire.ex
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ defmodule Spitfire do

# eat all the beginning eol tokens in case the file starts with a comment
parser =
while current_token(parser) == :eol <- parser do
while current_token(parser) in [:eol, :";"] <- parser do
next_token(parser)
end

Expand Down Expand Up @@ -230,6 +230,8 @@ defmodule Spitfire do
precedence < power
end

# TODO Is anything needed here? Removing all of these tokens does not break any tests
# TODO Should we add :";" to the list?
@terminals MapSet.new([:eol, :eof, :"}", :")", :"]", :">>"])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All tests pass even with
@terminals MapSet.new([])

@terminals_with_comma MapSet.put(@terminals, :",")
defp(parse_expression(parser, assoc \\ @lowest, is_list \\ false, is_map \\ false, is_top \\ false, is_stab \\ false))
Expand Down Expand Up @@ -310,7 +312,8 @@ defmodule Spitfire do
end

{parser, is_valid} = validate_peek(parser, current_token_type(parser))

# TODO should we handle ; here?
# TODO removing peek_token(parser) != :eol does not break any tests
if is_valid do
while (is_nil(Map.get(parser, :stab_state)) and not MapSet.member?(terminals, peek_token(parser))) &&
(current_token(parser) != :do and peek_token(parser) != :eol) &&
Expand Down Expand Up @@ -396,7 +399,7 @@ defmodule Spitfire do

cond do
# if the next token is the closing paren or if the next token is a newline and the next next token is the closing paren
peek_token(parser) == :")" || (peek_token(parser) == :eol && peek_token(next_token(parser)) == :")") ->
peek_token(parser) == :")" || (peek_token(parser) in [:eol, :";"] && peek_token(next_token(parser)) == :")") ->
parser =
parser
|> Map.put(:nesting, old_nesting)
Expand Down Expand Up @@ -428,11 +431,11 @@ defmodule Spitfire do
{ast, parser}

# if the next token is a new line, but the next next token is not the closing paren (implied from previous clause)
peek_token(parser) == :eol or current_token(parser) == :-> ->
peek_token(parser) in [:eol, :";"] or current_token(parser) == :-> ->
# second conditon checks of the next next token is a closing paren or another expression
{exprs, parser} =
while2 current_token(parser) == :-> ||
(peek_token(parser) == :eol && parser |> next_token() |> peek_token() != :")") <- parser do
(peek_token(parser) in [:eol, :";"] && parser |> next_token() |> peek_token() != :")") <- parser do
{ast, parser} =
case Map.get(parser, :stab_state) do
%{ast: lhs} ->
Expand Down Expand Up @@ -479,7 +482,7 @@ defmodule Spitfire do

# handles if the closing paren is on a new line or the same line
parser =
if peek_token(parser) == :eol do
if peek_token(parser) in [:eol, :";"] do
next_token(parser)
else
parser
Expand Down Expand Up @@ -1667,6 +1670,8 @@ defmodule Spitfire do
else
{left, parser} = prefix.(parser)

# TODO should we add ; here?
# TODO is anything needed? Removing all tokens does not break any tests
terminals = [:eol, :eof, :"}", :")", :"]", :">>"]

{parser, is_valid} = validate_peek(parser, current_token_type(parser))
Expand Down Expand Up @@ -2325,8 +2330,12 @@ defmodule Spitfire do
parser
end

# TODO this may be too greedy. Probably the better option would be to have distinct eat_eol/1 and eat_eol_or_semicolon/1
defp eat_eol(parser) do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

eat_eol should be more context aware. Semicolons are not allowed in all contexts or change the meaning

eat(%{:eol => true, :";" => true}, parser)
case eat(%{:eol => true, :";" => true}, parser) do
%{current_token: {token, _}} = parser when token in [:eol, :";"] -> eat_eol(parser)
parser -> parser
end
end

defp eat_eol_at(parser, idx) do
Expand Down Expand Up @@ -2389,7 +2398,7 @@ defmodule Spitfire do
:eof
end

defp peek_token_eat_eol(%{peek_token: {:eol, _token}} = parser) do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

eat_eol should be more context aware. Semicolons are not allowed in all contexts or change the meaning

defp peek_token_eat_eol(%{peek_token: {token, _token}} = parser) when token in [:eol, :";"] do
peek_token_eat_eol(next_token(parser))
end

Expand Down Expand Up @@ -2601,6 +2610,7 @@ defmodule Spitfire do
nil
end

# NOTE no need to handle ; here as this function is used only for building metadata and counting newlines there
defp peek_newlines(%{peek_token: {:eol, {_line, _col, newlines}}}) when is_integer(newlines) do
newlines
end
Expand Down
47 changes: 47 additions & 0 deletions test/spitfire_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ defmodule SpitfireTest do
# assert Spitfire.parse(code) == s2q(code)
end

test "semicolon in block" do
code = """
defmodule MyModule do
import List

;(__cursor__())
end
"""

assert Spitfire.parse(code) == s2q(code)
end

test "parses valid elixir" do
code = """
defmodule Foo do
Expand Down Expand Up @@ -415,6 +427,9 @@ defmodule SpitfireTest do
!false
)
''',
~S'''
(; !false;)
''',
"(not false)",
~S'''
a do
Expand Down Expand Up @@ -995,6 +1010,21 @@ defmodule SpitfireTest do

assert Spitfire.parse(code) == s2q(code)

code = ~S'''
(
min_line = line(meta); max_line = closing_line(meta); Enum.any?(comments, fn %{line: line} -> line > min_line and line < max_line end)
)
'''

assert Spitfire.parse(code) == s2q(code)

code = ~S'''
(
min_line = line(meta); max_line = closing_line(meta); Enum.any?(comments, fn %{line: line} -> line > min_line and line < max_line end); )
'''

assert Spitfire.parse(code) == s2q(code)

code = ~S'''
(min_line = line(meta)
max_line = closing_line(meta)
Expand Down Expand Up @@ -1715,6 +1745,15 @@ defmodule SpitfireTest do
assert Spitfire.parse(code) == s2q(code)
end

test "starts with a semicolon" do
code = """
;
some_code = :foo
"""

assert Spitfire.parse(code) == s2q(code)
end

test "default args" do
code = ~S'''
def foo(arg \\ :value) do
Expand All @@ -1725,6 +1764,14 @@ defmodule SpitfireTest do
assert Spitfire.parse(code) == s2q(code)
end

test "default args semicolons" do
code = ~S'''
def foo(arg \\ :value) do; :ok; end
'''

assert Spitfire.parse(code) == s2q(code)
end

test "literal encoder" do
codes = [
~S'''
Expand Down
Loading