|
|
@ -88,27 +88,17 @@ |
|
|
|
qtree :: term() |
|
|
|
}). |
|
|
|
|
|
|
|
-type syntaxTree() :: erl_syntax:syntaxTree(). |
|
|
|
|
|
|
|
-record(state, { |
|
|
|
event = undefined :: syntaxTree(), |
|
|
|
fields = [] :: [{atom(), syntaxTree()}], |
|
|
|
fieldc = 0 :: non_neg_integer(), |
|
|
|
paramvars = [] :: [{term(), syntaxTree()}], |
|
|
|
paramstab = undefined :: ets:tid() |
|
|
|
}). |
|
|
|
|
|
|
|
-type nextFun() :: fun((#state{}) -> [syntaxTree()]). |
|
|
|
-type q() :: tuple(). |
|
|
|
|
|
|
|
-spec lt(atom(), term()) -> q(). |
|
|
|
lt(Key, Term) when is_atom(Key) -> {Key, '<', Term}. |
|
|
|
-spec lt(atom(), term()) -> glc_ops:op(). |
|
|
|
lt(Key, Term) -> |
|
|
|
glc_ops:lt(Key, Term). |
|
|
|
|
|
|
|
-spec eq(atom(), term()) -> q(). |
|
|
|
eq(Key, Term) when is_atom(Key) -> {Key, '=', Term}. |
|
|
|
-spec eq(atom(), term()) -> glc_ops:op(). |
|
|
|
eq(Key, Term) -> |
|
|
|
glc_ops:eq(Key, Term). |
|
|
|
|
|
|
|
-spec gt(atom(), term()) -> q(). |
|
|
|
gt(Key, Term) when is_atom(Key) -> {Key, '>', Term}. |
|
|
|
-spec gt(atom(), term()) -> glc_ops:op(). |
|
|
|
gt(Key, Term) -> |
|
|
|
glc_ops:gt(Key, Term). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Filter the input using multiple filters. |
|
|
@ -117,11 +107,10 @@ gt(Key, Term) when is_atom(Key) -> {Key, '>', Term}. |
|
|
|
%% in the list must hold for the input event. The list is expected to |
|
|
|
%% be a non-empty list. If the list of filters is an empty list a `badarg' |
|
|
|
%% error will be thrown. |
|
|
|
-spec all([q()]) -> q(). |
|
|
|
all([_|_]=Conds) -> |
|
|
|
{all, Conds}; |
|
|
|
all(Other) -> |
|
|
|
erlang:error(badarg, [Other]). |
|
|
|
-spec all([glc_ops:op()]) -> glc_ops:op(). |
|
|
|
all(Filters) -> |
|
|
|
glc_ops:all(Filters). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Filter the input using one of multiple filters. |
|
|
|
%% |
|
|
@ -129,31 +118,40 @@ all(Other) -> |
|
|
|
%% in the list must hold for the input event. The list is expected to be |
|
|
|
%% a non-empty list. If the list of filters is an empty list a `badarg' |
|
|
|
%% error will be thrown. |
|
|
|
-spec any([q()]) -> q(). |
|
|
|
any([_|_]=Conds) -> |
|
|
|
{any, Conds}; |
|
|
|
any(Other) -> |
|
|
|
erlang:error(badarg, [Other]). |
|
|
|
-spec any([glc_ops:op()]) -> glc_ops:op(). |
|
|
|
any(Filters) -> |
|
|
|
glc_ops:any(Filters). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Always return `true' or `false'. |
|
|
|
-spec null(boolean()) -> q(). |
|
|
|
null(Result) when is_boolean(Result) -> |
|
|
|
{null, Result}. |
|
|
|
-spec null(boolean()) -> glc_ops:op(). |
|
|
|
null(Result) -> |
|
|
|
glc_ops:null(Result). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Apply a function to each output of a query. |
|
|
|
%% |
|
|
|
%% Updating the output action of a query finalizes it. Attempting |
|
|
|
%% to use a finalized query to construct a new query will result |
|
|
|
%% in a `badarg' error. |
|
|
|
-spec with(q(), fun((gre:event()) -> term())) -> q(). |
|
|
|
with(Query, Fun) when is_function(Fun, 1) -> |
|
|
|
{with, Query, Fun}. |
|
|
|
-spec with(glc_ops:op(), fun((gre:event()) -> term())) -> glc_ops:op(). |
|
|
|
with(Query, Action) -> |
|
|
|
glc_ops:with(Query, Action). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Return a union of multiple queries. |
|
|
|
-spec union([q()]) -> q(). |
|
|
|
%% |
|
|
|
%% The union of multiple queries is the equivalent of executing multiple |
|
|
|
%% queries separately on the same input event. The advantage is that filter |
|
|
|
%% conditions that are common to all or some of the queries only need to |
|
|
|
%% be tested once. |
|
|
|
%% |
|
|
|
%% All queries are expected to be valid and have an output action other |
|
|
|
%% than the default which is `output'. If these expectations don't hold |
|
|
|
%% a `badarg' error will be thrown. |
|
|
|
-spec union([glc_ops:op()]) -> glc_ops:op(). |
|
|
|
union(Queries) -> |
|
|
|
{union, Queries}. |
|
|
|
glc_ops:union(Queries). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Compile a query to a module. |
|
|
@ -164,10 +162,10 @@ union(Queries) -> |
|
|
|
-spec compile(atom(), list()) -> {ok, atom()}. |
|
|
|
compile(Module, Query) -> |
|
|
|
{ok, ModuleData} = module_data(Query), |
|
|
|
{ok, forms, Forms} = abstract_module(Module, ModuleData), |
|
|
|
{ok, Module, Binary} = compile_forms(Forms, []), |
|
|
|
{ok, loaded, Module} = load_binary(Module, Binary), |
|
|
|
{ok, Module}. |
|
|
|
case glc_code:compile(Module, ModuleData) of |
|
|
|
{ok, Module} -> |
|
|
|
{ok, Module} |
|
|
|
end. |
|
|
|
|
|
|
|
%% @doc Handle an event using a compiled query. |
|
|
|
%% |
|
|
@ -206,6 +204,7 @@ module_data(Query) -> |
|
|
|
{ok, #module{'query'=Query, tables=Tables, qtree=Query2}}. |
|
|
|
|
|
|
|
|
|
|
|
%% @todo Move comment. |
|
|
|
%% @private Map a query to a simplified query tree term. |
|
|
|
%% |
|
|
|
%% The simplified query tree is used to combine multiple queries into one |
|
|
@ -228,324 +227,6 @@ module_data(Query) -> |
|
|
|
%% |
|
|
|
%% If an event must be selected based on the runtime state of an event handler |
|
|
|
%% this must be done in the body of the handler. |
|
|
|
-type qcond() :: |
|
|
|
{atom(), '<', term()} | |
|
|
|
{atom(), '=', term()} | |
|
|
|
{atom(), '>', term()} | |
|
|
|
{any, [qcond()]} | |
|
|
|
{all, [qcond()]}. |
|
|
|
|
|
|
|
|
|
|
|
%% abstract code geneation functions |
|
|
|
|
|
|
|
%% @private Generate an abstract dispatch module. |
|
|
|
-spec abstract_module(atom(), #module{}) -> {ok, forms, list()}. |
|
|
|
abstract_module(Module, Data) -> |
|
|
|
Forms = [erl_syntax:revert(E) || E <- abstract_module_(Module, Data)], |
|
|
|
case lists:keyfind(errors, 1, erl_syntax_lib:analyze_forms(Forms)) of |
|
|
|
false -> {ok, forms, Forms}; |
|
|
|
{_, []} -> {ok, forms, Forms}; |
|
|
|
{_, [_|_]}=Errors -> Errors |
|
|
|
end. |
|
|
|
|
|
|
|
%% @private Generate an abstract dispatch module. |
|
|
|
-spec abstract_module_(atom(), #module{}) -> [erl_syntax:syntaxTree()]. |
|
|
|
abstract_module_(Module, #module{tables=Tables, qtree=Tree}=Data) -> |
|
|
|
{_, ParamsTable} = lists:keyfind(params, 1, Tables), |
|
|
|
AbstractMod = [ |
|
|
|
%% -module(Module) |
|
|
|
erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]), |
|
|
|
%% -export([ |
|
|
|
erl_syntax:attribute( |
|
|
|
erl_syntax:atom(export), |
|
|
|
[erl_syntax:list([ |
|
|
|
%% info/1 |
|
|
|
erl_syntax:arity_qualifier( |
|
|
|
erl_syntax:atom(info), |
|
|
|
erl_syntax:integer(1)), |
|
|
|
%% table/1 |
|
|
|
erl_syntax:arity_qualifier( |
|
|
|
erl_syntax:atom(table), |
|
|
|
erl_syntax:integer(1)), |
|
|
|
%% handle/1 |
|
|
|
erl_syntax:arity_qualifier( |
|
|
|
erl_syntax:atom(handle), |
|
|
|
erl_syntax:integer(1))])]), |
|
|
|
%% ]). |
|
|
|
%% info(Name) -> Term. |
|
|
|
erl_syntax:function( |
|
|
|
erl_syntax:atom(info), |
|
|
|
abstract_info(Data) ++ |
|
|
|
[erl_syntax:clause( |
|
|
|
[erl_syntax:underscore()], none, |
|
|
|
[abstract_apply(erlang, error, [erl_syntax:atom(badarg)])])]), |
|
|
|
%% table(Name) -> ets:tid(). |
|
|
|
erl_syntax:function( |
|
|
|
erl_syntax:atom(table), |
|
|
|
abstract_tables(Tables) ++ |
|
|
|
[erl_syntax:clause( |
|
|
|
[erl_syntax:underscore()], none, |
|
|
|
[abstract_apply(erlang, error, [erl_syntax:atom(badarg)])])]), |
|
|
|
%% handle(Event) - entry function |
|
|
|
erl_syntax:function( |
|
|
|
erl_syntax:atom(handle), |
|
|
|
[erl_syntax:clause([erl_syntax:variable("Event")], none, |
|
|
|
[abstract_count(input), |
|
|
|
erl_syntax:application(none, |
|
|
|
erl_syntax:atom(handle_), [erl_syntax:variable("Event")])])]), |
|
|
|
%% input_(Node, App, Pid, Tags, Values) - filter roots |
|
|
|
erl_syntax:function( |
|
|
|
erl_syntax:atom(handle_), |
|
|
|
[erl_syntax:clause([erl_syntax:variable("Event")], none, |
|
|
|
abstract_filter(Tree, #state{ |
|
|
|
event=erl_syntax:variable("Event"), |
|
|
|
paramstab=ParamsTable}))]) |
|
|
|
], |
|
|
|
%% Transform Term -> Key to Key -> Term |
|
|
|
ParamsList = [{K, V} || {V, K} <- ets:tab2list(ParamsTable)], |
|
|
|
ets:delete_all_objects(ParamsTable), |
|
|
|
ets:insert(ParamsTable, ParamsList), |
|
|
|
AbstractMod. |
|
|
|
|
|
|
|
%% @private Return the clauses of the table/1 function. |
|
|
|
abstract_tables(Tables) -> |
|
|
|
[erl_syntax:clause( |
|
|
|
[erl_syntax:abstract(K)], none, |
|
|
|
[erl_syntax:abstract(V)]) |
|
|
|
|| {K, V} <- Tables]. |
|
|
|
|
|
|
|
%% @private Return the clauses of the info/1 function. |
|
|
|
abstract_info(#module{'query'=Query}) -> |
|
|
|
[erl_syntax:clause([erl_syntax:abstract(K)], none, V) |
|
|
|
|| {K, V} <- [ |
|
|
|
{'query', abstract_query(Query)}, |
|
|
|
{input, abstract_getcount(input)}, |
|
|
|
{filter, abstract_getcount(filter)}, |
|
|
|
{output, abstract_getcount(output)} |
|
|
|
]]. |
|
|
|
|
|
|
|
%% @private Return the original query as an expression. |
|
|
|
abstract_query({with, _, _}) -> |
|
|
|
[erl_syntax:abstract([])]; |
|
|
|
abstract_query(Query) -> |
|
|
|
[erl_syntax:abstract(Query)]. |
|
|
|
|
|
|
|
|
|
|
|
%% @private Return a list of expressions to apply a filter. |
|
|
|
%% @todo Allow mulitple functions to be specified using `with/2'. |
|
|
|
-spec abstract_filter(q(), #state{}) -> [syntaxTree()]. |
|
|
|
abstract_filter({with, Cond, Fun}, State) -> |
|
|
|
abstract_filter_(Cond, |
|
|
|
_OnMatch=fun(State2) -> |
|
|
|
[abstract_count(output)] ++ abstract_with(Fun, State2) end, |
|
|
|
_OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State); |
|
|
|
abstract_filter(Cond, State) -> |
|
|
|
abstract_filter_(Cond, |
|
|
|
_OnMatch=fun(_State2) -> [abstract_count(output)] end, |
|
|
|
_OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State). |
|
|
|
|
|
|
|
%% @private Return a list of expressions to apply a filter. |
|
|
|
%% A filter expects two continuation functions which generates the expressions |
|
|
|
%% to apply when the filter matches or fails to match. The state passed to the |
|
|
|
%% functions will be contain all variable bindings to previously accessed |
|
|
|
%% fields and parameters. |
|
|
|
-spec abstract_filter_(qcond(), nextFun(), nextFun(), #state{}) -> |
|
|
|
syntaxTree(). |
|
|
|
abstract_filter_({null, true}, OnMatch, _OnNomatch, State) -> |
|
|
|
OnMatch(State); |
|
|
|
abstract_filter_({null, false}, _OnMatch, OnNomatch, State) -> |
|
|
|
OnNomatch(State); |
|
|
|
abstract_filter_({Key, Op, Value}, OnMatch, OnNomatch, State) |
|
|
|
when Op =:= '>'; Op =:= '='; Op =:= '<' -> |
|
|
|
Op2 = case Op of '=' -> '=:='; Op -> Op end, |
|
|
|
abstract_opfilter(Key, Op2, Value, OnMatch, OnNomatch, State); |
|
|
|
abstract_filter_({'any', Conds}, OnMatch, OnNomatch, State) -> |
|
|
|
abstract_any(Conds, OnMatch, OnNomatch, State); |
|
|
|
abstract_filter_({'all', Conds}, OnMatch, OnNomatch, State) -> |
|
|
|
abstract_all(Conds, OnMatch, OnNomatch, State). |
|
|
|
|
|
|
|
%% @private Return a branch based on a built in operator. |
|
|
|
-spec abstract_opfilter(atom(), atom(), term(), nextFun(), |
|
|
|
nextFun(), #state{}) -> [syntaxTree()]. |
|
|
|
abstract_opfilter(Key, Opname, Value, OnMatch, OnNomatch, State) -> |
|
|
|
abstract_getkey(Key, |
|
|
|
_OnMatch=fun(#state{fields=Fields}=State2) -> |
|
|
|
{_, Field} = lists:keyfind(Key, 1, Fields), |
|
|
|
[erl_syntax:case_expr( |
|
|
|
erl_syntax:application( |
|
|
|
erl_syntax:atom(erlang), erl_syntax:atom(Opname), |
|
|
|
[Field, erl_syntax:abstract(Value)]), |
|
|
|
[erl_syntax:clause([erl_syntax:atom(true)], none, |
|
|
|
OnMatch(State2)), |
|
|
|
erl_syntax:clause([erl_syntax:atom(false)], none, |
|
|
|
OnNomatch(State2))])] end, |
|
|
|
_OnNomatch=fun(State2) -> OnNomatch(State2) end, State). |
|
|
|
|
|
|
|
|
|
|
|
%% @private Generate an `all' filter. |
|
|
|
%% An `all' filter is evaluated by testing all conditions that must hold. If |
|
|
|
%% any of the conditions does not hold the evaluation is short circuted at that |
|
|
|
%% point. This means that the `OnNomatch' branch is executed once for each |
|
|
|
%% condition. The `OnMatch' branch is only executed once. |
|
|
|
-spec abstract_all([qcond()], nextFun(), nextFun(), #state{}) -> |
|
|
|
[syntaxTree()]. |
|
|
|
abstract_all([H|T], OnMatch, OnNomatch, State) -> |
|
|
|
abstract_filter_(H, |
|
|
|
_OnMatch=fun(State2) -> abstract_all(T, OnMatch, OnNomatch, State2) |
|
|
|
end, OnNomatch, State); |
|
|
|
abstract_all([], OnMatch, _OnNomatch, State) -> |
|
|
|
OnMatch(State). |
|
|
|
|
|
|
|
%% @private |
|
|
|
-spec abstract_any([qcond()], nextFun(), nextFun(), #state{}) -> |
|
|
|
[syntaxTree()]. |
|
|
|
abstract_any([H|T], OnMatch, OnNomatch, State) -> |
|
|
|
abstract_filter_(H, OnMatch, |
|
|
|
_OnNomatch=fun(State2) -> abstract_any(T, OnMatch, OnNomatch, State2) |
|
|
|
end, State); |
|
|
|
abstract_any([], _OnMatch, OnNomatch, State) -> |
|
|
|
OnNomatch(State). |
|
|
|
|
|
|
|
%% @private |
|
|
|
-spec abstract_with(fun((gre:event()) -> term()), #state{}) -> [syntaxTree()]. |
|
|
|
abstract_with(Fun, State) when is_function(Fun, 1) -> |
|
|
|
abstract_getparam(Fun, fun(#state{event=Event, paramvars=Params}) -> |
|
|
|
{_, Fun2} = lists:keyfind(Fun, 1, Params), |
|
|
|
[erl_syntax:application(none, Fun2, [Event])] |
|
|
|
end, State). |
|
|
|
|
|
|
|
%% @private Bind the value of a field to a variable. |
|
|
|
%% If the value of a field has already been bound to a variable the previous |
|
|
|
%% binding is reused over re-accessing the value. The `OnMatch' function is |
|
|
|
%% expected to access the variable stored in the state record. The `OnNomatch' |
|
|
|
%% function must not attempt to access the variable. |
|
|
|
-spec abstract_getkey(atom(), nextFun(), nextFun(), #state{}) -> |
|
|
|
[syntaxTree()]. |
|
|
|
abstract_getkey(Key, OnMatch, OnNomatch, #state{fields=Fields}=State) -> |
|
|
|
case lists:keyfind(Key, 1, Fields) of |
|
|
|
{Key, _Variable} -> OnMatch(State); |
|
|
|
false -> abstract_getkey_(Key, OnMatch, OnNomatch, State) |
|
|
|
end. |
|
|
|
|
|
|
|
|
|
|
|
-spec abstract_getkey_(atom(), nextFun(), nextFun(), #state{}) -> |
|
|
|
[syntaxTree()]. |
|
|
|
abstract_getkey_(Key, OnMatch, OnNomatch, #state{ |
|
|
|
event=Event, fields=Fields}=State) -> |
|
|
|
[erl_syntax:case_expr( |
|
|
|
abstract_apply(gre, find, [erl_syntax:atom(Key), Event]), |
|
|
|
[erl_syntax:clause([ |
|
|
|
erl_syntax:tuple([ |
|
|
|
erl_syntax:atom(true), |
|
|
|
field_variable(Key)])], none, |
|
|
|
OnMatch(State#state{ |
|
|
|
fields=[{Key, field_variable(Key)}|Fields]})), |
|
|
|
erl_syntax:clause([ |
|
|
|
erl_syntax:atom(false)], none, |
|
|
|
OnNomatch(State)) |
|
|
|
] |
|
|
|
)]. |
|
|
|
|
|
|
|
%% @private Bind the value of a parameter to a variable. |
|
|
|
%% During code generation the parameter value is used as the identity of the |
|
|
|
%% parameter. At runtime a unique integer is used as the identity. |
|
|
|
-spec abstract_getparam(term(), nextFun(), #state{}) -> [syntaxTree()]. |
|
|
|
abstract_getparam(Term, OnBound, #state{paramvars=Params}=State) -> |
|
|
|
case lists:keyfind(Term, 1, Params) of |
|
|
|
{_, _Variable} -> OnBound(State); |
|
|
|
%% parameter not bound to variable in this scope. |
|
|
|
false -> abstract_getparam_(Term, OnBound, State) |
|
|
|
end. |
|
|
|
|
|
|
|
|
|
|
|
-spec abstract_getparam_(term(), nextFun(), #state{}) -> [syntaxTree()]. |
|
|
|
abstract_getparam_(Term, OnBound, #state{paramstab=Table, |
|
|
|
paramvars=Params}=State) -> |
|
|
|
Key = case ets:lookup(Table, Term) of |
|
|
|
[{_, Key2}] -> |
|
|
|
Key2; |
|
|
|
[] -> |
|
|
|
Key2 = ets:info(Table, size), |
|
|
|
ets:insert(Table, {Term, Key2}), |
|
|
|
Key2 |
|
|
|
end, |
|
|
|
[erl_syntax:match_expr( |
|
|
|
param_variable(Key), |
|
|
|
abstract_apply(ets, lookup_element, |
|
|
|
[abstract_apply(table, [erl_syntax:atom(params)]), |
|
|
|
erl_syntax:abstract(Key), |
|
|
|
erl_syntax:abstract(2)])) |
|
|
|
] ++ OnBound(State#state{paramvars=[{Term, param_variable(Key)}|Params]}). |
|
|
|
|
|
|
|
%% @private Generate a variable name for the value of a field. |
|
|
|
%% @todo Encode non-alphanumeric characters as integer values. |
|
|
|
-spec field_variable(atom()) -> syntaxTree(). |
|
|
|
field_variable(Key) -> |
|
|
|
erl_syntax:variable("Field_" ++ atom_to_list(Key)). |
|
|
|
|
|
|
|
%% @private Generate a variable name for the value of a parameter. |
|
|
|
-spec param_variable(integer()) -> syntaxTree(). |
|
|
|
param_variable(Key) -> |
|
|
|
erl_syntax:variable("Param_" ++ integer_to_list(Key)). |
|
|
|
|
|
|
|
|
|
|
|
%% @private Return an expression to increment a counter. |
|
|
|
%% @todo Pass state record. Only Generate code if `statistics' is enabled. |
|
|
|
-spec abstract_count(atom()) -> syntaxTree(). |
|
|
|
abstract_count(Counter) -> |
|
|
|
abstract_apply(ets, update_counter, |
|
|
|
[abstract_apply(table, [erl_syntax:atom(counters)]), |
|
|
|
erl_syntax:abstract(Counter), |
|
|
|
erl_syntax:abstract({2,1})]). |
|
|
|
|
|
|
|
|
|
|
|
%% @private Return an expression to get the value of a counter. |
|
|
|
%% @todo Pass state record. Only Generate code if `statistics' is enabled. |
|
|
|
-spec abstract_getcount(atom()) -> [syntaxTree()]. |
|
|
|
abstract_getcount(Counter) -> |
|
|
|
[abstract_apply(ets, lookup_element, |
|
|
|
[abstract_apply(table, [erl_syntax:atom(counters)]), |
|
|
|
erl_syntax:abstract(Counter), |
|
|
|
erl_syntax:abstract(2)])]. |
|
|
|
|
|
|
|
|
|
|
|
%% abstract code util functions |
|
|
|
|
|
|
|
|
|
|
|
%% @private Compile an abstract module. |
|
|
|
-spec compile_forms(term(), [term()]) -> {ok, atom(), binary()}. |
|
|
|
compile_forms(Forms, _Opts) -> |
|
|
|
case compile:forms(Forms) of |
|
|
|
{ok, Module, Binary} -> |
|
|
|
{ok, Module, Binary}; |
|
|
|
{ok, Module, Binary, _Warnings} -> |
|
|
|
{ok, Module, Binary}; |
|
|
|
Error -> |
|
|
|
erlang:error({compile_forms, Error}) |
|
|
|
end. |
|
|
|
|
|
|
|
%% @private Load a module binary. |
|
|
|
-spec load_binary(atom(), binary()) -> {ok, loaded, atom()}. |
|
|
|
load_binary(Module, Binary) -> |
|
|
|
case code:load_binary(Module, "", Binary) of |
|
|
|
{module, Module} -> {ok, loaded, Module}; |
|
|
|
{error, Reason} -> exit({error_loading_module, Module, Reason}) |
|
|
|
end. |
|
|
|
|
|
|
|
%% @private Apply an exported function. |
|
|
|
-spec abstract_apply(atom(), atom(), [syntaxTree()]) -> syntaxTree(). |
|
|
|
abstract_apply(Module, Function, Arguments) -> |
|
|
|
erl_syntax:application( |
|
|
|
erl_syntax:atom(Module), |
|
|
|
erl_syntax:atom(Function), |
|
|
|
Arguments). |
|
|
|
|
|
|
|
%% @private Apply a module local function. |
|
|
|
-spec abstract_apply(atom(), [syntaxTree()]) -> syntaxTree(). |
|
|
|
abstract_apply(Function, Arguments) -> |
|
|
|
erl_syntax:application( |
|
|
|
erl_syntax:atom(Function), |
|
|
|
Arguments). |
|
|
|
|
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
@ -671,9 +352,8 @@ with_function_test() -> |
|
|
|
?assertEqual(1, receive Msg -> Msg after 0 -> notcalled end), |
|
|
|
done. |
|
|
|
|
|
|
|
union_single_test() -> |
|
|
|
{compiled, _Mod} = setup_query(testmod13, |
|
|
|
glc:union([glc:eq(a, 1)])), |
|
|
|
union_error_test() -> |
|
|
|
?assertError(badarg, glc:union([glc:eq(a, 1)])), |
|
|
|
done. |
|
|
|
|
|
|
|
-endif. |