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