Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: elixir-lang/elixir
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.15.0
Choose a base ref
...
head repository: elixir-lang/elixir
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.15.1
Choose a head ref

Commits on Jun 19, 2023

  1. Fix path on Windows

    josevalim committed Jun 19, 2023
    Copy the full SHA
    0dd933b View commit details

Commits on Jun 20, 2023

  1. Copy the full SHA
    9b254e6 View commit details

Commits on Jun 21, 2023

  1. Copy the full SHA
    aab3543 View commit details
  2. Copy the full SHA
    1ca5d78 View commit details
  3. Copy the full SHA
    df5bc5a View commit details
  4. Copy the full SHA
    367d906 View commit details
  5. Copy the full SHA
    858835f View commit details

Commits on Jun 22, 2023

  1. Copy the full SHA
    c22f734 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    cc2d056 View commit details

Commits on Jun 23, 2023

  1. Copy the full SHA
    dcaa468 View commit details
  2. Copy the full SHA
    f322d26 View commit details
  3. Copy the full SHA
    3a34f24 View commit details
  4. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    bc40b6e View commit details
  5. Copy the full SHA
    fde96ce View commit details
  6. Copy the full SHA
    4c12bfc View commit details
  7. Use printf instead of echo to output user flags (#12704)

    Fixes #12677.
    
    Using printf here prevents an error when outputting the filtered flag
    `-e` as doing with with `echo "-e"` doesn't output anything
    (deleting the option we wanted to filter). 
    
    More info: https://unix.stackexchange.com/a/65819
    cjschneider2 authored and josevalim committed Jun 23, 2023
    Copy the full SHA
    0c6b447 View commit details
  8. Update CHANGELOG

    josevalim committed Jun 23, 2023
    Copy the full SHA
    52cf991 View commit details

Commits on Jun 24, 2023

  1. Parser honors :static_atoms_encoder for multi-letter sigils (#12706)

    * Unify sigil token generation
    
    * Parser honors :static_atoms_encoder for multi-letter sigils
    
    * Remove chars from token, reverse engineer from atom
    sabiwara authored and josevalim committed Jun 24, 2023
    Copy the full SHA
    c032839 View commit details
  2. Copy the full SHA
    93bfbdf View commit details
  3. Copy the full SHA
    a89f8a9 View commit details
  4. Copy the full SHA
    3785e6f View commit details

Commits on Jun 25, 2023

  1. Fix heisentest

    josevalim committed Jun 25, 2023
    Copy the full SHA
    871e737 View commit details

Commits on Jun 27, 2023

  1. Copy the full SHA
    6032094 View commit details
  2. Copy the full SHA
    9e195f5 View commit details
  3. Copy the full SHA
    e63b0b6 View commit details
  4. Copy the full SHA
    0fcee76 View commit details
  5. mix format

    josevalim committed Jun 27, 2023
    Copy the full SHA
    7db2dae View commit details
  6. Copy the full SHA
    785c292 View commit details

Commits on Jun 28, 2023

  1. Copy the full SHA
    6d82912 View commit details

Commits on Jun 30, 2023

  1. Copy the full SHA
    bceda78 View commit details
  2. Release v1.15.1

    josevalim committed Jun 30, 2023
    Copy the full SHA
    f1e5d77 View commit details
Showing with 668 additions and 337 deletions.
  1. +44 −0 CHANGELOG.md
  2. +1 −1 VERSION
  3. +9 −4 bin/elixir
  4. +9 −4 bin/elixir.bat
  5. +1 −1 bin/iex
  6. +1 −1 bin/iex.bat
  7. +4 −1 lib/elixir/lib/code.ex
  8. +21 −5 lib/elixir/lib/code/fragment.ex
  9. +1 −1 lib/elixir/lib/config/provider.ex
  10. +3 −0 lib/elixir/lib/float.ex
  11. +3 −1 lib/elixir/src/elixir.erl
  12. +12 −3 lib/elixir/src/elixir_errors.erl
  13. +2 −2 lib/elixir/src/elixir_parser.yrl
  14. +21 −11 lib/elixir/src/elixir_tokenizer.erl
  15. +64 −32 lib/elixir/test/elixir/code_fragment_test.exs
  16. +30 −0 lib/elixir/test/elixir/kernel/parser_test.exs
  17. +10 −10 lib/elixir/test/erlang/tokenizer_test.erl
  18. +16 −9 lib/ex_unit/lib/ex_unit/capture_server.ex
  19. +3 −3 lib/ex_unit/lib/ex_unit/case.ex
  20. +27 −41 lib/ex_unit/lib/ex_unit/doc_test.ex
  21. +13 −0 lib/ex_unit/test/ex_unit/capture_log_test.exs
  22. +12 −2 lib/ex_unit/test/ex_unit/doc_test_test.exs
  23. +11 −3 lib/iex/lib/iex.ex
  24. +4 −8 lib/iex/lib/iex/cli.ex
  25. +1 −1 lib/iex/lib/iex/helpers.ex
  26. +2 −2 lib/logger/lib/logger.ex
  27. +3 −3 lib/logger/lib/logger/formatter.ex
  28. +23 −1 lib/logger/test/logger/formatter_test.exs
  29. +71 −52 lib/mix/lib/mix/compilers/elixir.ex
  30. +26 −21 lib/mix/lib/mix/compilers/erlang.ex
  31. +34 −29 lib/mix/lib/mix/dep/converger.ex
  32. +5 −5 lib/mix/lib/mix/dep/loader.ex
  33. +1 −1 lib/mix/lib/mix/dep/umbrella.ex
  34. +2 −1 lib/mix/lib/mix/release.ex
  35. +2 −2 lib/mix/lib/mix/tasks/compile.ex
  36. +6 −2 lib/mix/lib/mix/tasks/compile.leex.ex
  37. +6 −2 lib/mix/lib/mix/tasks/compile.yecc.ex
  38. +10 −1 lib/mix/lib/mix/tasks/release.ex
  39. +64 −26 lib/mix/test/mix/release_test.exs
  40. +17 −0 lib/mix/test/mix/tasks/compile.yecc_test.exs
  41. +40 −13 lib/mix/test/mix/tasks/compile_test.exs
  42. +33 −32 lib/mix/test/mix/umbrella_test.exs
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -74,6 +74,14 @@ A new function, called `Code.with_diagnostics/2`, has been added so this
information can be leveraged by editors, allowing them to point to several
errors at once.

### Potential incompatibilities

As part of this effort, the behaviour where undefined variables were
transformed into nullary function calls, often leading to confusing error
reports, has been disabled during project compilation. You can invoke
`Code.compiler_options(on_undefined_variable: :warn)` at the top of
your `mix.exs` to bring the old behaviour back.

## Integration with Erlang/OTP logger

This release provides additional features such as global logger
@@ -109,6 +117,42 @@ in the long term.
See the new `Logger` documentation for more information on the
new features and on compatibility.

## v1.15.1 (2023-06-30)

### 1. Enhancements

* [Code] `Code.string_to_quoted/2` honors `:static_atoms_encoder` for multi-letter sigils

### 2. Bug fixes

#### ExUnit

* [ExUnit.CaptureLog] Fix race condition on concurrent `capture_log`
* [ExUnit.CaptureLog] Respect options passed to nested `capture_log` calls
* [ExUnit.Doctest] Properly compile doctests without results terminated by fences
* [ExUnit.Doctest] Allow variables defined in doctests to be used in expectation

#### IEx

* [IEx] Ensure `pry` works on Erlang/OTP 25 and earlier while IEx is booting
* [IEx] `Code.Fragment.surround_context` considers surround context around spaces and parens

#### Logger

* [Logger] Do not assume Logger has been loaded at compile-time
* [Logger.Formatter] Properly handle `:function` as metadata

#### Mix

* [mix compile] Ensure the current project is available on the code path after its Elixir sources are compiled
* [mix compile] Guarantee yecc/leex are available when emitting warnings from previous runs
* [mix compile] Fix bug where an external resource was deleted after its
mtime was successfully retrieved
* [mix compile] Track removed modules and exports across local deps
* [mix deps] Fix an issue where dependencies could not be started in an umbrella projects
* [mix release] Properly handle optional dependencies when there is a conflict in the application start mode
* [mix release] Remove `--werl` from release scripts on Erlang/OTP 26

## v1.15.0 (2023-06-19)

### 1. Enhancements
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.15.0
1.15.1
13 changes: 9 additions & 4 deletions bin/elixir
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
set -e

ELIXIR_VERSION=1.15.0
ELIXIR_VERSION=1.15.1

if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -94,6 +94,7 @@ starts_with () {
}

ERL_EXEC="erl"
MODE="cli"
I=1
E=0
LENGTH=$#
@@ -104,9 +105,13 @@ while [ $I -le $LENGTH ]; do
S=0
C=0
case "$1" in
+elixirc|+iex)
+elixirc)
C=1
;;
+iex)
C=1
MODE="iex"
;;
-v|--no-halt|--dbg)
C=1
;;
@@ -223,12 +228,12 @@ fi
ERTS_BIN=
ERTS_BIN="$ERTS_BIN"

set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_cli $ERL "$@"
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_$MODE $ERL "$@"

if [ -n "$RUN_ERL_PIPE" ]; then
ESCAPED=""
for PART in "$@"; do
ESCAPED="$ESCAPED $(echo "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
ESCAPED="$ESCAPED $(printf '%s' "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
done
mkdir -p "$RUN_ERL_PIPE"
mkdir -p "$RUN_ERL_LOG"
13 changes: 9 additions & 4 deletions bin/elixir.bat
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)

set ELIXIR_VERSION=1.15.0
set ELIXIR_VERSION=1.15.1

setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
@@ -82,7 +82,7 @@ rem Option which determines whether the loop is over
set endLoop=0

rem Designates which mode / Elixir component to run as
set runMode="elixir"
set runMode="cli"

rem Designates the path to the current script
set SCRIPT_PATH=%~dp0
@@ -106,7 +106,7 @@ if !endLoop! == 1 (
)
rem ******* EXECUTION OPTIONS **********************
if !par!=="--werl" (set useWerl=1 && goto startloop)
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && goto startloop)
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && set runMode="iex" && goto startloop)
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && goto startloop)
rem ******* EVAL PARAMETERS ************************
if ""==!par:-e=! (
@@ -164,8 +164,13 @@ reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>n
if %errorlevel% == 0 (
set beforeExtra=-elixir ansi_enabled true !beforeExtra!
)
if !runMode! == "iex" (
set beforeExtra=-s elixir start_iex !beforeExtra!
) else (
set beforeExtra=-s elixir start_cli !beforeExtra!
)

set beforeExtra=-noshell -elixir_root !SCRIPT_PATH!..\lib -pa !SCRIPT_PATH!..\lib\elixir\ebin -s elixir start_cli !beforeExtra!
set beforeExtra=-noshell -elixir_root "!SCRIPT_PATH!..\lib" -pa "!SCRIPT_PATH!..\lib\elixir\ebin" !beforeExtra!

if defined ELIXIR_CLI_DRY_RUN (
if defined useWerl (
2 changes: 1 addition & 1 deletion bin/iex
Original file line number Diff line number Diff line change
@@ -30,4 +30,4 @@ readlink_f () {

SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" +iex "$@"
2 changes: 1 addition & 1 deletion bin/iex.bat
Original file line number Diff line number Diff line change
@@ -24,6 +24,6 @@ goto end

:run
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex %__ELIXIR_IEX_FLAGS% %*
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" +iex %__ELIXIR_IEX_FLAGS% %*
:end
endlocal
5 changes: 4 additions & 1 deletion lib/elixir/lib/code.ex
Original file line number Diff line number Diff line change
@@ -1136,7 +1136,10 @@ defmodule Code do
* syntax keywords (`fn`, `do`, `else`, and so on)
* atoms containing interpolation (`:"#{1 + 1} is two"`), as these
atoms are constructed at runtime.
atoms are constructed at runtime
* atoms used to represent single-letter sigils like `:sigil_X`
(but multi-letter sigils like `:sigil_XYZ` are encoded).
"""
@spec string_to_quoted(List.Chars.t(), keyword) ::
26 changes: 21 additions & 5 deletions lib/elixir/lib/code/fragment.ex
Original file line number Diff line number Diff line change
@@ -619,15 +619,15 @@ defmodule Code.Fragment do
{reversed_pre, post} = adjust_position(reversed_pre, post)

case take_identifier(post, []) do
{_, [], _} ->
:none ->
maybe_operator(reversed_pre, post, line, opts)

{:identifier, reversed_post, rest} ->
{rest, _} = strip_spaces(rest, 0)
reversed = reversed_post ++ reversed_pre

case codepoint_cursor_context(reversed, opts) do
{{:struct, acc}, offset} ->
{{:struct, acc}, offset} when acc != [] ->
build_surround({:struct, acc}, reversed, line, offset)

{{:alias, acc}, offset} ->
@@ -729,15 +729,31 @@ defmodule Code.Fragment do
do: take_identifier(t, [h | acc])

defp take_identifier(rest, acc) do
with {[?. | t], _} <- strip_spaces(rest, 0),
{stripped, _} = strip_spaces(rest, 0)

with [?. | t] <- stripped,
{[h | _], _} when h in ?A..?Z <- strip_spaces(t, 0) do
take_alias(rest, acc)
else
_ -> {:identifier, acc, rest}
# Consider it an identifier if we are at the end of line
# or if we have spaces not followed by . (call) or / (arity)
_ when acc == [] and (rest == [] or (hd(rest) in @space and hd(stripped) not in ~c"/.")) ->
{:identifier, acc, rest}

# If we are immediately followed by a container, we are still part of the identifier.
# We don't consider << as it _may_ be an operator.
_ when acc == [] and hd(stripped) in ~c"({[" ->
{:identifier, acc, rest}

_ when acc == [] ->
:none

_ ->
{:identifier, acc, rest}
end
end

defp take_alias([h | t], acc) when h not in @non_identifier,
defp take_alias([h | t], acc) when h in ?A..?Z or h in ?a..?z or h in ?0..9 or h == ?_,
do: take_alias(t, [h | acc])

defp take_alias(rest, acc) do
2 changes: 1 addition & 1 deletion lib/elixir/lib/config/provider.ex
Original file line number Diff line number Diff line change
@@ -342,7 +342,7 @@ defmodule Config.Provider do
* Make the runtime value match the compile time one
* Recompile your project. If the misconfigured application is a dependency, \
you may need to run "mix deps.compile #{app} --force"
you may need to run "mix deps.clean #{app} --build"
* Alternatively, you can disable this check. If you are using releases, you can \
set :validate_compile_env to false in your release configuration. If you are \
3 changes: 3 additions & 0 deletions lib/elixir/lib/float.ex
Original file line number Diff line number Diff line change
@@ -4,6 +4,9 @@ defmodule Float do
@moduledoc """
Functions for working with floating-point numbers.
For mathematical operations on top of floating-points,
see Erlang's [`:math`](`:math`) module.
## Kernel functions
There are functions related to floating-point numbers on the `Kernel` module
4 changes: 3 additions & 1 deletion lib/elixir/src/elixir.erl
Original file line number Diff line number Diff line change
@@ -192,7 +192,8 @@ start_cli() ->
'Elixir.Kernel.CLI':main(init:get_plain_arguments()),
elixir_config:booted().

%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26.
%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26
%% and add "-e :elixir.start_iex" to bin/iex instead of $MODE.
start() ->
case code:ensure_loaded(prim_tty) of
{module, _} ->
@@ -207,6 +208,7 @@ start() ->
start_iex() ->
case code:ensure_loaded(prim_tty) of
{module, _} ->
start_cli(),
spawn(fun() ->
elixir_config:wait_until_booted(),
(shell:whereis() =:= undefined) andalso 'Elixir.IEx':cli()
15 changes: 12 additions & 3 deletions lib/elixir/src/elixir_errors.erl
Original file line number Diff line number Diff line change
@@ -170,13 +170,22 @@ parse_error(Location, File, <<"syntax error before: ">>, Keyword, Input)

%% Produce a human-readable message for errors before a sigil
parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full, Input) ->
{sigil, _, Sigil, [Content | _], _, _, _} = parse_erl_term(Full),
{sigil, _, Atom, [Content | _], _, _, _} = parse_erl_term(Full),
Content2 = case is_binary(Content) of
true -> Content;
false -> <<>>
end,
SigilName = list_to_binary(Sigil),
Message = <<"syntax error before: sigil \~", SigilName/binary, " starting with content '", Content2/binary, "'">>,

% :static_atoms_encoder might encode :sigil_ atoms as arbitrary terms
MaybeSigil = case is_atom(Atom) of
true -> case atom_to_binary(Atom) of
<<"sigil_", Chars/binary>> -> <<"\~", Chars/binary, " ">>;
_ -> <<>>
end;
false -> <<>>
end,

Message = <<"syntax error before: sigil ", MaybeSigil/binary, "starting with content '", Content2/binary, "'">>,
raise_snippet(Location, File, Input, 'Elixir.SyntaxError', Message);

%% Binaries (and interpolation) are wrapped in [<<...>>]
4 changes: 2 additions & 2 deletions lib/elixir/src/elixir_parser.yrl
Original file line number Diff line number Diff line change
@@ -939,11 +939,11 @@ build_access(Expr, {List, Meta}) ->

%% Interpolation aware

build_sigil({sigil, Location, Sigil, Parts, Modifiers, Indentation, Delimiter}) ->
build_sigil({sigil, Location, Atom, Parts, Modifiers, Indentation, Delimiter}) ->
Meta = meta_from_location(Location),
MetaWithDelimiter = [{delimiter, Delimiter} | Meta],
MetaWithIndentation = meta_with_indentation(Meta, Indentation),
{list_to_atom("sigil_" ++ Sigil),
{Atom,
MetaWithDelimiter,
[{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}.

32 changes: 21 additions & 11 deletions lib/elixir/src/elixir_tokenizer.erl
Original file line number Diff line number Diff line change
@@ -1550,7 +1550,7 @@ tokenize_sigil_name([S | T], NameAcc, Line, Column, Scope, Tokens) when ?is_upca
tokenize_sigil_name(T, [S | NameAcc], Line, Column + 1, Scope, Tokens);
% With a lowercase letter and a non-empty NameAcc we return an error.
tokenize_sigil_name([S | _T] = Original, [_ | _] = NameAcc, _Line, _Column, _Scope, _Tokens) when ?is_downcase(S) ->
Message = "invalid sigil name, it should be either a one-letter lowercase letter or a" ++
Message = "invalid sigil name, it should be either a one-letter lowercase letter or a" ++
" sequence of uppercase letters only, got: ",
{error, Message, [$~] ++ lists:reverse(NameAcc) ++ Original};
% We finished the letters, so the name is over.
@@ -1561,12 +1561,8 @@ tokenize_sigil_contents([H, H, H | T] = Original, [S | _] = SigilName, Line, Col
when ?is_quote(H) ->
case extract_heredoc_with_interpolation(Line, Column, Scope, ?is_downcase(S), T, H) of
{ok, NewLine, NewColumn, Parts, Rest, NewScope} ->
{Final, Modifiers} = collect_modifiers(Rest, []),
Indentation = NewColumn - 4,
TokenColumn = Column - 1 - length(SigilName),
Token = {sigil, {Line, TokenColumn, nil}, SigilName, Parts, Modifiers, Indentation, <<H, H, H>>},
NewColumnWithModifiers = NewColumn + length(Modifiers),
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, NewScope, Tokens, Indentation, <<H, H, H>>);

{error, Reason} ->
error(Reason, [$~] ++ SigilName ++ Original, Scope, Tokens)
@@ -1576,12 +1572,8 @@ tokenize_sigil_contents([H | T] = Original, [S | _] = SigilName, Line, Column, S
when ?is_sigil(H) ->
case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
{NewLine, NewColumn, Parts, Rest, NewScope} ->
{Final, Modifiers} = collect_modifiers(Rest, []),
Indentation = nil,
TokenColumn = Column - 1 - length(SigilName),
Token = {sigil, {Line, TokenColumn, nil}, SigilName, tokens_to_binary(Parts), Modifiers, Indentation, <<H>>},
NewColumnWithModifiers = NewColumn + length(Modifiers),
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <<H>>);

{error, Reason} ->
Sigil = [$~, S, H],
@@ -1601,6 +1593,24 @@ tokenize_sigil_contents([H | _] = Original, SigilName, Line, Column, Scope, Toke
tokenize_sigil_contents([], _SigilName, Line, Column, Scope, Tokens) ->
tokenize([], Line, Column, Scope, Tokens).

add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, Scope, Tokens, Indentation, Delimiter) ->
TokenColumn = Column - 1 - length(SigilName),
MaybeEncoded = case SigilName of
% Single-letter sigils present no risk of atom exhaustion (limited possibilities)
[_Char] -> {ok, list_to_atom("sigil_" ++ SigilName)};
_ -> unsafe_to_atom("sigil_" ++ SigilName, Line, TokenColumn, Scope)
end,
case MaybeEncoded of
{ok, Atom} ->
{Final, Modifiers} = collect_modifiers(Rest, []),
Token = {sigil, {Line, TokenColumn, nil}, Atom, Parts, Modifiers, Indentation, Delimiter},
NewColumnWithModifiers = NewColumn + length(Modifiers),
tokenize(Final, NewLine, NewColumnWithModifiers, Scope, [Token | Tokens]);

{error, Reason} ->
error(Reason, Rest, Scope, Tokens)
end.

%% Fail early on invalid do syntax. For example, after
%% most keywords, after comma and so on.
tokenize_keyword_terminator(DoLine, DoColumn, do, [{identifier, {Line, Column, Meta}, Atom} | T]) ->
Loading