1
1
-module (elixir_quote ).
2
- -export ([escape /3 , linify /3 , linify_with_context_counter /3 , quote /5 , has_unquotes /1 ]).
3
- -export ([dot /5 , tail_list /3 , list /2 ]). % % Quote callbacks
2
+ -export ([escape /3 , linify /3 , linify_with_context_counter /3 , build / 6 , quote /6 , has_unquotes /1 ]).
3
+ -export ([dot /5 , tail_list /3 , list /2 , validate_runtime / 2 ]). % % Quote callbacks
4
4
5
5
-include (" elixir.hrl" ).
6
6
-define (defs (Kind ), Kind == def ; Kind == defp ; Kind == defmacro ; Kind == defmacrop ; Kind == '@' ).
7
7
-define (lexical (Kind ), Kind == import ; Kind == alias ; Kind == require ).
8
8
-compile ({inline , [keyfind / 2 , keystore / 3 , keydelete / 2 , keynew / 3 , do_tuple_linify / 5 ]}).
9
9
10
+ -record (elixir_quote , {
11
+ line = false ,
12
+ file = nil ,
13
+ context = nil ,
14
+ vars_hygiene = true ,
15
+ aliases_hygiene = true ,
16
+ imports_hygiene = true ,
17
+ unquote = true ,
18
+ generated = false
19
+ }).
20
+
21
+ build (Meta , Line , File , Context , Unquote , Generated ) ->
22
+ Acc0 = [],
23
+ {ELine , Acc1 } = validate_compile (Meta , line , Line , Acc0 ),
24
+ {EFile , Acc2 } = validate_compile (Meta , file , File , Acc1 ),
25
+ {EContext , Acc3 } = validate_compile (Meta , context , Context , Acc2 ),
26
+ validate_runtime (unquote , Unquote ),
27
+ validate_runtime (generated , Generated ),
28
+
29
+ Q = # elixir_quote {
30
+ line = ELine ,
31
+ file = EFile ,
32
+ unquote = Unquote ,
33
+ context = EContext ,
34
+ generated = Generated
35
+ },
36
+
37
+ {Q , Acc3 }.
38
+
39
+ validate_compile (Meta , Key , Value , Acc ) ->
40
+ case is_valid (Key , Value ) of
41
+ true ->
42
+ {Value , Acc };
43
+ false ->
44
+ Var = {Key , Meta , ? MODULE },
45
+ Call = {{'.' , Meta , [? MODULE , validate_runtime ]}, Meta , [Key , Value ]},
46
+ {Var , [{'=' , Meta , [Var , Call ]} | Acc ]}
47
+ end .
48
+
49
+ validate_runtime (Key , Value ) ->
50
+ case is_valid (Key , Value ) of
51
+ true ->
52
+ Value ;
53
+
54
+ false ->
55
+ erlang :error (
56
+ 'Elixir.ArgumentError' :exception (
57
+ <<" invalid value for option :" , (erlang :atom_to_binary (Key , utf8 ))/binary ,
58
+ " in quote, got: " , ('Elixir.Kernel' :inspect (Value ))/binary >>
59
+ )
60
+ )
61
+ end .
62
+
63
+ is_valid (line , Line ) -> is_integer (Line ) orelse is_boolean (Line );
64
+ is_valid (file , File ) -> is_binary (File ) orelse (File == nil );
65
+ is_valid (context , Context ) -> is_atom (Context ) andalso (Context /= nil );
66
+ is_valid (generated , Generated ) -> is_boolean (Generated );
67
+ is_valid (unquote , Unquote ) -> is_boolean (Unquote ).
68
+
10
69
% % Apply the line from site call on quoted contents.
11
70
% % Receives a Key to look for the default line as argument.
12
71
linify (0 , _Key , Exprs ) ->
@@ -164,26 +223,31 @@ escape(Expr, Kind, Unquote) ->
164
223
165
224
% % Quotes an expression and return its quoted Elixir AST.
166
225
167
- quote (_Meta , {unquote_splicing , _ , [_ ]}, _Binding , # elixir_quote {unquote = true }, _ ) ->
226
+ quote (_Meta , {unquote_splicing , _ , [_ ]}, _Binding , # elixir_quote {unquote = true }, _ , _ ) ->
168
227
argument_error (<<" unquote_splicing only works inside arguments and block contexts, "
169
228
" wrap it in parens if you want it to work with one-liners" >>);
170
229
171
- quote (_Meta , Expr , nil , Q , E ) ->
172
- do_quote (Expr , Q , E );
173
-
174
- quote (Meta , Expr , Binding , Q , E ) ->
230
+ quote (Meta , Expr , Binding , Q , Prelude , E ) ->
175
231
Context = Q # elixir_quote .context ,
176
- VarMeta = [Pair || {K , _ } = Pair <- Meta , K == counter ],
177
232
178
- Vars = [ {'{}' , [],
179
- [ '=' , [], [
180
- {'{}' , [], [K , VarMeta , Context ]},
233
+ Vars = [{'{}' , [],
234
+ ['=' , [], [
235
+ {'{}' , [], [K , Meta , Context ]},
181
236
V
182
- ] ]
237
+ ]]
183
238
} || {K , V } <- Binding ],
184
239
185
- TExprs = do_quote (Expr , Q , E ),
186
- {'{}' , [], ['__block__' , [], Vars ++ [TExprs ]]}.
240
+ Quoted = do_quote (Expr , Q , E ),
241
+
242
+ WithVars = case Vars of
243
+ [] -> Quoted ;
244
+ _ -> {'{}' , [], ['__block__' , [], Vars ++ [Quoted ]]}
245
+ end ,
246
+
247
+ case Prelude of
248
+ [] -> WithVars ;
249
+ _ -> {'__block__' , [], Prelude ++ [WithVars ]}
250
+ end .
187
251
188
252
% % Actual quoting and helpers
189
253
@@ -224,11 +288,12 @@ do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=t
224
288
225
289
% % Vars
226
290
227
- do_quote ({Left , Meta , nil }, # elixir_quote {vars_hygiene = true , imports_hygiene = true } = Q , E ) when is_atom (Left ) ->
228
- do_quote_import (Left , Meta , Q # elixir_quote .context , Q , E );
291
+ do_quote ({Name , Meta , nil }, # elixir_quote {vars_hygiene = true , imports_hygiene = true } = Q , E ) when is_atom (Name ) ->
292
+ ImportMeta = import_meta (Meta , Name , 0 , Q , E ),
293
+ {'{}' , [], [Name , meta (ImportMeta , Q ), Q # elixir_quote .context ]};
229
294
230
- do_quote ({Left , Meta , nil }, # elixir_quote {vars_hygiene = true } = Q , E ) when is_atom (Left ) ->
231
- do_quote_tuple ( Left , Meta , Q # elixir_quote .context , Q , E ) ;
295
+ do_quote ({Name , Meta , nil }, # elixir_quote {vars_hygiene = true } = Q , _E ) when is_atom (Name ) ->
296
+ { '{}' , [], [ Name , meta ( Meta , Q ), Q # elixir_quote .context ]} ;
232
297
233
298
% % Unquote
234
299
@@ -244,8 +309,11 @@ do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
244
309
# elixir_quote {imports_hygiene = true } = Q , E ) when is_atom (F ), is_integer (A ), is_atom (C ) ->
245
310
do_quote_fa ('&' , Meta , Args , F , A , Q , E );
246
311
247
- do_quote ({Name , Meta , ArgsOrAtom }, # elixir_quote {imports_hygiene = true } = Q , E ) when is_atom (Name ) ->
248
- do_quote_import (Name , Meta , ArgsOrAtom , Q , E );
312
+ do_quote ({Name , Meta , Args }, # elixir_quote {imports_hygiene = true } = Q , E ) when is_atom (Name ), is_list (Args ) ->
313
+ do_quote_import (Meta , Name , length (Args ), Args , Q , E );
314
+
315
+ do_quote ({Name , Meta , Context }, # elixir_quote {imports_hygiene = true } = Q , E ) when is_atom (Name ), is_atom (Context ) ->
316
+ do_quote_import (Meta , Name , 0 , Context , Q , E );
249
317
250
318
% % Two-element tuples
251
319
@@ -341,15 +409,8 @@ bad_escape(Arg) ->
341
409
" The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, " ,
342
410
" PIDs and remote functions in the format &Mod.fun/arity" >>).
343
411
344
- % % do_quote_*
345
-
346
- do_quote_import (Name , Meta , ArgsOrAtom , # elixir_quote {imports_hygiene = true } = Q , E ) ->
347
- Arity = case is_atom (ArgsOrAtom ) of
348
- true -> 0 ;
349
- false -> length (ArgsOrAtom )
350
- end ,
351
-
352
- NewMeta = case (keyfind (import , Meta ) == false ) andalso
412
+ import_meta (Meta , Name , Arity , Q , E ) ->
413
+ case (keyfind (import , Meta ) == false ) andalso
353
414
elixir_dispatch :find_import (Meta , Name , Arity , E ) of
354
415
false ->
355
416
case (Arity == 1 ) andalso keyfind (ambiguous_op , Meta ) of
@@ -358,9 +419,13 @@ do_quote_import(Name, Meta, ArgsOrAtom, #elixir_quote{imports_hygiene=true} = Q,
358
419
end ;
359
420
Receiver ->
360
421
keystore (import , keystore (context , Meta , Q # elixir_quote .context ), Receiver )
361
- end ,
422
+ end .
423
+
424
+ % % do_quote_*
362
425
363
- Annotated = annotate ({Name , NewMeta , ArgsOrAtom }, Q # elixir_quote .context ),
426
+ do_quote_import (Meta , Name , Arity , ArgsOrAtom , Q , E ) ->
427
+ ImportMeta = import_meta (Meta , Name , Arity , Q , E ),
428
+ Annotated = annotate ({Name , ImportMeta , ArgsOrAtom }, Q # elixir_quote .context ),
364
429
do_quote_tuple (Annotated , Q , E ).
365
430
366
431
do_quote_call (Left , Meta , Expr , Args , Q , E ) ->
0 commit comments