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

242 lines
8.7 KiB
Erlang

%% Handle code related to rocket args, guard and -> matching
%% for case, fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
%% Get pairs from a clause.
get_pairs(Line, Key, Clauses, S) ->
get_pairs(Line, Key, Clauses, S, false).
get_pairs(Line, Key, Clauses, S, AllowNil) ->
case orddict:find(Key, Clauses) of
{ ok, { '->', _, Pairs } } ->
[{ Key, Left, Right } || { Left, Right } <- Pairs];
{ ok, nil } when AllowNil ->
[];
{ ok, _ } ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
_ ->
[]
end.
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=orddict:new()}),
{ Result, NewS#elixir_scope{context=Context} };
assigns(Fun, Args, S) -> Fun(Args, S).
%% Function for translating a block that is preceeded by an
%% assignment and optional guards. This is used by def* and fn.
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
{ Args, Guards } = extract_guards(BareArgs),
assigns_block(Line, Fun, Args, Exprs, Guards, S).
assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
FArgs = listify(TArgs),
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
FGuards = case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end,
% Uncompact expressions from the block.
case TExprs of
[{ block, _, FExprs }] -> [];
_ -> FExprs = TExprs
end,
{ { clause, Line, FArgs, FGuards, FExprs }, SE }.
% Translate/Extract guards from the given expression.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
extract_guards(Else) -> { Else, [] }.
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
extract_or_clauses(Term, Acc) -> [Term|Acc].
% Extract name and args from the given expression.
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args }.
% Extract guards when it is in the last element of the args
extract_last_guards([]) -> { [], [] };
extract_last_guards(Args) ->
{ Left, Right } = elixir_tree_helpers:split_last(Args),
{ Bare, Guards } = extract_guards(Right),
{ Left ++ [Bare], Guards }.
% Function for translating macros with match style like case and receive.
match(Line, DecoupledClauses, RawS) ->
S = RawS#elixir_scope{clause_vars=orddict:new()},
case DecoupledClauses of
[DecoupledClause] ->
{ TDecoupledClause, TS } = each_clause(Line, DecoupledClause, S),
{ [TDecoupledClause], TS };
_ ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(Line, X, Acc),
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
NewVars = lists:umerge([lists:sort(orddict:fetch_keys(X)) || X <- CV]),
case NewVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun normalize_vars/2, TS, NewVars),
% Defines a tuple that will be used as left side of the match operator
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
% Expand all clauses by adding a match operation at the end
% that assigns variables missing in one clause to the others.
expand_clauses(Line, TClauses, CV, LeftVars, FinalVars, [], FS)
end
end.
expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) || { Var, Kind, _, OldValue } <- FinalVars],
AssignExpr = generate_match(Line, LeftVars, RightVars),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
_ ->
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
end;
false ->
{ [Final,AssignExpr|RawClauseExprs], S }
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Line, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
expand_clauses(_Line, [], [], _LeftVars, _FinalVars, Acc, S) ->
{ lists:reverse(Acc), S }.
% Handle each key/value clause pair and translate them accordingly.
each_clause(Line, { do, [Condition], Expr }, S) ->
assigns_block(Line, fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Line, { 'after', [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
{ TBody, SB } = elixir_translator:translate([Expr], SC),
{ { clause, Line, [TCondition], [], TBody }, SB };
each_clause(Line, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
each_clause(Line, { Key, _, _ }, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid key ~s", [Key]).
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
% existing assignments instead of creating new ones every time.
has_match_tuple({'receive', _, _, _, _}) ->
true;
has_match_tuple({'receive', _, _}) ->
true;
has_match_tuple({'case', _, _, _}) ->
true;
has_match_tuple({match, _, _, _}) ->
true;
has_match_tuple(H) when is_tuple(H) ->
has_match_tuple(tuple_to_list(H));
has_match_tuple(H) when is_list(H) ->
lists:any(fun has_match_tuple/1, H);
has_match_tuple(_) -> false.
% Normalize the given var checking its existence in the scope var dictionary.
normalize_vars({ Var, quoted }, S) ->
normalize_vars(Var, quoted, #elixir_scope.quote_vars, S);
normalize_vars({ Var, Kind }, S) ->
normalize_vars(Var, Kind, #elixir_scope.vars, S).
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
Vars = element(Index, S),
{ { _, _, NewValue }, S1 } = elixir_scope:build_erl_var(0, S),
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
Expr = case orddict:find(Var, Vars) of
{ ok, OldValue } -> { var, 0, OldValue };
error -> { atom, 0, nil }
end,
{ { Var, Kind, NewValue, Expr }, S3 }.
% Normalize a var by checking if it was defined in the clause.
% If so, use it, otherwise use from main scope.
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
case orddict:find({ Var, Kind }, ClauseVars) of
{ ok, ClauseValue } -> { var, 0, ClauseValue };
error -> OldValue
end.
%% generate_match
generate_match(Line, [Left], [Right]) ->
{ match, Line, Left, Right };
generate_match(Line, LeftVars, RightVars) ->
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
%% Listify
listify(Expr) when not is_list(Expr) -> [Expr];
listify(Expr) -> Expr.