diff --git a/README.md b/README.md index e85f860..2f46e3a 100644 --- a/README.md +++ b/README.md @@ -134,11 +134,11 @@ want sent to your `AdviceFunctionName` at runtime. The result of this will be a function which calls back into your module directly. In order to utilise (2), your `codegen/3` should return tuples which contain -`{Name, fun()}` where `Name` is the name you wish to export and the -corresponding fun is the function you wish to generate. The idea of this API -is that you can use the `codegen` module from `parse_trans` to generate your -function conveniently, and have the annotation processing engine deal with the -rest. +`{Name, Fun}` where `Name` is the name you wish to export and the +corresponding function definition is the function you wish to generate. +The idea of this API is that you can use the `codegen` module from `parse_trans` +to generate your function conveniently, and have the annotation processing +engine deal with the wiring. For an example of code generation in practise, take a look at the [delegate](https://github.com/hyperthunk/delegate) library. diff --git a/src/annotation.erl b/src/annotation.erl index 5f1fa9c..43e98b1 100644 --- a/src/annotation.erl +++ b/src/annotation.erl @@ -23,12 +23,15 @@ %% ----------------------------------------------------------------------------- -module(annotation). -export([behaviour_info/1]). --export([has_advice/1, has_advice/2, has_codegen/1]). +-export([has_advice/1, has_advice/2]). +-export([has_codegen/1, has_rewrite/1, has_coregen/1]). -export([process_annotation/2, call_advised/3, get_scope/0]). -annotation(['application', 'package', 'module']). -include("types.hrl"). +%% TODO: introduce 'codegen' to the behaviour_info + behaviour_info(callbacks) -> [{process_annotation, 2}, {get_scope, 0}]; behaviour_info(_) -> @@ -43,6 +46,12 @@ call_advised(M, F, Inputs) -> has_codegen(#annotation{name=Module}) -> erlang:function_exported(Module, codegen, 3). +has_coregen(#annotation{name=Module}) -> + erlang:function_exported(Module, coregen, 5). + +has_rewrite(#annotation{name=Module}) -> + erlang:function_exported(Module, rewrite, 1). + has_advice(#annotation{name=Module}) -> lists:member(true, all_advice(Module)). diff --git a/src/annotations.erl b/src/annotations.erl index b04daa0..80ab9d8 100644 --- a/src/annotations.erl +++ b/src/annotations.erl @@ -23,11 +23,13 @@ %% ----------------------------------------------------------------------------- -module(annotations). +-include("types.hrl"). + -export([is_annotation/1, process_annotation/1]). -export([list/1, find_by_function/2, find/2, find/3]). %% parse_transform and runtime introspection utilities --export([parse_transform/2, from_ast/2, +-export([parse_transform/2, from_ast/2, are_equal/2, get_scope/1, advised_name/1, check_scope/2]). -type(target() :: atom() | {atom(), atom(), integer()}). @@ -36,12 +38,9 @@ {scope(), target()} | {[scope()] | target() } | 'undefined'). --export_type([target/0, scope/0, annotation_scope/0]). +-opaque annotation() :: #annotation{}. --include("types.hrl"). - --type(annotation() :: #annotation{}). --export_type([annotation/0]). +-export_type([annotation/0, target/0, scope/0, annotation_scope/0]). -define(DEFAULT_SCOPE, 'module'). @@ -98,6 +97,10 @@ get_scope(Annotation) -> '_' end. +-spec are_equal(annotation(), annotation()) -> boolean(). +are_equal(#annotation{name=N1, scope=S1}, #annotation{name=N2, scope=S2}) -> + N1 =:= N2 andalso S1 == S2. + -spec(is_annotation/1 :: (#annotation{} | atom() | tuple()) -> boolean() | {boolean(), boolean()}). is_annotation(#annotation{name=Name}) -> {true, is_annotation(Name)}; @@ -153,7 +156,8 @@ filter(Type, Things) -> check_scope({Scope0, Target0}=A, {Scope1, Target1}=B) -> case check(Scope0, Scope1) of true -> - Checks = check_scope(tuple_to_list(Target0), tuple_to_list(Target1)), + Checks = check_scope(tuple_to_list(Target0), + tuple_to_list(Target1)), case lists:all(fun(X) -> X == ok end, Checks) of true -> ok; false -> @@ -175,9 +179,13 @@ check_scope(Allowed, Required) -> {invalid_scope, {Required, Allowed}} end. -check(A, B) when is_list(A) andalso is_atom(B) -> - lists:member(B, A); -check(A, B) when is_atom(A) andalso is_list(B) -> - lists:member(A, B); -check(A, B) -> - A == B. +check(A, B) when is_list(A) andalso + is_atom(B) -> lists:member(B, A); +check(A, B) when is_atom(A) andalso + is_list(B) -> lists:member(A, B); +check(A, {B, _}) when is_atom(A) andalso + is_atom(B) -> check(A, B); +check([H|_]=A, {B, _}) when is_atom(H) andalso + is_list(A) andalso + is_atom(B) -> check(A, B); +check(A, B) -> A == B. diff --git a/src/annotations_pt.erl b/src/annotations_pt.erl index 815e820..0c24f4e 100644 --- a/src/annotations_pt.erl +++ b/src/annotations_pt.erl @@ -30,9 +30,10 @@ parse_transform(Forms0, Options) -> {ok, P} = file:get_cwd(), progress_message("~s: Options: ~p~n", [P, Options]), Opt = merge_options(Options), - {Forms, FunAccs, _} = - lists:foldl(fun pick_annotations/2, {[], [], Opt}, Forms0), - process_fun_accs(FunAccs, Forms, Opt). + {Forms, FunAccs, TopAccs, _} = + lists:foldl(fun pick_annotations/2, {[], [], [], Opt}, Forms0), + NewForms = maybe_apply_changes(Forms, TopAccs), + process_fun_accs(FunAccs, NewForms, Opt). process_fun_accs(FuncAccs, Forms, Opt) -> %% TODO: move the whole sweep to using parse_trans @@ -73,6 +74,12 @@ xform_fun(function, Form, Ctx, {Annotations, Acc}) -> {NewForms, Form2, Acc2} = maybe_transform(Module, Form, {Applicable, Acc, []}), {NewForms, Form2, [], true, {Annotations, Acc2}}; +%xform_fun(attribute, {_, _, module, Module}=Form, _Ctx, {Annotations, Acc}) -> +% progress_message("Found ~p module level annotations for ~p~n", +% [length(Annotations), Module]), +% {NewForms, Form2, Acc2} = maybe_transform(Module, Form, +% {Annotations, Acc, []}), +% {[], Form2, NewForms, false, Acc2}; xform_fun(_Thing, Form, _Ctx, Acc) -> {[], Form, [], true, Acc}. @@ -112,6 +119,10 @@ gen_forms(Mod, Form, #annotation{name=AnnotationMod}=A) -> %% 2. {AdviceFunctionName, TargetFunctionName, Arity, Data} %% 3. {funName, fun()} (generation completely handled by the annotation callback module) %% +gen_function(_Mod, _Form, {Name, FunCode}, #annotation{name=_AnnotMod}) -> + NewImpl = erl_syntax:function(Name, FunCode), + Arity = erl_syntax:function_arity(NewImpl), + {NewImpl, {Name, Arity}}; gen_function(Mod, Form, {Advice, Target, Arity, Data}, #annotation{name=AnnotMod}) when is_atom(Target) -> @@ -224,31 +235,52 @@ process_attribute({attribute, L, N, _}=A, Mod, Opt) -> case is_annotation(N, Opt) of false -> A; _True -> - {attribute, L, annotation, annotations:from_ast(A, {'module', Mod})} + {attribute, L, annotation, + annotations:from_ast(A, {'module', Mod})} end. pick_annotations({attribute, _, _, _}=Form, - {Forms, [{maybe,_}|_]=FuncAccs, Opt}) -> - {[Form|Forms], FuncAccs, Opt}; -pick_annotations({attribute, _, Name, _}=Form, {Forms, FuncAccs, Opt}) -> + {Forms, [{maybe,_}|_]=FuncAccs, TopAccs, Opt}) -> + {[Form|Forms], FuncAccs, TopAccs, Opt}; +pick_annotations({attribute, _, Name, _}=Form, + {Forms, FuncAccs, TopAccs, Opt}) -> case is_annotation(Name, Opt) of false -> - {[Form|Forms], FuncAccs, Opt}; + {[Form|Forms], FuncAccs, TopAccs, Opt}; _True -> - {Forms, [{maybe, Form}| FuncAccs], Opt} + case annotations:get_scope(Name) of + Scopes when is_list(Scopes) -> + case lists:member(function, Scopes) of + true -> + {Forms, [{maybe, Form}| FuncAccs], TopAccs, Opt}; + _ -> + case lists:member(module, Scopes) of + true -> + {Forms, FuncAccs, [Form|TopAccs], Opt}; + false -> + %% TODO: log why we dropped it? + %% FIXME: set this at 'policy' level instead + {[Form|Forms], FuncAccs, TopAccs, Opt} + end + end; + _ -> + {Forms, [{maybe, Form}| FuncAccs], TopAccs, Opt} + end end; pick_annotations({function, _, FName, Arity, _}=Form, - {Forms, [{maybe, Annotation}|FuncAccs], Opt}) -> + {Forms, [{maybe, Annotation}|FuncAccs], TopAccs, Opt}) -> case lists:keyfind(FName, 1, FuncAccs) of false -> - {[Form|Forms], [{{FName, Arity}, [Annotation]}|FuncAccs], Opt}; + {[Form|Forms], + [{{FName, Arity}, [Annotation]}|FuncAccs], TopAccs, Opt}; {FName, Annotations} -> {[Form|Forms], lists:keyreplace({FName, Arity}, 1, FuncAccs, - {FName, [Annotation|Annotations]}), Opt} + {FName, [Annotation|Annotations]}), + TopAccs, Opt} end; -pick_annotations(Form, {Acc1, Acc2, Opt}) -> - {[Form|Acc1], Acc2, Opt}. +pick_annotations(Form, {Acc1, Acc2, TopAccs, Opt}) -> + {[Form|Acc1], Acc2, TopAccs, Opt}. current_scope(Forms) -> {attribute,_,module,Name} = lists:keyfind(module, 3, Forms),