Files
n8n-openai-adapter/lib/elixir/src/elixir_translator.erl
T

709 lines
24 KiB
Erlang

%% Main entry point for translations. Are macros that cannot be
%% overriden are defined in this file.
-module(elixir_translator).
-export([forms/4, 'forms!'/4]).
-export([translate/2, translate_each/2, translate_arg/2,
translate_args/2, translate_apply/7, translate_fn/3]).
-import(elixir_scope, [umergev/2, umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
forms(String, StartLine, File, Opts) ->
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
{ ok, Tokens } ->
try elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
end;
{ error, { _, _, _ } } = Else -> Else
catch
{ interpolation_error, { _, _, _ } = Tuple} -> { error, Tuple }
end.
'forms!'(String, StartLine, File, Opts) ->
case forms(String, StartLine, File, Opts) of
{ ok, Forms } -> Forms;
{ error, { Line, Error, Token } } -> parse_error(Line, File, Error, Token)
end.
translate(Forms, S) ->
lists:mapfoldl(fun translate_each/2, S, Forms).
%% Those macros are "low-level". They are the basic mechanism
%% that makes the language work and cannot be partially applied
%% nor overwritten.
%% Assignment operator
translate_each({ '=', Meta, [Left, Right] }, S) ->
assert_no_guard_scope(Meta, '=', S),
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
{ { match, ?line(Meta), TLeft, TRight }, SL };
%% Containers
translate_each({ C, _, _ } = Original, S) when C == '[]'; C == '{}'; C == '<<>>' ->
elixir_literal:translate(Original, S);
%% Blocks and scope rewriters
translate_each({ '__block__', Meta, [] }, S) ->
{ { atom, ?line(Meta), nil }, S };
translate_each({ '__block__', _Line, [Arg] }, S) ->
translate_each(Arg, S);
translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
{ { block, ?line(Meta), TArgs }, NS };
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
Old = S#elixir_scope.file,
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
{ TExpr, TS#elixir_scope{file=Old} };
%% Erlang op
translate_each({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
{ TExpr, NS } = translate_each(Expr, S),
{ { op, ?line(Meta), convert_op(Op), TExpr }, NS };
translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
{ { op, ?line(Meta), convert_op(Op), TLeft, TRight }, NS };
%% Lexical
translate_each({ alias, Meta, [Ref] }, S) ->
translate_each({ alias, Meta, [Ref,[]] }, S);
translate_each({ alias, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, alias, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
translate_alias(Meta, true, Old, TKV, ST);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
end;
translate_each({ require, Meta, [Ref] }, S) ->
translate_each({ require, Meta, [Ref, []] }, S);
translate_each({ require, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, require, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, ST),
translate_require(Meta, Old, TKV, ST);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
end;
translate_each({ import, Meta, [Left] }, S) ->
translate_each({ import, Meta, [Left, []]}, S);
translate_each({ import, Meta, [Left,Opts] }, S) when is_list(Opts) ->
translate_each({ import, Meta, [default, Left, Opts]}, S);
translate_each({ import, Meta, [Left,Right] }, S) ->
%% Second argument is ambiguous, translate it and take a peek
case translate_each(Right, S) of
{ { atom, _, _ }, _ } ->
translate_each({ import, Meta, [Left, Right, []]}, S);
_ ->
translate_each({ import, Meta, [default, Left, Right]}, S)
end;
translate_each({ import, Meta, [Left, Right, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, import, S),
{ TSelector, SL } = translate_each(Left, S),
{ TRef, SR } = translate_each(Right, SL),
{ TKV, ST } = translate_opts(Meta, import, [as, only, except, warn], no_alias_opts(KV), SR),
Selector = case TSelector of
{ atom, _, SelectorAtom } -> SelectorAtom;
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
end,
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, ST),
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
translate_require(Meta, Old, TKV, SF);
_ ->
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end;
%% Pseudo variables
translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
{ { atom, ?line(Meta), S#elixir_scope.module }, S };
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(S#elixir_scope.file, S);
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(filename:dirname(S#elixir_scope.file), S);
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
{ elixir_tree_helpers:elixir_to_erl(Env), S };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
%% Aliases
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom }, S };
Aliases ->
{ TAliases, SA } = translate_args(Aliases, S),
case lists:all(fun is_atom_tuple/1, TAliases) of
true ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
{ { atom, ?line(Meta), elixir_aliases:concat(Atoms) }, SA };
false ->
Args = [elixir_tree_helpers:list_to_cons(?line(Meta), TAliases)],
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
end
end;
%% Quoting
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{ do, Do } ->
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
false ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
end;
translate_each({ quote, Meta, [_] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, Do) of
{ do, E } -> E;
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding],
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
{ hygiene, List } when is_list(List) ->
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, TKV) of
{ context, Atom } when is_atom(Atom) ->
Atom;
{ context, _ } ->
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
false ->
case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
end
end,
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
{ location, keep } -> { keep, keep };
false -> { nil, nil }
end,
Line = case lists:keyfind(line, 1, TKV) of
{ line, LineValue } -> LineValue;
false -> DefaultLine
end,
File = case lists:keyfind(file, 1, TKV) of
{ file, FileValue } -> FileValue;
false -> DefaultFile
end,
Scope = case File of
keep -> S#elixir_scope.file;
_ -> File
end,
QExprs = if
is_binary(Scope) ->
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
File == nil ->
Exprs;
true ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
end,
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
{ binding, B } when is_list(B) -> { B, false };
false -> { nil, true }
end,
Unquote = case lists:keyfind(unquote, 1, TKV) of
{ unquote, Bool } when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
{ TExprs, TS };
translate_each({ quote, Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
translate_each(Arg, S);
translate_each({ 'var!', Meta, [Arg] }, S) ->
translate_each({ 'var!', Meta, [Arg, nil] }, S);
translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name), is_atom(Atom) ->
translate_each({ 'var!', Meta, [Name, Kind] }, S);
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
end);
translate_each({ 'var!', Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid first argument for var!, expected a var or an atom");
%% Functions
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
assert_no_match_or_guard_scope(Meta, 'fn', S),
translate_fn(Meta, Pairs, S);
%% Comprehensions
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Meta, Kind, Args, S);
%% Super (exceptionally supports partial application)
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S) of
error ->
assert_no_match_or_guard_scope(Meta, super, S),
Module = assert_module_scope(Meta, super, S),
Function = assert_function_scope(Meta, super, S),
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
{ _, Arity } = Function,
{ TArgs, TS } = if
length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
end,
Super = elixir_def_overridable:name(Module, Function),
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
Else -> Else
end;
translate_each({ 'super?', Meta, [] }, S) ->
Module = assert_module_scope(Meta, 'super?', S),
Function = assert_function_scope(Meta, 'super?', S),
Bool = elixir_def_overridable:is_defined(Module, Function),
{ { atom, ?line(Meta), Bool }, S };
%% Variables
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
{ ok, Value } ->
{ { var, Meta, Value }, S };
error ->
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
syntax_error(Meta, S#elixir_scope.file,
"cannot use ^~ts outside of match clauses", [Name]);
translate_each({ '^', Meta, [ Expr ] }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
translate_each({ Name, Meta, [] }, S)
end);
%% Local calls
translate_each({ '->', Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
assert_no_ambiguous_op(Atom, Meta, Args, S),
case elixir_partials:is_sequential(Args) andalso
elixir_dispatch:import_function(Meta, Atom, length(Args), S) of
false ->
case elixir_partials:handle(Original, S) of
error ->
Callback = fun() ->
case S#elixir_scope.context of
match ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
end;
_ ->
translate_local(Meta, Atom, Args, S)
end
end,
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
Else -> Else
end;
Else -> Else
end;
%% Remote calls
translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_atom(Right) ->
{ TLeft, SL } = translate_each(Left, S),
Fun = (element(1, TLeft) == atom) andalso elixir_partials:is_sequential(Args) andalso
elixir_dispatch:require_function(Meta, element(3, TLeft), Right, length(Args), SL),
case Fun of
false ->
case elixir_partials:handle(Original, S) of
error ->
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
Callback = fun() -> translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) end,
case TLeft of
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
case S#elixir_scope.context of
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
_ ->
Callback()
end
end);
_ ->
case S#elixir_scope.context of
Context when Context == match; Context == guard ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
[Right, length(Args), Context]);
_ ->
Callback()
end
end;
Else -> Else
end;
Else -> Else
end;
%% Anonymous function calls
translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ atom, _, Atom } ->
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
_ ->
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) };
Else -> Else
end
end;
%% Invalid calls
translate_each({ Invalid, Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
['Elixir.Macro':to_string(Invalid)]);
%% Literals
translate_each(Literal, S) ->
elixir_literal:translate(Literal, S).
%% Helpers
%% Opts
translate_opts(Meta, Kind, Allowed, Opts, S) ->
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
true -> { Opts, S };
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
end,
validate_opts(Meta, Kind, Allowed, Expanded, TS),
{ Expanded, TS }.
skip_expansion('Elixir.Kernel') -> true;
skip_expansion('Elixir.Kernel.Typespec') -> true;
skip_expansion(_) -> false.
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
literal_opts(_) -> false.
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
[begin
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
%% Quote
expand_quote_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_quote_context(Meta, Alias, Msg, S) ->
case translate_each(Alias, S) of
{ { atom, _, Atom }, _ } ->
Atom;
_ ->
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
%% Require
translate_require(Meta, Old, TKV, S) ->
SF = S#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_alias(Meta, false, Old, TKV, SF).
%% Aliases
translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
New = case lists:keyfind(as, 1, TKV) of
{ as, true } ->
elixir_aliases:last(Old);
{ as, false } ->
Old;
{ as, Atom } when is_atom(Atom) ->
Atom;
false ->
if
IncludeByDefault -> elixir_aliases:last(Old);
true -> Old
end;
_ ->
syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, expected an atom or alias in option :as")
end,
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
true -> ok;
false -> syntax_error(Meta, S#elixir_scope.file,
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
end,
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
false -> KV
end;
no_alias_opts(KV) -> KV.
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
{ '__aliases__', Meta, ['Elixir',H|T] };
no_alias_expansion(Other) ->
Other.
is_atom_tuple({ atom, _, _ }) -> true;
is_atom_tuple(_) -> false.
%% Function
translate_fn(Meta, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
%% Locals
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
translate_local(Meta, Name, Args, S) ->
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
%% Translate args
%% Variables in arguments are not propagated from one
%% argument to the other. For instance:
%%
%% x = 1
%% foo(x = x + 2, x)
%% x
%%
%% Should be the same as:
%%
%% foo(3, 1)
%% 3
%%
%% However, notice that if we are doing an assignment,
%% it behaves the same as translate.
translate_arg(Arg, { Acc, S }) ->
{ TArg, TAcc } = translate_each(Arg, Acc),
{ TArg, { umergec(Acc, TAcc), umergev(S, TAcc) } }.
translate_args(Args, #elixir_scope{context=match} = S) ->
translate(Args, S);
translate_args(Args, S) ->
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
{ TArgs, umergec(SV, SC) }.
%% Translate apply
%% Used by both apply and external function invocation macros.
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) ->
Line = ?line(Meta),
Optimize = case (Args == []) orelse lists:last(Args) of
{ '|', _, _ } -> false;
_ ->
case TRight of
{ atom, _, _ } -> true;
_ -> false
end
end,
case Optimize of
true ->
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, FS };
false ->
{ TArgs, SA } = translate_each(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ ?wrap_call(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
end.
%% __op__ helpers
convert_op('and') -> 'andalso';
convert_op('or') -> 'orelse';
convert_op('and!') -> 'and';
convert_op('or!') -> 'or';
convert_op('!==') -> '=/=';
convert_op('===') -> '=:=';
convert_op('!=') -> '/=';
convert_op('<=') -> '=<';
convert_op('<-') -> '!';
convert_op(Else) -> Else.
assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
case lists:keyfind(ambiguous_op, 1, Meta) of
{ ambiguous_op, Kind } ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
error -> ok;
_ ->
syntax_error(Meta, S#elixir_scope.file, "\"~ts ~ts\" looks like a function call but there is a variable named \"~ts\", "
"please use explicit parenthesis or even spaces", [Name, 'Elixir.Macro':to_string(Arg), Name])
end;
_ -> ok
end;
assert_no_ambiguous_op(_Atom, _Meta, _Args, _S) -> ok.
%% Comprehensions
translate_comprehension(Meta, Kind, Args, S) ->
case elixir_tree_helpers:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
_ ->
syntax_error(Meta, S#elixir_scope.file, "keyword argument :do missing for comprehension ~s", [Kind])
end.
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
translate_each(Expr, S);
translate_comprehension_do(Meta, bc, _Expr, S) ->
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
translate_each(Expr, S).
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(Meta, X, S) ->
{ TX, TS } = translate_each(X, S),
elixir_tree_helpers:convert_to_boolean(?line(Meta), TX, true, false, TS).