11-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
44
55-include (" elixir.hrl" ).
66-define (defs (Kind ), Kind == def ; Kind == defp ; Kind == defmacro ; Kind == defmacrop ; Kind == '@' ).
77-define (lexical (Kind ), Kind == import ; Kind == alias ; Kind == require ).
88-compile ({inline , [keyfind / 2 , keystore / 3 , keydelete / 2 , keynew / 3 , do_tuple_linify / 5 ]}).
99
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+
1069% % Apply the line from site call on quoted contents.
1170% % Receives a Key to look for the default line as argument.
1271linify (0 , _Key , Exprs ) ->
@@ -164,26 +223,31 @@ escape(Expr, Kind, Unquote) ->
164223
165224% % Quotes an expression and return its quoted Elixir AST.
166225
167- quote (_Meta , {unquote_splicing , _ , [_ ]}, _Binding , # elixir_quote {unquote = true }, _ ) ->
226+ quote (_Meta , {unquote_splicing , _ , [_ ]}, _Binding , # elixir_quote {unquote = true }, _ , _ ) ->
168227 argument_error (<<" unquote_splicing only works inside arguments and block contexts, "
169228 " wrap it in parens if you want it to work with one-liners" >>);
170229
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 ) ->
175231 Context = Q # elixir_quote .context ,
176- VarMeta = [Pair || {K , _ } = Pair <- Meta , K == counter ],
177232
178- Vars = [ {'{}' , [],
179- [ '=' , [], [
180- {'{}' , [], [K , VarMeta , Context ]},
233+ Vars = [{'{}' , [],
234+ ['=' , [], [
235+ {'{}' , [], [K , Meta , Context ]},
181236 V
182- ] ]
237+ ]]
183238 } || {K , V } <- Binding ],
184239
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 .
187251
188252% % Actual quoting and helpers
189253
@@ -224,11 +288,12 @@ do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=t
224288
225289% % Vars
226290
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 ]};
229294
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 ]} ;
232297
233298% % Unquote
234299
@@ -244,8 +309,11 @@ do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
244309 # elixir_quote {imports_hygiene = true } = Q , E ) when is_atom (F ), is_integer (A ), is_atom (C ) ->
245310 do_quote_fa ('&' , Meta , Args , F , A , Q , E );
246311
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 );
249317
250318% % Two-element tuples
251319
@@ -341,15 +409,8 @@ bad_escape(Arg) ->
341409 " The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, " ,
342410 " PIDs and remote functions in the format &Mod.fun/arity" >>).
343411
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
353414 elixir_dispatch :find_import (Meta , Name , Arity , E ) of
354415 false ->
355416 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,
358419 end ;
359420 Receiver ->
360421 keystore (import , keystore (context , Meta , Q # elixir_quote .context ), Receiver )
361- end ,
422+ end .
423+
424+ % % do_quote_*
362425
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 ),
364429 do_quote_tuple (Annotated , Q , E ).
365430
366431do_quote_call (Left , Meta , Expr , Args , Q , E ) ->
0 commit comments