Skip to content

Commit 708da64

Browse files
mkaputmhanberg
andauthored
fix: parse newline ternary continuation after ellipsis (#115)
Ellipsis was being treated as standalone before `//` when ternary continuation arrived with newline metadata, which produced a mismatched AST for forms like `x...\n//y`. This change detects newline-associated ternary continuation in `parse_ellipsis_op/1` and keeps existing semicolon-separated behavior intact (`x...;//y`). It also adds regression assertions for newline and semicolon-separated ternary continuation cases in the property-regression test block. --------- Co-authored-by: Mitchell Hanberg <mitch@mitchellhanberg.com>
1 parent f0a79a0 commit 708da64

2 files changed

Lines changed: 42 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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2305,6 +2305,13 @@ defmodule SpitfireTest do
23052305
# with/else stab body with leading semicolon after newline
23062306
assert Spitfire.parse("with x <- 1 do :ok else _ -> \n;a end") ==
23072307
s2q("with x <- 1 do :ok else _ -> \n;a end")
2308+
2309+
# Ellipsis + ternary edge cases (newline and semicolon-separated)
2310+
assert Spitfire.parse("x...\n//y") == s2q("x...\n//y")
2311+
assert Spitfire.parse("x...;//y") == s2q("x...;//y")
2312+
assert Spitfire.parse("x...\n;//y") == s2q("x...\n;//y")
2313+
assert Spitfire.parse("x...\n;\n//y") == s2q("x...\n;\n//y")
2314+
assert Spitfire.parse("x...\n;\n# comment\n//y") == s2q("x...\n;\n# comment\n//y")
23082315
end
23092316
end
23102317

@@ -2319,6 +2326,20 @@ defmodule SpitfireTest do
23192326
[{[line: 1, column: 5], "unknown token: %"}]}
23202327
end
23212328

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

0 commit comments

Comments
 (0)