-module(sourceror_errors). -export([parse_error/4]). -include("sourceror_elixir.hrl"). %% Tokenization parsing/errors. -spec parse_error(elixir:keyword(), binary() | {binary(), binary()}, binary(), binary()) -> no_return(). parse_error(Location, File, Error, <<>>) -> Message = case Error of <<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>; _ -> Error end, raise(Location, File, 'Elixir.TokenMissingError', Message); %% Show a nicer message for end of line parse_error(Location, File, <<"syntax error before: ">>, <<"eol">>) -> raise(Location, File, 'Elixir.SyntaxError', <<"unexpectedly reached end of line. The current expression is invalid or incomplete">>); %% Show a nicer message for keywords pt1 (Erlang keywords show up wrapped in single quotes) parse_error(Location, File, <<"syntax error before: ">>, Keyword) when Keyword == <<"'not'">>; Keyword == <<"'and'">>; Keyword == <<"'or'">>; Keyword == <<"'when'">>; Keyword == <<"'after'">>; Keyword == <<"'catch'">>; Keyword == <<"'end'">> -> raise_reserved(Location, File, binary_part(Keyword, 1, byte_size(Keyword) - 2)); %% Show a nicer message for keywords pt2 (Elixir keywords show up as is) parse_error(Location, File, <<"syntax error before: ">>, Keyword) when Keyword == <<"fn">>; Keyword == <<"else">>; Keyword == <<"rescue">>; Keyword == <<"true">>; Keyword == <<"false">>; Keyword == <<"nil">>; Keyword == <<"in">> -> raise_reserved(Location, File, Keyword); %% Produce a human-readable message for errors before a sigil parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) -> {sigil, _, Sigil, [Content | _], _, _, _} = parse_erl_term(Full), Content2 = case is_binary(Content) of true -> Content; false -> <<>> end, Message = <<"syntax error before: sigil \~", Sigil, " starting with content '", Content2/binary, "'">>, raise(Location, File, 'Elixir.SyntaxError', Message); %% Binaries (and interpolation) are wrapped in [<<...>>] parse_error(Location, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -> Term = case parse_erl_term(Full) of [H | _] when is_binary(H) -> <<$", H/binary, $">>; _ -> <<$">> end, raise(Location, File, 'Elixir.SyntaxError', <>); %% Given a string prefix and suffix to insert the token inside the error message rather than append it parse_error(Location, File, {ErrorPrefix, ErrorSuffix}, Token) when is_binary(ErrorPrefix), is_binary(ErrorSuffix), is_binary(Token) -> Message = <>, raise(Location, File, 'Elixir.SyntaxError', Message); %% Misplaced char tokens (for example, {char, _, 97}) are translated by Erlang into %% the char literal (i.e., the token in the previous example becomes $a), %% because {char, _, _} is a valid Erlang token for an Erlang char literal. We %% want to represent that token as ?a in the error, according to the Elixir %% syntax. parse_error(Location, File, <<"syntax error before: ">>, <<$$, Char/binary>>) -> Message = <<"syntax error before: ?", Char/binary>>, raise(Location, File, 'Elixir.SyntaxError', Message); %% Everything else is fine as is parse_error(Location, File, Error, Token) when is_binary(Error), is_binary(Token) -> Message = <>, raise(Location, File, 'Elixir.SyntaxError', Message). parse_erl_term(Term) -> {ok, Tokens, _} = erl_scan:string(binary_to_list(Term)), {ok, Parsed} = erl_parse:parse_term(Tokens ++ [{dot, 1}]), Parsed. raise_reserved(Location, File, Keyword) -> raise(Location, File, 'Elixir.SyntaxError', <<"syntax error before: ", Keyword/binary, ". \"", Keyword/binary, "\" is a " "reserved word in Elixir and therefore its usage is limited. For instance, " "it can't be used as a variable or be defined nor invoked as a regular function">>). raise(Location, File, Kind, Message) when is_binary(File) -> raise(Kind, Message, [{file, File} | Location]). raise(Kind, Message, Opts) when is_binary(Message) -> Stacktrace = try throw(ok) catch _:_:Stack -> Stack end, Exception = Kind:exception([{description, Message} | Opts]), erlang:raise(error, Exception, tl(Stacktrace)).