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

572 lines
19 KiB
Erlang

-module(elixir_exp).
-export([expand/2, expand_args/2, expand_arg/2]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
%% =
expand({'=', Meta, [Left, Right]}, E) ->
assert_no_guard_scope(Meta, '=', E),
{ERight, ER} = expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
{{'=', Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
%% Literal operators
expand({'{}', Meta, Args}, E) ->
{EArgs, EA} = expand_args(Args, E),
{{'{}', Meta, EArgs}, EA};
expand({'%{}', Meta, Args}, E) ->
elixir_map:expand_map(Meta, Args, E);
expand({'%', Meta, [Left, Right]}, E) ->
elixir_map:expand_struct(Meta, Left, Right, E);
expand({'<<>>', Meta, Args}, E) ->
elixir_bitstring:expand(Meta, Args, E);
%% Other operators
expand({'__op__', Meta, [_, _] = Args}, E) ->
{EArgs, EA} = expand_args(Args, E),
{{'__op__', Meta, EArgs}, EA};
expand({'__op__', Meta, [_, _, _] = Args}, E) ->
{EArgs, EA} = expand_args(Args, E),
{{'__op__', Meta, EArgs}, EA};
expand({'->', Meta, _Args}, E) ->
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
%% __block__
expand({'__block__', _Meta, []}, E) ->
{nil, E};
expand({'__block__', _Meta, [Arg]}, E) ->
expand(Arg, E);
expand({'__block__', Meta, Args}, E) when is_list(Args) ->
{EArgs, EA} = expand_many(Args, E),
{{'__block__', Meta, EArgs}, EA};
%% __aliases__
expand({'__aliases__', _, _} = Alias, E) ->
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
Receiver when is_atom(Receiver) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{Receiver, E};
Aliases ->
{EAliases, EA} = expand_args(Aliases, E),
case lists:all(fun is_atom/1, EAliases) of
true ->
Receiver = elixir_aliases:concat(EAliases),
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{Receiver, EA};
false ->
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
end
end;
%% alias
expand({alias, Meta, [Ref]}, E) ->
expand({alias, Meta, [Ref,[]]}, E);
expand({alias, Meta, [Ref, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, alias, E),
{ERef, ER} = expand(Ref, E),
{EKV, ET} = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
if
is_atom(ERef) ->
{{alias, Meta, [ERef, EKV]},
expand_alias(Meta, true, ERef, EKV, ET)};
true ->
compile_error(Meta, E#elixir_env.file,
"invalid argument for alias, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
expand({require, Meta, [Ref]}, E) ->
expand({require, Meta, [Ref, []]}, E);
expand({require, Meta, [Ref, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, require, E),
{ERef, ER} = expand(Ref, E),
{EKV, ET} = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
{{require, Meta, [ERef, EKV]},
expand_require(Meta, ERef, EKV, ET)};
true ->
compile_error(Meta, E#elixir_env.file,
"invalid argument for require, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
expand({import, Meta, [Left]}, E) ->
expand({import, Meta, [Left, []]}, E);
expand({import, Meta, [Ref, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, import, E),
{ERef, ER} = expand(Ref, E),
{EKV, ET} = expand_opts(Meta, import, [only, except, warn], KV, ER),
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
{Functions, Macros} = elixir_import:import(Meta, ERef, EKV, ET),
{{import, Meta, [ERef, EKV]},
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros})};
true ->
compile_error(Meta, E#elixir_env.file,
"invalid argument for import, expected a compile time atom or alias, got: ~ts",
['Elixir.Kernel':inspect(ERef)])
end;
%% Pseudo vars
expand({'__MODULE__', _, Atom}, E) when is_atom(Atom) ->
{E#elixir_env.module, E};
expand({'__DIR__', _, Atom}, E) when is_atom(Atom) ->
{filename:dirname(E#elixir_env.file), E};
expand({'__CALLER__', _, Atom} = Caller, E) when is_atom(Atom) ->
{Caller, E};
expand({'__ENV__', Meta, Atom}, E) when is_atom(Atom) ->
Env = elixir_env:env_to_ex({?line(Meta), E}),
{{'{}', [], tuple_to_list(Env)}, E};
expand({{'.', DotMeta, [{'__ENV__', Meta, Atom}, Field]}, CallMeta, []}, E) when is_atom(Atom), is_atom(Field) ->
Env = elixir_env:env_to_ex({?line(Meta), E}),
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
true -> {Env:Field(), E};
false -> {{{'.', DotMeta, [{'{}', [], tuple_to_list(Env)}, Field]}, CallMeta, []}, E}
end;
%% Quote
expand({Unquote, Meta, [_]}, E) when Unquote == unquote; Unquote == unquote_splicing ->
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
expand({quote, Meta, [Opts]}, E) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{do, Do} ->
expand({quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]]}, E);
false ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
end;
expand({quote, Meta, [_]}, E) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, Do) of
{do, Expr} -> Expr;
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
{hygiene, List} when is_list(List) ->
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, EKV) of
{context, Atom} when is_atom(Atom) ->
Atom;
{context, Ctx} ->
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, "
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
false ->
case E#elixir_env.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},
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
Line = proplists:get_value(line, EKV, false),
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
{bind_quoted, BQ} -> {BQ, false};
false -> {nil, true}
end,
Unquote = case lists:keyfind(unquote, 1, EKV) of
{unquote, Bool} when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET);
expand({quote, Meta, [_, _]}, E) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
%% Functions
expand({'&', _, [Arg]} = Original, E) when is_integer(Arg) ->
{Original, E};
expand({'&', Meta, [Arg]}, E) ->
assert_no_match_or_guard_scope(Meta, '&', E),
case elixir_fn:capture(Meta, Arg, E) of
{local, Fun, Arity} ->
{{'&', Meta, [{'/', [], [{Fun, [], nil}, Arity]}]}, E};
{expanded, Expr, EE} ->
expand(Expr, EE)
end;
expand({fn, Meta, Pairs}, E) ->
assert_no_match_or_guard_scope(Meta, fn, E),
elixir_fn:expand(Meta, Pairs, E);
%% Case/Receive/Try
expand({'case', Meta, [Expr, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'case', E),
{EExpr, EE} = expand(Expr, E),
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
{{'case', Meta, [EExpr, EClauses]}, EC};
expand({'receive', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'receive', E),
{EClauses, EC} = elixir_exp_clauses:'receive'(Meta, KV, E),
{{'receive', Meta, [EClauses]}, EC};
expand({'try', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'try', E),
{EClauses, EC} = elixir_exp_clauses:'try'(Meta, KV, E),
{{'try', Meta, [EClauses]}, EC};
%% Comprehensions
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
elixir_errors:deprecation(Meta, E#elixir_env.file, "~ts is deprecated, please use for comprehensions instead", [Kind]),
expand_comprehension(Meta, Kind, Args, E);
expand({for, Meta, Args}, E) when is_list(Args) ->
elixir_for:expand(Meta, Args, E);
%% Super
expand({super, Meta, Args}, E) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, super, E),
{EArgs, EA} = expand_args(Args, E),
{{super, Meta, EArgs}, EA};
%% Vars
expand({'^', Meta, [Arg]}, #elixir_env{context=match} = E) ->
case expand(Arg, E) of
{{Name, _, Kind} = EArg, EA} when is_atom(Name), is_atom(Kind) ->
{{'^', Meta, [EArg]}, EA};
_ ->
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
end;
expand({'^', Meta, [Arg]}, E) ->
compile_error(Meta, E#elixir_env.file,
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
{Var, E};
expand({Name, Meta, Kind} = Var, #elixir_env{context=match, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
Pair = {Name, var_kind(Meta, Kind)},
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
true -> ordsets:add_element(Pair, Export);
false -> Export
end,
{Var, E#elixir_env{vars=NewVars, export_vars=NewExport}};
expand({Name, Meta, Kind} = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
case lists:member({Name, var_kind(Meta, Kind)}, Vars) of
true ->
{Var, E};
false ->
VarMeta = lists:keyfind(var, 1, Meta),
if
VarMeta == {var, true} ->
Extra = case Kind of
nil -> "";
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
end,
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
true ->
expand({Name, Meta, []}, E)
end
end;
%% Local calls
expand({Atom, Meta, Args}, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
assert_no_ambiguous_op(Atom, Meta, Args, E),
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
expand_local(Meta, Atom, Args, E)
end);
%% Remote calls
expand({{'.', DotMeta, [Left, Right]}, Meta, Args}, E)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ELeft, EL} = expand(Left, E),
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
end);
%% Anonymous calls
expand({{'.', DotMeta, [Expr]}, Meta, Args}, E) when is_list(Args) ->
{EExpr, EE} = expand(Expr, E),
if
is_atom(EExpr) ->
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
true ->
{EArgs, EA} = expand_args(Args, elixir_env:mergea(E, EE)),
{{{'.', DotMeta, [EExpr]}, Meta, EArgs}, elixir_env:mergev(EE, EA)}
end;
%% Invalid calls
expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
['Elixir.Macro':to_string(Invalid)]);
expand({_, _, _} = Tuple, E) ->
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
%% Literals
expand({Left, Right}, E) ->
{[ELeft, ERight], EE} = expand_args([Left, Right], E),
{{ELeft, ERight}, EE};
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
expand_list(List, fun expand/2, E, []);
expand(List, E) when is_list(List) ->
{EArgs, {EC, EV}} = expand_list(List, fun expand_arg/2, {E, E}, []),
{EArgs, elixir_env:mergea(EV, EC)};
expand(Function, E) when is_function(Function) ->
case (erlang:fun_info(Function, type) == {type, external}) andalso
(erlang:fun_info(Function, env) == {env, []}) of
true ->
{Function, E};
false ->
compile_error([{line,0}], E#elixir_env.file,
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
end;
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
{Other, E};
expand(Other, E) ->
compile_error([{line,0}], E#elixir_env.file,
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
%% Helpers
expand_list([{'|', Meta, [_, _] = Args}], Fun, Acc, List) ->
{EArgs, EAcc} = lists:mapfoldl(Fun, Acc, Args),
expand_list([], Fun, EAcc, [{'|', Meta, EArgs}|List]);
expand_list([H|T], Fun, Acc, List) ->
{EArg, EAcc} = Fun(H, Acc),
expand_list(T, Fun, EAcc, [EArg|List]);
expand_list([], _Fun, Acc, List) ->
{lists:reverse(List), Acc}.
expand_many(Args, E) ->
lists:mapfoldl(fun expand/2, E, 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, lexical information is.
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
{Arg, Acc};
expand_arg(Arg, {Acc1, Acc2}) ->
{EArg, EAcc} = expand(Arg, Acc1),
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
expand_args(Args, #elixir_env{context=match} = E) ->
expand_many(Args, E);
expand_args(Args, E) ->
{EArgs, {EC, EV}} = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
{EArgs, elixir_env:mergea(EV, EC)}.
%% Match/var helpers
var_kind(Meta, Kind) ->
case lists:keyfind(counter, 1, Meta) of
{counter, Counter} -> Counter;
false -> Kind
end.
%% Locals
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
case lists:keyfind(ambiguous_op, 1, Meta) of
{ambiguous_op, Kind} ->
case lists:member({Name, Kind}, E#elixir_env.vars) of
true ->
compile_error(Meta, E#elixir_env.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]);
false ->
ok
end;
_ ->
ok
end;
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
ok.
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
{EArgs, EA} = expand_args(Args, E),
{{Name, Meta, EArgs}, EA};
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
elixir_locals:record_local({Name, length(Args)}, Module, Function),
{EArgs, EA} = expand_args(Args, E),
{{Name, Meta, EArgs}, EA};
expand_local(Meta, Name, Args, E) ->
expand({{'.', Meta, [E#elixir_env.local, Name]}, Meta, Args}, E).
%% Remote
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
if
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
true -> ok
end,
{EArgs, EA} = expand_args(Args, E),
{{{'.', DotMeta, [Receiver, Right]}, Meta, EArgs}, elixir_env:mergev(EL, EA)}.
%% Lexical helpers
expand_opts(Meta, Kind, Allowed, Opts, E) ->
{EOpts, EE} = expand(Opts, E),
validate_opts(Meta, Kind, Allowed, EOpts, EE),
{EOpts, EE}.
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
[begin
compile_error(Meta, E#elixir_env.file,
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
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.
expand_require(Meta, Ref, KV, E) ->
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
expand_alias(Meta, false, Ref, KV, RE).
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
%% Add the alias to context_modules if defined is true.
%% This is used by defmodule in order to store the defined
%% module in context modules.
NewContext =
case lists:keyfind(defined, 1, Meta) of
{defined, Mod} when is_atom(Mod) -> [Mod|Context];
false -> Context
end,
{Aliases, MacroAliases} = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
expand_as({as, true}, _Meta, _IncludeByDefault, Ref, _E) ->
elixir_aliases:last(Ref);
expand_as({as, false}, _Meta, _IncludeByDefault, Ref, _E) ->
Ref;
expand_as({as, Atom}, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
case length(string:tokens(atom_to_list(Atom), ".")) of
1 -> compile_error(Meta, E#elixir_env.file,
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
2 -> Atom;
_ -> compile_error(Meta, E#elixir_env.file,
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
end;
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
if IncludeByDefault -> elixir_aliases:last(Ref);
true -> Ref
end;
expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
compile_error(Meta, E#elixir_env.file,
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
%% Comprehensions
expand_comprehension(Meta, Kind, Args, E) ->
case elixir_utils:split_last(Args) of
{Cases, [{do,Expr}]} ->
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
{EExpr, _} = expand(Expr, EC),
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
_ ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
end.
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
{ERight, ER} = expand(Right, E),
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
expand_comprehension_clause(X, E) ->
expand(X, E).
%% Assertions
assert_no_match_or_guard_scope(Meta, Kind, E) ->
assert_no_match_scope(Meta, Kind, E),
assert_no_guard_scope(Meta, Kind, E).
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
compile_error(Meta, File, "invalid expression in match");
assert_no_match_scope(_Meta, _Kind, _E) -> [].
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
compile_error(Meta, File, "invalid expression in guard");
assert_no_guard_scope(_Meta, _Kind, _E) -> [].