Current section
Files
Jump to
Current section
Files
src/otpcl_stdmeta.erl
-module(otpcl_stdmeta).
-export([import/2, eval/2, 'fun'/2, def/2]).
-otpcl_funs([import, eval, 'fun', def]).
%% import
% Import the whole module...
import([Module], State) ->
import([Module, otpcl_funs(Module:module_info(attributes))], State);
% ...if it specifies Made For OTPCLâ„¢ functions
import([Module, {otpcl_funs, Names}], State) ->
import([otpcl, Module, Names], State);
% ...if it doesn't
import([Module, no_otpcl_funs], State) ->
Names = [Name || {Name, _} <- Module:module_info(exports)],
import([erlang, Module, Names], State);
% Import a list of functions from a module
import([Type, Module, [Name|Names]], State) ->
{ok, NewState} = import([Type, Module, Name], State),
import([Type, Module, Names], NewState);
import([otpcl, Module, []], State) ->
{{ok, Module}, State};
% Import a specific function...
import([Module, Name], State) ->
import([erlang, Module, Name], State);
% ...if it's Made For OTPCLâ„¢
import([otpcl, Module, Name], State) ->
'fun'([set, Name, fun Module:Name/2], State);
% ...if it's a normal Erlang function (the default assumption)
import([erlang, Module, Name], State) ->
WrappedFun = fun (Args, S) -> {apply(Module, Name, Args), S} end,
'fun'([set, Name, WrappedFun], State).
otpcl_funs([{otpcl_funs, Names}|_]) ->
{otpcl_funs, Names};
otpcl_funs([{_,_}|Rem]) ->
otpcl_funs(Rem);
otpcl_funs([]) ->
no_otpcl_funs.
%% eval
eval([erlang, Txt], State) when is_binary(Txt) ->
eval([erlang, binary_to_list(Txt)], State);
eval([erlang, Txt], State) ->
{ok, Tokens, _} = erl_scan:string(Txt),
{ok, Parsed} = erl_parse:parse_exprs(Tokens),
{value, Result, _} = erl_eval:exprs(Parsed, []),
{Result, State};
eval([Name, Args], State) ->
{Fun, State} = 'fun'([get, Name], State),
apply(Fun, [Args, State]);
eval([Txt], State) ->
otpcl_eval:eval(Txt, State).
%% This is where the fun begins
'fun'([get, Name], {Funs, Vars}) ->
case maps:find(Name, Funs) of
{ok, Fun} ->
{Fun, {Funs, Vars}};
_ ->
{error, {no_such_fun, Name}, {Funs, Vars}}
end;
'fun'([set, Name, Fun], {Funs, Vars}) when is_function(Fun) ->
{ok, {maps:put(Name, Fun, Funs), Vars}};
'fun'([call, Fun, Args], State) when is_function(Fun) ->
apply(Fun, [Args, State]);
'fun'([call, Name, Args], State) ->
case 'fun'([get, Name], State) of
Err = {error, _, _} ->
Err;
{Fun, State} ->
apply(Fun, [Args, State])
end;
'fun'([wrap, Fun], State) when is_function(Fun) ->
Wrapped = fun (Args, FunState) ->
{apply(Fun, Args), FunState}
end,
{Wrapped, State}.
% def is pretty complicated, since we basically have to write a
% mini-interpreter specifically for parsing the argspecs.
def(Args, State) ->
def(Args, State, []).
% TODO: allow guards
def([Name, <<"">>, Body | Clauses], State, Acc) ->
def([Name, <<"# EMPTY">>, Body | Clauses], State, Acc);
def([Name, ArgSpec, Body | Clauses], State, Acc) ->
{ok, {parsed, program, SubClauses}, []} = otpcl_parse:parse(ArgSpec),
BuiltSCs = [build_subclause(SC, Body) || SC <- SubClauses],
def([Name | Clauses], State, BuiltSCs ++ Acc);
def([Name], State, Acc) ->
Clauses = lists:reverse(Acc),
% FIXME: use a sane line number here instead of defaulting to line
% 0.
Eval = {'fun', 0, {clauses, Clauses}},
{value, Fun, _} = erl_eval:expr(Eval, []),
'fun'([set, Name, Fun], State).
build_subclause({parsed, comment, _}, Body) ->
Args = erl_parse:abstract([]),
{clause, 0, [Args, {var, 0, 'State0'}], [], build_body([], Body)};
build_subclause({parsed, command, Words}, Body) ->
% FIXME: use a sane line number here instead of defaulting to line
% 0.
{Args, Vars} = build_args(Words),
{clause, 0, [Args, {var, 0, 'State0'}], [], build_body(Vars, Body)}.
build_args(Words) ->
build_args(Words, [], []).
build_args([{parsed, var_unquoted, Tokens}|Rem], Acc, Vars) ->
build_args([{parsed, var, Tokens}|Rem], Acc, Vars);
build_args([{parsed, var_braced, Tokens}|Rem], Acc, Vars) ->
build_args([{parsed, var, Tokens}|Rem], Acc, Vars);
build_args([{parsed, var, Tokens}|Rem], Acc, Vars) ->
Var = otpcl_eval:make_atom(Tokens),
build_args(Rem, [{var, 0, Var}|Acc], [Var|Vars]);
% Tuples and lists might have to-be-bound variables inside them, so we
% can't just shove 'em into erl_parse:abstract/1.
build_args([{parsed, tuple, Items}|Rem], Acc, Vars) ->
{ItemArgs, ItemVars} = build_args(Items),
build_args(Rem, [{tuple, 0, ItemArgs}|Acc], ItemVars ++ Vars);
% Of *course* Erlang can't just provide a simple {list,LINE,L} thing;
% it just *has* to be all lispy with its cons cells and such.
build_args([{parsed, list, Items}|Rem], Acc, Vars) ->
build_args([{parsed, list, lists:reverse(Items), {nil,1}}|Rem], Acc, Vars);
build_args([{parsed, list, [I|Items], Conses}|Rem], Acc, Vars) ->
{ConsInner, ConsVars} = build_args(I),
Cons = {cons, 0, ConsInner, Conses},
build_args([{parsed, list, Items, Cons}|Rem], Acc, ConsVars ++ Vars);
build_args([{parsed, list, [], Conses}|Rem], Acc, Vars) ->
build_args(Rem, [Conses|Acc], Vars);
% TODO: do something cool with funcalls in argspecs instead of just
% ignoring them. Maybe they could be used for guards?
build_args([{parsed, funcall, _}|Rem], Acc, Vars) ->
build_args(Rem, Acc, Vars);
build_args([{parsed, comment, _}|Rem], Acc, Vars) ->
build_args(Rem, Acc, Vars);
% All other word types should be safe to blindly interpret and shove
% into the Erlang parse tree we're building.
build_args([Arg = {parsed, _, _}|Rem], Acc, Vars) ->
Interpreted = otpcl_eval:interpret(Arg),
Abstracted = erl_parse:abstract(Interpreted),
build_args(Rem, [Abstracted|Acc], Vars);
% We're done
build_args([], Acc, Vars) ->
{consify(Acc, {nil,0}), Vars};
% Catch-all for anything else.
build_args(Rem, Acc, Vars) ->
{error, invalid_argspec, Rem, Acc, Vars}.
consify([Item|List], Conses) ->
consify(List, {cons, 0, Item, Conses});
consify([], Conses) ->
Conses.
build_body(Vars, Body) ->
build_body(Vars, Body, [], 0).
build_body([Var|Vars], Body, BuiltVars, StateIdx) ->
Left = build_match_left(StateIdx + 1),
Right = build_match_right(Var, StateIdx),
Match = {match, 0, Left, Right},
build_body(Vars, Body, [Match|BuiltVars], StateIdx + 1);
build_body([], Body, BuiltVars, StateIdx) ->
Wrapped = erl_parse:abstract([Body]),
StateN = {var, 0, state_name(StateIdx)},
Mod = {atom, 0, otpcl_stdmeta},
Fun = {atom, 0, eval},
Call = {call, 1, {remote, 1, Mod, Fun}, [Wrapped, StateN]},
lists:reverse([Call|BuiltVars]).
build_match_left(StateIdx) ->
State = {var, 0, state_name(StateIdx)},
Empty = {var, 0, '_'},
{tuple, 0, [Empty, State]}.
build_match_right(VarName, StateIdx) ->
StateName = state_name(StateIdx),
ValCons = {cons, 0, {var, 0, VarName}, {nil, 0}},
NameCons = {cons, 0, {atom, 0, VarName}, ValCons},
StateVar = {var, 0, StateName},
FunRef = {remote, 0, {atom, 0, otpcl_stdlib}, {atom, 0, set}},
Args = [NameCons, StateVar],
{call, 1, FunRef, Args}.
state_name(StateIdx) ->
list_to_atom("State" ++ integer_to_list(StateIdx)).