4
4
% % Note that this is also called by the Erlang backend, so we also support
5
5
% % the line number to be none (as it may happen in some erlang errors).
6
6
-module (elixir_errors ).
7
- -export ([compile_error /3 , compile_error /4 , form_error /4 , parse_error /4 ]).
7
+ -export ([compile_error /3 , compile_error /4 , form_error /4 , parse_error /5 ]).
8
8
-export ([warning_prefix /0 , erl_warn /3 , print_warning /3 , log_and_print_warning /4 , form_warn /4 ]).
9
9
-include (" elixir.hrl" ).
10
10
-type location () :: non_neg_integer () | {non_neg_integer (), non_neg_integer ()}.
@@ -82,24 +82,36 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
82
82
compile_error (Meta , File , io_lib :format (Format , Args )).
83
83
84
84
% % Tokenization parsing/errors.
85
+ snippet (InputString , Location , StartLine , StartColumn ) ->
86
+ {line , Line } = lists :keyfind (line , 1 , Location ),
87
+ case lists :keyfind (column , 1 , Location ) of
88
+ {column , Column } ->
89
+ Lines = string :split (InputString , " \n " , all ),
90
+ Snippet = elixir_utils :characters_to_binary (lists :nth (Line - StartLine + 1 , Lines )),
91
+ #{content => Snippet , offset => (Column - StartColumn )};
92
+
93
+ false ->
94
+ nil
95
+ end .
85
96
86
97
-spec parse_error (elixir :keyword (), binary () | {binary (), binary ()},
87
- binary (), binary ()) -> no_return ().
88
- parse_error (Location , File , Error , <<>>) ->
98
+ binary (), binary (), { list (), integer (), integer ()} ) -> no_return ().
99
+ parse_error (Location , File , Error , <<>>, { InputString , StartLine , StartColumn } ) ->
89
100
Message = case Error of
90
101
<<" syntax error before: " >> -> <<" syntax error: expression is incomplete" >>;
91
- _ -> Error
102
+ _ -> << Error / binary >>
92
103
end ,
93
- raise (Location , File , 'Elixir.TokenMissingError' , Message );
104
+ Snippet = snippet (InputString , Location , StartLine , StartColumn ),
105
+ raise (Location , File , 'Elixir.TokenMissingError' , Message , Snippet );
94
106
95
107
% % Show a nicer message for end of line
96
- parse_error (Location , File , <<" syntax error before: " >>, <<" eol" >>) ->
108
+ parse_error (Location , File , <<" syntax error before: " >>, <<" eol" >>, _Input ) ->
97
109
raise (Location , File , 'Elixir.SyntaxError' ,
98
110
<<" unexpectedly reached end of line. The current expression is invalid or incomplete" >>);
99
111
100
112
101
113
% % Show a nicer message for keywords pt1 (Erlang keywords show up wrapped in single quotes)
102
- parse_error (Location , File , <<" syntax error before: " >>, Keyword )
114
+ parse_error (Location , File , <<" syntax error before: " >>, Keyword , _Input )
103
115
when Keyword == <<" 'not'" >>;
104
116
Keyword == <<" 'and'" >>;
105
117
Keyword == <<" 'or'" >>;
@@ -110,7 +122,7 @@ parse_error(Location, File, <<"syntax error before: ">>, Keyword)
110
122
raise_reserved (Location , File , binary_part (Keyword , 1 , byte_size (Keyword ) - 2 ));
111
123
112
124
% % Show a nicer message for keywords pt2 (Elixir keywords show up as is)
113
- parse_error (Location , File , <<" syntax error before: " >>, Keyword )
125
+ parse_error (Location , File , <<" syntax error before: " >>, Keyword , _Input )
114
126
when Keyword == <<" fn" >>;
115
127
Keyword == <<" else" >>;
116
128
Keyword == <<" rescue" >>;
@@ -121,7 +133,7 @@ parse_error(Location, File, <<"syntax error before: ">>, Keyword)
121
133
raise_reserved (Location , File , Keyword );
122
134
123
135
% % Produce a human-readable message for errors before a sigil
124
- parse_error (Location , File , <<" syntax error before: " >>, <<" {sigil," , _Rest /binary >> = Full ) ->
136
+ parse_error (Location , File , <<" syntax error before: " >>, <<" {sigil," , _Rest /binary >> = Full , _Input ) ->
125
137
{sigil , _ , Sigil , [Content | _ ], _ , _ , _ } = parse_erl_term (Full ),
126
138
Content2 = case is_binary (Content ) of
127
139
true -> Content ;
@@ -131,15 +143,15 @@ parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/bina
131
143
raise (Location , File , 'Elixir.SyntaxError' , Message );
132
144
133
145
% % Binaries (and interpolation) are wrapped in [<<...>>]
134
- parse_error (Location , File , Error , <<" [" , _ /binary >> = Full ) when is_binary (Error ) ->
146
+ parse_error (Location , File , Error , <<" [" , _ /binary >> = Full , _Input ) when is_binary (Error ) ->
135
147
Term = case parse_erl_term (Full ) of
136
148
[H | _ ] when is_binary (H ) -> <<$" , H /binary , $" >>;
137
149
_ -> <<$" >>
138
150
end ,
139
151
raise (Location , File , 'Elixir.SyntaxError' , <<Error /binary , Term /binary >>);
140
152
141
153
% % Given a string prefix and suffix to insert the token inside the error message rather than append it
142
- parse_error (Location , File , {ErrorPrefix , ErrorSuffix }, Token ) when is_binary (ErrorPrefix ), is_binary (ErrorSuffix ), is_binary (Token ) ->
154
+ parse_error (Location , File , {ErrorPrefix , ErrorSuffix }, Token , _Input ) when is_binary (ErrorPrefix ), is_binary (ErrorSuffix ), is_binary (Token ) ->
143
155
Message = <<ErrorPrefix /binary , Token /binary , ErrorSuffix /binary >>,
144
156
raise (Location , File , 'Elixir.SyntaxError' , Message );
145
157
@@ -148,14 +160,15 @@ parse_error(Location, File, {ErrorPrefix, ErrorSuffix}, Token) when is_binary(Er
148
160
% % because {char, _, _} is a valid Erlang token for an Erlang char literal. We
149
161
% % want to represent that token as ?a in the error, according to the Elixir
150
162
% % syntax.
151
- parse_error (Location , File , <<" syntax error before: " >>, <<$$ , Char /binary >>) ->
163
+ parse_error (Location , File , <<" syntax error before: " >>, <<$$ , Char /binary >>, _Input ) ->
152
164
Message = <<" syntax error before: ?" , Char /binary >>,
153
165
raise (Location , File , 'Elixir.SyntaxError' , Message );
154
166
155
167
% % Everything else is fine as is
156
- parse_error (Location , File , Error , Token ) when is_binary (Error ), is_binary (Token ) ->
157
- Message = <<Error /binary , Token /binary >>,
158
- raise (Location , File , 'Elixir.SyntaxError' , Message ).
168
+ parse_error (Location , File , Error , Token , {InputString , StartLine , StartColumn }) when is_binary (Error ), is_binary (Token ) ->
169
+ Message = <<Error /binary , Token /binary >>,
170
+ Snippet = snippet (InputString , Location , StartLine , StartColumn ),
171
+ raise (Location , File , 'Elixir.SyntaxError' , Message , Snippet ).
159
172
160
173
parse_erl_term (Term ) ->
161
174
{ok , Tokens , _ } = erl_scan :string (binary_to_list (Term )),
@@ -196,6 +209,9 @@ meta_location(Meta, File) ->
196
209
nil -> [{file , File }, {line , ? line (Meta )}]
197
210
end .
198
211
212
+ raise (Location , File , Kind , Message , Snippet ) when is_binary (File ) ->
213
+ raise (Kind , Message , [{file , File }, {snippet , Snippet } | Location ]).
214
+
199
215
raise (Location , File , Kind , Message ) when is_binary (File ) ->
200
216
raise (Kind , Message , [{file , File } | Location ]).
201
217
0 commit comments