@@ -19,41 +19,169 @@ defmodule Representer do
1919 File . write! ( mapping_output , to_string ( mapping ) )
2020 end
2121
22+ @ spec represent ( code :: String . t ( ) ) :: { Macro . t ( ) , map ( ) }
2223 def represent ( code ) do
2324 { ast , mapping } =
2425 code
2526 |> Code . string_to_quoted! ( )
2627 |> Macro . prewalk ( & add_meta / 1 )
27- |> Macro . prewalk ( Mapping . init ( ) , & define_placeholders / 2 )
28+ # gathering type definitions
29+ |> Macro . prewalk ( Mapping . init ( ) , & define_type_placeholders / 2 )
30+
31+ # replacing type definitions
32+ { ast , mapping } =
33+ Macro . prewalk ( ast , mapping , & use_existing_placeholders / 2 )
34+ |> Macro . prewalk ( & protect_types / 1 )
35+
36+ # gathering function names and variables
37+ { ast , mapping } = Macro . prewalk ( ast , mapping , & define_placeholders / 2 )
2838
2939 ast
30- # names in local function calls can only be exchanged after all names in function definitions were exchanged
40+ # replacing function names and variables
3141 |> Macro . prewalk ( mapping , & use_existing_placeholders / 2 )
3242 |> Macro . prewalk ( & drop_docstring / 1 )
3343 |> Macro . prewalk ( & drop_line_meta / 1 )
3444 |> Macro . prewalk ( & add_parentheses_in_pipes / 1 )
3545 end
3646
37- @ doc """
38- """
39- def add_meta ( { :"::" , _ , [ _ , { :binary , meta , _ } = bin ] = args } = node ) do
47+ # protect string interpolations
48+ defp add_meta ( { :"::" , meta , [ interpolate , { :binary , meta2 , atom } ] } ) do
49+ meta2 = Keyword . put ( meta2 , :visited? , true )
50+ { :"::" , meta , [ interpolate , { :binary , meta2 , atom } ] }
51+ end
52+
53+ defp add_meta ( node ) , do: node
54+
55+ defp define_type_placeholders ( { _ , meta , _ } = node , represented ) do
56+ if meta [ :visited? ] do
57+ { node , represented }
58+ else
59+ do_define_type_placeholders ( node , represented )
60+ end
61+ end
62+
63+ defp define_type_placeholders ( node , represented ) do
64+ do_define_type_placeholders ( node , represented )
65+ end
66+
67+ @ typecreation ~w( type typep opaque) a
68+ @ typespecs ~w( spec callback macrocallback) a
69+ # type creation and type specifications without :when
70+ defp do_define_type_placeholders (
71+ { :@ , meta , [ { create , meta2 , [ { :"::" , meta3 , [ { name , meta4 , args } , definition ] } ] } ] } ,
72+ represented
73+ )
74+ when create in @ typecreation or create in @ typespecs do
75+ { :ok , represented , name } = Mapping . get_placeholder ( represented , name )
76+
77+ { args , represented } =
78+ cond do
79+ is_atom ( args ) ->
80+ { args , represented }
81+
82+ args == [ ] ->
83+ { nil , represented }
84+
85+ create in @ typespecs ->
86+ args = Enum . map ( args , & remove_type_parentheses / 1 )
87+ { args , represented }
88+
89+ create in @ typecreation ->
90+ args = Enum . map ( args , & remove_type_parentheses / 1 )
91+ # when creating types, types may be passed as arguments to be used in the definitions
92+ vars = Enum . map ( args , fn { var , _ , nil } -> var end )
93+ { :ok , represented , _ } = Mapping . get_placeholder ( represented , vars )
94+ { args , represented }
95+ end
96+
97+ definition = Macro . prewalk ( definition , & remove_type_parentheses / 1 )
98+ meta = Keyword . put ( meta , :visited? , true )
99+ meta2 = Keyword . put ( meta2 , :visited? , true )
100+ meta4 = Keyword . put ( meta4 , :visited? , true )
101+
102+ { { :@ , meta , [ { create , meta2 , [ { :"::" , meta3 , [ { name , meta4 , args } , definition ] } ] } ] } ,
103+ represented }
104+ end
105+
106+ # type specifications with :when
107+ defp do_define_type_placeholders (
108+ { :@ , meta ,
109+ [
110+ { create , meta2 ,
111+ [ { :when , meta_when , [ { :"::" , meta3 , [ { name , meta4 , args } , definition ] } , conditions ] } ] }
112+ ] } ,
113+ represented
114+ )
115+ when create in @ typespecs do
116+ { :ok , represented , name } = Mapping . get_placeholder ( represented , name )
117+
118+ { args , represented } =
119+ if is_atom ( args ) do
120+ { args , represented }
121+ else
122+ args = Enum . map ( args , & remove_type_parentheses / 1 )
123+ { args , represented }
124+ end
125+
126+ conditions = Macro . prewalk ( conditions , & remove_type_parentheses / 1 )
127+ # typespecs may receive variable types as arguments if they are constrained by :when
128+ vars = Enum . map ( conditions , fn { var , _type } -> var end )
129+ { :ok , represented , _ } = Mapping . get_placeholder ( represented , vars )
130+
131+ definition = Macro . prewalk ( definition , & remove_type_parentheses / 1 )
40132 meta = Keyword . put ( meta , :visited? , true )
41- bin = Tuple . delete_at ( bin , 1 ) |> Tuple . insert_at ( 1 , meta )
42- args = List . replace_at ( args , 1 , bin )
43- _node = Tuple . delete_at ( node , 2 ) |> Tuple . append ( args )
133+ meta2 = Keyword . put ( meta2 , :visited? , true )
134+ meta4 = Keyword . put ( meta4 , :visited? , true )
135+
136+ { { :@ , meta ,
137+ [
138+ { create , meta2 ,
139+ [ { :when , meta_when , [ { :"::" , meta3 , [ { name , meta4 , args } , definition ] } , conditions ] } ] }
140+ ] } , represented }
141+ end
142+
143+ defp do_define_type_placeholders ( node , represented ) , do: { node , represented }
144+
145+ defp remove_type_parentheses ( { :"::" , meta , [ var , type ] } ) do
146+ { :"::" , meta , [ var , remove_type_parentheses ( type ) ] }
147+ end
148+
149+ defp remove_type_parentheses ( { atom , type } ) when is_atom ( atom ) do
150+ { atom , remove_type_parentheses ( type ) }
151+ end
152+
153+ defp remove_type_parentheses ( { { :. , meta , path } , meta2 , args } ) do
154+ meta2 = Keyword . put ( meta2 , :no_parens , true )
155+ { { :. , meta , path } , meta2 , args }
156+ end
157+
158+ defp remove_type_parentheses ( { type , meta , args } ) when args == [ ] or is_atom ( args ) do
159+ meta = Keyword . put ( meta , :type? , true )
160+ { type , meta , nil }
161+ end
162+
163+ defp remove_type_parentheses ( node ) , do: node
164+
165+ defp protect_types ( { name , meta , args } = node ) do
166+ if meta [ :type? ] do
167+ meta = meta |> Keyword . drop ( [ :type? ] ) |> Keyword . put ( :visited? , true )
168+ { name , meta , args }
169+ else
170+ node
171+ end
44172 end
45173
46- def add_meta ( node ) , do: node
174+ defp protect_types ( node ) , do: node
47175
48- def define_placeholders ( { _ , meta , _ } = node , represented ) do
176+ defp define_placeholders ( { _ , meta , _ } = node , represented ) do
49177 if meta [ :visited? ] do
50178 { node , represented }
51179 else
52180 do_define_placeholders ( node , represented )
53181 end
54182 end
55183
56- def define_placeholders ( node , represented ) do
184+ defp define_placeholders ( node , represented ) do
57185 do_define_placeholders ( node , represented )
58186 end
59187
@@ -87,13 +215,25 @@ defmodule Representer do
87215 # function/macro/guard definition with a guard
88216 [ { name3 , meta3 , args3 } | args2_tail ] = args2
89217
90- { :ok , represented , mapped_name } = Mapping . get_placeholder ( represented , name3 )
218+ { :ok , represented , mapped_name } =
219+ if meta3 [ :visited? ] do
220+ { :ok , represented , name3 }
221+ else
222+ Mapping . get_placeholder ( represented , name3 )
223+ end
224+
91225 meta2 = Keyword . put ( meta2 , :visited? , true )
92226 meta3 = Keyword . put ( meta3 , :visited? , true )
93227
94228 { [ { name , meta2 , [ { mapped_name , meta3 , args3 } | args2_tail ] } | args_tail ] , represented }
95229 else
96- { :ok , represented , mapped_name } = Mapping . get_placeholder ( represented , name )
230+ { :ok , represented , mapped_name } =
231+ if meta2 [ :visited? ] do
232+ { :ok , represented , name }
233+ else
234+ Mapping . get_placeholder ( represented , name )
235+ end
236+
97237 meta2 = Keyword . put ( meta2 , :visited? , true )
98238
99239 { [ { mapped_name , meta2 , args2 } | args_tail ] , represented }
@@ -103,53 +243,61 @@ defmodule Representer do
103243 { node , represented }
104244 end
105245
246+ # module attributes
247+ @ reserved_attributes ~w( after_compile before_compile behaviour impl compile deprecated doc typedoc dialyzer external_resource file moduledoc on_definition on_load vsn derive enforce_keys optional_callbacks) a
248+ defp do_define_placeholders ( { :@ , meta , [ { name , meta2 , value } ] } , represented )
249+ when name not in @ reserved_attributes do
250+ { :ok , represented , name } = Mapping . get_placeholder ( represented , name )
251+ node = { :@ , meta , [ { name , meta2 , value } ] }
252+ { node , represented }
253+ end
254+
106255 # variables
107256 # https://elixir-lang.org/getting-started/meta/quote-and-unquote.html
108257 # "The third element is either a list of arguments for the function call or an atom. When this element is an atom, it means the tuple represents a variable."
109258 @ special_var_names [ :__CALLER__ , :__DIR__ , :__ENV__ , :__MODULE__ , :__STACKTRACE__ , :... , :_ ]
110259 defp do_define_placeholders ( { atom , meta , context } , represented )
111- when is_atom ( atom ) and is_nil ( context ) and atom not in @ special_var_names do
260+ when is_atom ( atom ) and is_atom ( context ) and atom not in @ special_var_names do
112261 { :ok , represented , mapped_term } = Mapping . get_placeholder ( represented , atom )
113262
114263 { { mapped_term , meta , context } , represented }
115264 end
116265
117266 defp do_define_placeholders ( node , represented ) , do: { node , represented }
118267
119- def use_existing_placeholders ( { _ , meta , _ } = node , represented ) do
268+ defp use_existing_placeholders ( { _ , meta , _ } = node , represented ) do
120269 if meta [ :visited? ] do
121270 { node , represented }
122271 else
123272 do_use_existing_placeholders ( node , represented )
124273 end
125274 end
126275
127- def use_existing_placeholders ( node , represented ) do
276+ defp use_existing_placeholders ( node , represented ) do
128277 do_use_existing_placeholders ( node , represented )
129278 end
130279
131280 # module names
132281 defp do_use_existing_placeholders ( { :__aliases__ , meta , module_name } , represented )
133282 when is_list ( module_name ) do
134283 module_name =
135- Enum . map (
136- module_name ,
137- & ( Mapping . get_existing_placeholder ( represented , & 1 ) || & 1 )
138- )
284+ Enum . map ( module_name , & ( Mapping . get_existing_placeholder ( represented , & 1 ) || & 1 ) )
139285
140- meta = Keyword . put ( meta , :visited? , true )
141286 { { :__aliases__ , meta , module_name } , represented }
142287 end
143288
144- # local function calls
145- defp do_use_existing_placeholders ( { atom , meta , context } , represented )
146- when is_atom ( atom ) and is_list ( context ) do
147- placeholder = Mapping . get_existing_placeholder ( represented , atom )
148-
149- # if there is no placeholder for this name, that means it's an imported or a standard library function/macro/special form
150- atom = placeholder || atom
151-
152- { { atom , meta , context } , represented }
289+ # variables or local function calls
290+ defp do_use_existing_placeholders ( { atom , meta , context } = node , represented )
291+ when is_atom ( atom ) do
292+ case Mapping . get_existing_placeholder ( represented , atom ) do
293+ nil ->
294+ # no representation yet, built-in type, imported, standard function/macro/special form...
295+ { node , represented }
296+
297+ atom ->
298+ meta = Keyword . put ( meta , :visited? , true )
299+ { { atom , meta , context } , represented }
300+ end
153301 end
154302
155303 # external function calls
@@ -169,14 +317,10 @@ defmodule Representer do
169317 Mapping . get_existing_placeholder ( represented , function_name )
170318 else
171319 # hack: assuming that if a module has no complete placeholder name, that means it's not being defined in this file
172- # TODO: fix when dealing with aliases
173320 nil
174321 end
175322
176323 function_name = placeholder_function_name || function_name
177-
178- meta2 = Keyword . put ( meta2 , :visited? , true )
179-
180324 { { { :. , meta2 , [ module , function_name ] } , meta , context } , represented }
181325 end
182326
@@ -189,15 +333,18 @@ defmodule Representer do
189333 placeholder_function_name = Mapping . get_existing_placeholder ( represented , function_name )
190334
191335 function_name = placeholder_function_name || function_name
192- meta2 = Keyword . put ( meta2 , :visited? , true )
193- meta3 = Keyword . put ( meta3 , :visited? , true )
194-
195336 { { { :. , meta2 , [ { :__MODULE__ , meta3 , args3 } , function_name ] } , meta , context } , represented }
196337 end
197338
339+ # replace keys in key value pairs
340+ defp do_use_existing_placeholders ( { key , value } , represented ) when is_atom ( key ) do
341+ key = Mapping . get_existing_placeholder ( represented , key ) || key
342+ { { key , value } , represented }
343+ end
344+
198345 defp do_use_existing_placeholders ( node , represented ) , do: { node , represented }
199346
200- def drop_docstring ( { :__block__ , meta , children } ) do
347+ defp drop_docstring ( { :__block__ , meta , children } ) do
201348 children =
202349 children
203350 |> Enum . reject ( fn
@@ -210,18 +357,18 @@ defmodule Representer do
210357 { :__block__ , meta , children }
211358 end
212359
213- def drop_docstring ( node ) , do: node
360+ defp drop_docstring ( node ) , do: node
214361
215- def drop_line_meta ( { marker , metadata , children } ) do
362+ defp drop_line_meta ( { marker , metadata , children } ) do
216363 metadata = Keyword . drop ( metadata , [ :line ] )
217364 { marker , metadata , children }
218365 end
219366
220- def drop_line_meta ( node ) , do: node
367+ defp drop_line_meta ( node ) , do: node
221368
222- def add_parentheses_in_pipes ( { :|> , meta , [ input , { name , meta2 , atom } ] } ) when is_atom ( atom ) do
369+ defp add_parentheses_in_pipes ( { :|> , meta , [ input , { name , meta2 , atom } ] } ) when is_atom ( atom ) do
223370 { :|> , meta , [ input , { name , meta2 , [ ] } ] }
224371 end
225372
226- def add_parentheses_in_pipes ( node ) , do: node
373+ defp add_parentheses_in_pipes ( node ) , do: node
227374end
0 commit comments