1%% Convenience functions used throughout elixir source code
2%% for ast manipulation and querying.
3-module(elixir_utils).
4-export([get_line/1, split_last/1, noop/0, var_context/2,
5  characters_to_list/1, characters_to_binary/1, relative_to_cwd/1,
6  macro_name/1, returns_boolean/1, caller/4, meta_keep/1,
7  read_file_type/1, read_file_type/2, read_link_type/1, read_posix_mtime_and_size/1,
8  change_posix_time/2, change_universal_time/2,
9  guard_op/2, extract_splat_guards/1, extract_guards/1,
10  erlang_comparison_op_to_elixir/1, erl_fa_to_elixir_fa/2]).
11-include("elixir.hrl").
12-include_lib("kernel/include/file.hrl").
13
14macro_name(Macro) ->
15  list_to_atom("MACRO-" ++ atom_to_list(Macro)).
16
17erl_fa_to_elixir_fa(Name, Arity) ->
18  case atom_to_list(Name) of
19    "MACRO-" ++ Rest -> {list_to_atom(Rest), Arity - 1};
20    _ -> {Name, Arity}
21  end.
22
23guard_op('andalso', 2) ->
24  true;
25guard_op('orelse', 2) ->
26  true;
27guard_op(Op, Arity) ->
28  try erl_internal:op_type(Op, Arity) of
29    arith -> true;
30    comp  -> true;
31    bool  -> true;
32    list  -> false;
33    send  -> false
34  catch
35    _:_ -> false
36  end.
37
38erlang_comparison_op_to_elixir('/=') -> '!=';
39erlang_comparison_op_to_elixir('=<') -> '<=';
40erlang_comparison_op_to_elixir('=:=') -> '===';
41erlang_comparison_op_to_elixir('=/=') -> '!==';
42erlang_comparison_op_to_elixir(Other) -> Other.
43
44var_context(Meta, Kind) ->
45  case lists:keyfind(counter, 1, Meta) of
46    {counter, Counter} -> Counter;
47    false -> Kind
48  end.
49
50% Extract guards
51
52extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
53extract_guards(Else) -> {Else, []}.
54
55extract_or_guards({'when', _, [Left, Right]}) -> [Left | extract_or_guards(Right)];
56extract_or_guards(Term) -> [Term].
57
58% Extract guards when multiple left side args are allowed.
59
60extract_splat_guards([{'when', _, [_ | _] = Args}]) ->
61  {Left, Right} = split_last(Args),
62  {Left, extract_or_guards(Right)};
63extract_splat_guards(Else) ->
64  {Else, []}.
65
66%% No-op function that can be used for stuff like preventing tail-call
67%% optimization to kick in.
68noop() ->
69  ok.
70
71split_last([])           -> {[], []};
72split_last(List)         -> split_last(List, []).
73split_last([H], Acc)     -> {lists:reverse(Acc), H};
74split_last([H | T], Acc) -> split_last(T, [H | Acc]).
75
76read_file_type(File) ->
77  read_file_type(File, []).
78
79read_file_type(File, Opts) ->
80  case file:read_file_info(File, [{time, posix} | Opts]) of
81    {ok, #file_info{type=Type}} -> {ok, Type};
82    {error, _} = Error -> Error
83  end.
84
85read_link_type(File) ->
86  case file:read_link_info(File) of
87    {ok, #file_info{type=Type}} -> {ok, Type};
88    {error, _} = Error -> Error
89  end.
90
91read_posix_mtime_and_size(File) ->
92  case file:read_file_info(File, [raw, {time, posix}]) of
93    {ok, #file_info{mtime=Mtime, size=Size}} -> {ok, Mtime, Size};
94    {error, _} = Error -> Error
95  end.
96
97change_posix_time(Name, Time) when is_integer(Time) ->
98  file:write_file_info(Name, #file_info{mtime=Time}, [raw, {time, posix}]).
99
100change_universal_time(Name, {{Y, M, D}, {H, Min, Sec}}=Time)
101    when is_integer(Y), is_integer(M), is_integer(D),
102         is_integer(H), is_integer(Min), is_integer(Sec) ->
103  file:write_file_info(Name, #file_info{mtime=Time}, [{time, universal}]).
104
105relative_to_cwd(Path) ->
106  try elixir_config:get(relative_paths) of
107    true  -> 'Elixir.Path':relative_to_cwd(Path);
108    false -> Path
109  catch
110    _:_ -> Path
111  end.
112
113characters_to_list(Data) when is_list(Data) ->
114  Data;
115characters_to_list(Data) ->
116  case unicode:characters_to_list(Data) of
117    Result when is_list(Result) -> Result;
118    {error, Encoded, Rest} -> conversion_error(invalid, Encoded, Rest);
119    {incomplete, Encoded, Rest} -> conversion_error(incomplete, Encoded, Rest)
120  end.
121
122characters_to_binary(Data) when is_binary(Data) ->
123  Data;
124characters_to_binary(Data) ->
125  case unicode:characters_to_binary(Data) of
126    Result when is_binary(Result) -> Result;
127    {error, Encoded, Rest} -> conversion_error(invalid, Encoded, Rest);
128    {incomplete, Encoded, Rest} -> conversion_error(incomplete, Encoded, Rest)
129  end.
130
131conversion_error(Kind, Encoded, Rest) ->
132  error('Elixir.UnicodeConversionError':exception([{encoded, Encoded}, {rest, Rest}, {kind, Kind}])).
133
134%% Returns the caller as a stacktrace entry.
135caller(Line, File, nil, _) ->
136  {elixir_compiler_0, '__FILE__', 1, stack_location(Line, File)};
137caller(Line, File, Module, nil) ->
138  {Module, '__MODULE__', 0, stack_location(Line, File)};
139caller(Line, File, Module, {Name, Arity}) ->
140  {Module, Name, Arity, stack_location(Line, File)}.
141
142stack_location(Line, File) ->
143  [{file, elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File))},
144   {line, Line}].
145
146get_line(Opts) when is_list(Opts) ->
147  case lists:keyfind(line, 1, Opts) of
148    {line, Line} when is_integer(Line) -> Line;
149    _ -> 0
150  end.
151
152%% Meta location.
153%%
154%% Macros add a file pair on location keep which we
155%% should take into account for error reporting.
156%%
157%% Returns {binary, integer} on location keep or nil.
158
159meta_keep(Meta) ->
160  case lists:keyfind(keep, 1, Meta) of
161    {keep, {File, Line} = Pair} when is_binary(File), is_integer(Line) ->
162      Pair;
163    _ ->
164      nil
165  end.
166
167%% Boolean checks
168
169returns_boolean(Bool) when is_boolean(Bool) -> true;
170
171returns_boolean({{'.', _, [erlang, Op]}, _, [_]}) when Op == 'not' -> true;
172
173returns_boolean({{'.', _, [erlang, Op]}, _, [_, _]}) when
174  Op == 'and'; Op == 'or'; Op == 'xor';
175  Op == '==';  Op == '/='; Op == '=<';  Op == '>=';
176  Op == '<';   Op == '>';  Op == '=:='; Op == '=/=' -> true;
177
178returns_boolean({{'.', _, [erlang, Op]}, _, [_, Right]}) when
179  Op == 'andalso'; Op == 'orelse' ->
180  returns_boolean(Right);
181
182returns_boolean({{'.', _, [erlang, Fun]}, _, [_]}) when
183  Fun == is_atom;   Fun == is_binary;   Fun == is_bitstring; Fun == is_boolean;
184  Fun == is_float;  Fun == is_function; Fun == is_integer;   Fun == is_list;
185  Fun == is_number; Fun == is_pid;      Fun == is_port;      Fun == is_reference;
186  Fun == is_tuple;  Fun == is_map;      Fun == is_process_alive -> true;
187
188returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _]}) when
189  Fun == is_map_key; Fun == is_function; Fun == is_record -> true;
190
191returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _, _]}) when
192  Fun == function_exported; Fun == is_record -> true;
193
194returns_boolean({'case', _, [_, [{do, Clauses}]]}) ->
195  lists:all(fun
196    ({'->', _, [_, Expr]}) -> returns_boolean(Expr)
197  end, Clauses);
198
199returns_boolean({'cond', _, [[{do, Clauses}]]}) ->
200  lists:all(fun
201    ({'->', _, [_, Expr]}) -> returns_boolean(Expr)
202  end, Clauses);
203
204returns_boolean({'__block__', _, Exprs}) ->
205  returns_boolean(lists:last(Exprs));
206
207returns_boolean(_) -> false.
208