diff --git a/lib/spitfire.ex b/lib/spitfire.ex index 7d8688f..c9e415c 100644 --- a/lib/spitfire.ex +++ b/lib/spitfire.ex @@ -1404,8 +1404,28 @@ defmodule Spitfire do {rhs, parser} end + parser = + if token == :"//" and not match?({:.., _, [_, _]}, lhs) do + put_error( + pre_parser, + {meta, + "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: '//'"} + ) + else + parser + end + ast = case token do + :"//" -> + case lhs do + {:.., lhs_meta, [left, middle]} -> + {:..//, lhs_meta, [left, middle, rhs]} + + _ -> + {token, newlines ++ meta, [lhs, rhs]} + end + :"not in" -> {:not, meta, [{:in, meta, [lhs, rhs]}]} @@ -2911,7 +2931,7 @@ defmodule Spitfire do if MapSet.member?(@terminals_with_comma, peek_token(parser)) or peek_token(parser) == :";" or peek in [:stab_op, :do, :end, :block_identifier] or - (is_binary_op?(peek) and peek != :dual_op) do + (is_binary_op?(peek) and peek not in [:dual_op, :ternary_op]) do {{:..., current_meta(parser), []}, parser} else meta = current_meta(parser) diff --git a/test/spitfire_test.exs b/test/spitfire_test.exs index 9b0109f..483b4d6 100644 --- a/test/spitfire_test.exs +++ b/test/spitfire_test.exs @@ -2305,6 +2305,13 @@ defmodule SpitfireTest do # with/else stab body with leading semicolon after newline assert Spitfire.parse("with x <- 1 do :ok else _ -> \n;a end") == s2q("with x <- 1 do :ok else _ -> \n;a end") + + # Ellipsis + ternary edge cases (newline and semicolon-separated) + assert Spitfire.parse("x...\n//y") == s2q("x...\n//y") + assert Spitfire.parse("x...;//y") == s2q("x...;//y") + assert Spitfire.parse("x...\n;//y") == s2q("x...\n;//y") + assert Spitfire.parse("x...\n;\n//y") == s2q("x...\n;\n//y") + assert Spitfire.parse("x...\n;\n# comment\n//y") == s2q("x...\n;\n# comment\n//y") end end @@ -2319,6 +2326,20 @@ defmodule SpitfireTest do [{[line: 1, column: 5], "unknown token: %"}]} end + test "range step operator requires a range lhs" do + code = "x...//y" + + assert {:error, _} = s2q(code) + assert {:error, _ast, errors} = Spitfire.parse(code) + + assert Enum.any?(errors, fn {_meta, message} -> + String.contains?( + message, + "the range step operator (//) must immediately follow the range definition operator (..)" + ) + end) + end + test "missing bitstring brackets" do code = """ <