Skip to content

Commit 192697d

Browse files
mkaputmhanberg
authored andcommitted
fix: align ellipsis and range-step parsing with Elixir
Update parser semantics around ellipsis and range-step handling to match Elixir behavior for edge cases involving //. What changed: - In parse_infix_expression/2, handle infix // as a range-step operator only when the lhs is a range node, producing :..//. - For non-range lhs, record the same range-step diagnostic intent Elixir uses, instead of silently accepting a generic binary //. - In parse_ellipsis_op/1, treat :ternary_op as a continuation candidate rather than a standalone-ellipsis stop token, which fixes newline forms like x...\n//y. - Add a regression test asserting non-range // produces an error path for x...//y. Why: The previous behavior diverged from Elixir in two directions: it accepted non-range infix // and parsed x...\n//y as ternary continuation instead of preserving ellipsis unary continuation semantics. This change resolves both at grammar/parse semantics level rather than adding context-specific lookahead hacks. Special considerations: - Tokenizer behavior was already aligned with Elixir for these inputs; the fix is parser-only. - Existing semicolon-separated ellipsis cases remain covered and passing.
1 parent 0a01912 commit 192697d

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

lib/spitfire.ex

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1404,8 +1404,28 @@ defmodule Spitfire do
14041404
{rhs, parser}
14051405
end
14061406

1407+
parser =
1408+
if token == :"//" and not match?({:.., _, [_, _]}, lhs) do
1409+
put_error(
1410+
pre_parser,
1411+
{meta,
1412+
"the range step operator (//) must immediately follow the range definition operator (..), for example: 1..9//2. If you wanted to define a default argument, use (\\\\) instead. Syntax error before: '//'"}
1413+
)
1414+
else
1415+
parser
1416+
end
1417+
14071418
ast =
14081419
case token do
1420+
:"//" ->
1421+
case lhs do
1422+
{:.., lhs_meta, [left, middle]} ->
1423+
{:..//, lhs_meta, [left, middle, rhs]}
1424+
1425+
_ ->
1426+
{token, newlines ++ meta, [lhs, rhs]}
1427+
end
1428+
14091429
:"not in" ->
14101430
{:not, meta, [{:in, meta, [lhs, rhs]}]}
14111431

@@ -2911,7 +2931,7 @@ defmodule Spitfire do
29112931
if MapSet.member?(@terminals_with_comma, peek_token(parser)) or
29122932
peek_token(parser) == :";" or
29132933
peek in [:stab_op, :do, :end, :block_identifier] or
2914-
(is_binary_op?(peek) and peek != :dual_op) do
2934+
(is_binary_op?(peek) and peek not in [:dual_op, :ternary_op]) do
29152935
{{:..., current_meta(parser), []}, parser}
29162936
else
29172937
meta = current_meta(parser)

test/spitfire_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,6 +2325,20 @@ defmodule SpitfireTest do
23252325
[{[line: 1, column: 5], "unknown token: %"}]}
23262326
end
23272327

2328+
test "range step operator requires a range lhs" do
2329+
code = "x...//y"
2330+
2331+
assert {:error, _} = s2q(code)
2332+
assert {:error, _ast, errors} = Spitfire.parse(code)
2333+
2334+
assert Enum.any?(errors, fn {_meta, message} ->
2335+
String.contains?(
2336+
message,
2337+
"the range step operator (//) must immediately follow the range definition operator (..)"
2338+
)
2339+
end)
2340+
end
2341+
23282342
test "missing bitstring brackets" do
23292343
code = """
23302344
<<one::

0 commit comments

Comments
 (0)