diff options
Diffstat (limited to 'lib/erl_docgen/src/otp_specs.erl')
| -rw-r--r-- | lib/erl_docgen/src/otp_specs.erl | 701 | 
1 files changed, 701 insertions, 0 deletions
| diff --git a/lib/erl_docgen/src/otp_specs.erl b/lib/erl_docgen/src/otp_specs.erl new file mode 100644 index 0000000000..728ddb2e6e --- /dev/null +++ b/lib/erl_docgen/src/otp_specs.erl @@ -0,0 +1,701 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(otp_specs). + +-export([module/2, package/2, overview/2, type/1]). + +-include("xmerl.hrl"). + +-define(XML_EXPORT, xmerl_xml). +-define(DEFAULT_XML_EXPORT, ?XML_EXPORT). +-define(DEFAULT_PP, erl_pp). +-define(IND(N), #xmlText{value="\n" ++ lists:duplicate(N, $\s)}). +-define(NL, "\n"). + +module(Element, Options) -> +    XML = layout_module(Element, init_opts(Options)), +    Export = proplists:get_value(xml_export, Options, +				 ?DEFAULT_XML_EXPORT), +    xmerl:export_simple(XML, Export, [#xmlAttribute{name=prolog, +                                                    value=""}]). + +-record(opts, {pretty_print, file_suffix}). + +init_opts(Options) -> +    #opts{pretty_print = proplists:get_value(pretty_print, +                                             Options, ?DEFAULT_PP), +          %% It *is* depending on edoc.hrl! +          file_suffix = proplists:get_value(file_suffix, Options, ".html")}. + +layout_module(#xmlElement{name = module, content = Es}=E, Opts) -> +    Name = get_attrval(name, E), +    Functions = [{function_name(Elem), Elem} || +                    Elem <- get_content(functions, Es)], +    Types = [{type_name(Elem), Elem} || Elem <- get_content(typedecls, Es)], +    Body = [{module, +             [{name,[Name]}], +             ([?NL] ++ types(lists:sort(Types), Opts) +              ++ functions(lists:sort(Functions), Opts) +              ++ timestamp())}], +    Body. + +timestamp() -> +    [{timestamp, [io_lib:fwrite("Generated by EDoc, ~s, ~s.", +                                [edoc_lib:datestr(date()), +                                 edoc_lib:timestr(time())])]},?NL]. + +functions(Fs, Opts) -> +    lists:flatmap(fun ({Name, E}) -> function(Name, E, Opts) end, Fs). + +function(Name, #xmlElement{content = Es}, Opts) -> +    TS = get_content(typespec, Es), +    Spec = typespec(TS, Opts), +    [{spec,(Name +            ++ [?IND(2),{contract,Spec}] +            ++ typespec_annos(TS))}, +     ?NL]. + +function_name(E) -> +    [] = get_attrval(module, E), +    [?IND(2),{name,[atom(get_attrval(name, E))]}, +     ?IND(2),{arity,[get_attrval(arity, E)]}]. + +label_anchor(Content, E) -> +    case get_attrval(label, E) of +	"" -> Content; +	Ref -> [{marker, [{id, Ref}], Content}] +    end. + +typespec([], _Opts) -> []; +typespec(Es, Opts) -> +    {Head, LDefs} = collect_clause(Es, Opts), +    clause(Head, LDefs) ++ [?IND(2)]. + +collect_clause(Es, Opts) -> +    Name = t_name(get_elem(erlangName, Es)), +    Defs = get_elem(localdef, Es), +    [Type] = get_elem(type, Es), +    {format_spec(Name, Type, Opts), collect_local_defs(Defs, Opts)}. + +clause(Head, LDefs) -> +    FC = [?IND(6),{head,Head}] ++ local_clause_defs(LDefs), +    [?IND(4),{clause,FC}]. + +local_clause_defs([]) -> []; +local_clause_defs(LDefs) -> +    LocalDefs = [{subtype,T} || T <- coalesce_local_defs(LDefs, [])], +    [?IND(6),{guard,margin(8, LocalDefs)}]. + +types(Ts, Opts) -> +    lists:flatmap(fun ({Name, E}) -> typedecl(Name, E, Opts) end, Ts). + +typedecl(Name, E=#xmlElement{content = Es}, Opts) -> +    TD = get_content(typedef, Es), +    TypeDef = typedef(E, TD, Opts), +    [{type,(Name +            ++ [?IND(2),{typedecl, TypeDef}] +            ++ typedef_annos(TD))}, +      ?NL]. + +type_name(#xmlElement{content = Es}) -> +    Typedef = get_content(typedef, Es), +    [E] = get_elem(erlangName, Typedef), +    Args = get_content(argtypes, Typedef), +    [] = get_attrval(module, E), +    [?IND(2),{name,[atom(get_attrval(name, E))]}, +     ?IND(2),{n_vars,[integer_to_list(length(Args))]}]. + +typedef(E, Es, Opts) -> +    Ns = get_elem(erlangName, Es), +    Name = +        ([t_name(Ns), "("] +         ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])), +    LDefs = collect_local_defs(get_elem(localdef, Es), Opts), +    TypeHead = case get_elem(type, Es) of +                   [] -> label_anchor(Name, E); +                   Type -> (label_anchor(Name, E) +                            ++ format_type(Name, Type, Opts)) +               end, +    ([?IND(6),{typehead,TypeHead}] +     ++ local_type_defs(LDefs, [])). + +local_type_defs([], _) -> []; +local_type_defs(LDefs, Last) -> +    LocalDefs = [{local_def,T} || T <- coalesce_local_defs(LDefs, Last)], +    [?IND(6),{local_defs,margin(8, LocalDefs)}]. + +collect_local_defs(Es, Opts) -> +    [collect_localdef(E, Opts) || E <- Es]. + +collect_localdef(E = #xmlElement{content = Es}, Opts) -> +    Name = case get_elem(typevar, Es) of +               [] -> +                   label_anchor(N0 = t_abstype(get_content(abstype, Es)), E); +               [V] -> +                   N0 = t_var(V) +           end, +    {Name,N0,format_type(N0, get_elem(type, Es), Opts)}. + +%% "A = t(), B = t()" is coalesced into "A = B = t()". +%% Names as B above are kept, but the formated string is empty. +coalesce_local_defs([], _Last) -> +    []; +coalesce_local_defs([{Name,N0,TypeS} | L], Last) when Name =:= N0 -> +    cld(L, [{Name,N0}], TypeS, Last); +coalesce_local_defs([{Name,N0,TypeS} | L], Last) -> +    [local_def(N0, Name, TypeS, Last, L) | coalesce_local_defs(L, Last)]. + +cld([{Name,N0,TypeS} | L], Names, TypeS, Last) when Name =:= N0 -> +    cld(L, [{Name,N0} | Names], TypeS, Last); +cld(L, Names0, TypeS, Last) -> +    Names = [{_,Name0} | Names1] = lists:reverse(Names0), +    NS = join([N || {N,_} <- Names], [" = "]), +    ([local_def(Name0, NS, TypeS, Last, L) | +      [local_def(N0, "", "", [], L) || {_,N0} <- Names1]] +     ++ coalesce_local_defs(L, Last)). + +local_def(Name, NS, TypeS, Last, L) -> +    [{typename,Name},{string,NS ++ TypeS ++ [Last || L =:= []]}]. + +%% join([], Sep) when is_list(Sep) -> +%%     []; +join([H|T], Sep) -> +    H ++ lists:append([Sep ++ X || X <- T]). + +%% Use the default formatting of EDoc, which creates references, and +%% then insert newlines and indentation according to erl_pp (the +%% (fast) Erlang pretty printer). +format_spec(Name, Type, #opts{pretty_print = erl_pp}=Opts) -> +    try +        L = t_clause(Name, Type), +        O = pp_clause(Name, Type), +        {R, ".\n"} = diaf(L, O, Opts), +        R +    catch _:_ -> +        %% Example: "@spec ... -> record(a)" +        format_spec(Name, Type, Opts#opts{pretty_print=default}) +    end; +format_spec(Sep, Type, _Opts) -> +    t_clause(Sep, Type). + +t_clause(Name, Type) -> +    #xmlElement{content = [#xmlElement{name = 'fun', content = C}]} = Type, +    [Name] ++ t_fun(C). + +pp_clause(Pre, Type) -> +    Types = ot_utype([Type]), +    Atom = lists:duplicate(iolist_size(Pre), $a), +    L1 = erl_pp:attribute({attribute,0,spec,{{list_to_atom(Atom),0},[Types]}}), +    "-spec " ++ L2 = lists:flatten(L1), +    L3 = Pre ++ lists:nthtail(length(Atom), L2), +    re:replace(L3, "\n      ", "\n", [{return,list},global]). + +format_type(Name, Type, #opts{pretty_print = erl_pp}=Opts) -> +    try +        L = t_utype(Type), +        O = pp_type(Name, Type), +        {R, ".\n"} = diaf(L, O, Opts), +        [" = "] ++ R +    catch _:_ -> +        %% Example: "t() = record(a)." +        format_type(Name, Type, Opts#opts{pretty_print=default}) +    end; +format_type(_Name, Type, _Opts) -> +    [" = "] ++ t_utype(Type). + +pp_type(Prefix, Type) -> +    Atom = list_to_atom(lists:duplicate(iolist_size(Prefix), $a)), +    L1 = erl_pp:attribute({attribute,0,type,{Atom,ot_utype(Type),[]}}), +    {L2,N} = case lists:dropwhile(fun(C) -> C =/= $: end, lists:flatten(L1)) of +                 ":: " ++ L3 -> {L3,9}; % compensation for extra "()" and ":" +                 "::\n" ++ L3 -> {"\n"++L3,6} +             end, +    Ss = lists:duplicate(N, $\s), +    re:replace(L2, "\n"++Ss, "\n", [{return,list},global]). + +diaf(L, O0, Opts) -> +    {R0, O} = diaf(L, [], O0, [], Opts), +    R1 = rewrite_some_predefs(lists:reverse(R0)), +    R = indentation(lists:flatten(R1)), +    {R, O}. + +diaf([C | L], St, [C | O], R, Opts) -> +    diaf(L, St, O, [[C] | R], Opts); +diaf(" "++L, St, O, R, Opts) -> +    diaf(L, St, O, R, Opts); +diaf("", [Cs | St], O, R, Opts) -> +    diaf(Cs, St, O, R, Opts); +diaf("", [], O, R, _Opts) -> +    {R, O}; +diaf(L, St, " "++O, R, Opts) -> +    diaf(L, St, O, [" " | R], Opts); +diaf(L, St, "\n"++O, R, Opts) -> +    Ss = lists:takewhile(fun(C) -> C =:= $\s end, O), +    diaf(L, St, lists:nthtail(length(Ss), O), ["\n"++Ss | R], Opts); +diaf([{seealso, HRef0, S0} | L], St, O0, R, Opts) -> +    {S, O} = diaf(S0, app_fix(O0), Opts), +    HRef = fix_mod_ref(HRef0, Opts), +    diaf(L, St, O, [{seealso, HRef, S} | R], Opts); +diaf("="++L, St, "::"++O, R, Opts) -> +    %% EDoc uses "=" for record field types; Dialyzer uses "::". Maybe +    %% there should be an option for this, possibly affecting other +    %% similar discrepancies. +    diaf(L, St, O, ["=" | R], Opts); +diaf([Cs | L], St, O, R, Opts) -> +    diaf(Cs, [L | St], O, R, Opts). + +rewrite_some_predefs(S) -> +    xpredef(lists:flatten(S)). + +xpredef([]) -> +    []; +xpredef("neg_integer()"++L) -> +    ["integer() =< -1"] ++ xpredef(L); +xpredef("non_neg_integer()"++L) -> +    ["integer() >= 0"] ++ xpredef(L); +xpredef("pos_integer()"++L) -> +    ["integer() >= 1"] ++ xpredef(L); +xpredef([T | Es]) when is_tuple(T) -> +    [T | xpredef(Es)]; +xpredef([E | Es]) -> +    [[E] | xpredef(Es)]. + +indentation([]) -> +    []; +indentation([$\n|L]) -> +    [{br,[]}|indent(L)]; +indentation([T | Es]) when is_tuple(T) -> +    [T | indentation(Es)]; +indentation([E|L]) -> +    [[E]|indentation(L)]. + +indent([$\s|L]) -> +    [{nbsp,[]}|indent(L)]; +indent(L) -> +    indentation(L). + +app_fix(L) -> +    try +        {"//" ++ R1,L2} = app_fix(L, 1), +        [App, Mod] = string:tokens(R1, "/"), +        "//" ++ atom(App) ++ "/" ++ atom(Mod) ++ L2 +    catch _:_ -> L +    end. + +app_fix(L, I) -> % a bit slow +    {L1, L2} = lists:split(I, L), +    case erl_scan:tokens([], L1 ++ ". ", 1) of +        {done, {ok,[{atom,_,Atom}|_],_}, _} -> {atom_to_list(Atom), L2}; +        _ -> app_fix(L, I+1) +    end. + +%% Remove the file suffix from module references. +fix_mod_ref(HRef, #opts{file_suffix = ""}) -> +    HRef; +fix_mod_ref([{marker, S}]=HRef0, #opts{file_suffix = FS}) -> +    {A, B} = lists:splitwith(fun(C) -> C =/= $# end, S), +    case lists:member($:, A) of +        true -> +            HRef0; % should "save" most application references "http:" +        false -> +            case {lists:suffix(FS, A), B} of +                {true, "#"++_} -> +                    [{marker, lists:sublist(A, length(A)-length(FS)) ++ B}]; +                _ -> +                    HRef0 +            end +    end. + +see(E, Es) -> +    case href(E) of +	[] -> Es; +	Ref -> +	    [{seealso, Ref, Es}] +    end. + +href(E) -> +    case get_attrval(href, E) of +	"" -> []; +	URI -> +	    [{marker, URI}] +    end. + +atom(String) -> +    io_lib:write_atom(list_to_atom(String)). + +t_name([E]) -> +    N = get_attrval(name, E), +    case get_attrval(module, E) of +	"" -> atom(N); +	M -> +	    S = atom(M) ++ ":" ++ atom(N), +	    case get_attrval(app, E) of +		"" -> S; +		A -> "//" ++ atom(A) ++ "/" ++ S +	    end +    end. + +t_utype([E]) -> +    t_utype_elem(E). + +t_utype_elem(E=#xmlElement{content = Es}) -> +    case get_attrval(name, E) of +	"" -> t_type(Es); +	Name -> +	    T = t_type(Es), +	    case T of +		[Name] -> T;    % avoid generating "Foo::Foo" +		T -> [Name] ++ ["::"] ++ T +	    end +    end. + +t_type([E=#xmlElement{name = typevar}]) -> +    t_var(E); +t_type([E=#xmlElement{name = atom}]) -> +    t_atom(E); +t_type([E=#xmlElement{name = integer}]) -> +    t_integer(E); +t_type([E=#xmlElement{name = range}]) -> +    t_range(E); +t_type([E=#xmlElement{name = binary}]) -> +    t_binary(E); +t_type([E=#xmlElement{name = float}]) -> +    t_float(E); +t_type([#xmlElement{name = nil}]) -> +    t_nil(); +t_type([#xmlElement{name = list, content = Es}]) -> +    t_list(Es); +t_type([#xmlElement{name = nonempty_list, content = Es}]) -> +    t_nonempty_list(Es); +t_type([#xmlElement{name = tuple, content = Es}]) -> +    t_tuple(Es); +t_type([#xmlElement{name = 'fun', content = Es}]) -> +    ["fun("] ++ t_fun(Es) ++ [")"]; +t_type([E = #xmlElement{name = record, content = Es}]) -> +    t_record(E, Es); +t_type([E = #xmlElement{name = abstype, content = Es}]) -> +    t_abstype(E, Es); +t_type([#xmlElement{name = union, content = Es}]) -> +    t_union(Es). + +t_var(E) -> +    [get_attrval(name, E)]. + +t_atom(E) -> +    [get_attrval(value, E)]. + +t_integer(E) -> +    [get_attrval(value, E)]. + +t_range(E) -> +    [get_attrval(value, E)]. + +t_binary(E) -> +    [get_attrval(value, E)]. + +t_float(E) -> +    [get_attrval(value, E)]. + +t_nil() -> +    ["[]"]. + +t_list(Es) -> +    ["["] ++ t_utype(get_elem(type, Es)) ++ ["]"]. + +t_nonempty_list(Es) -> +    ["["] ++ t_utype(get_elem(type, Es)) ++ [", ...]"]. + +t_tuple(Es) -> +    ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]). + +t_fun(Es) -> +    ["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), +		 [") -> "] ++ t_utype(get_elem(type, Es))). + +t_record(E, Es) -> +    Name = ["#"] ++ t_type(get_elem(atom, Es)), +    case get_elem(field, Es) of +        [] -> +            see(E, [Name, "{}"]); +        Fs -> +            see(E, Name) ++ ["{"] ++ seq(fun t_field/1, Fs, ["}"]) +    end. + +t_field(#xmlElement{content = Es}) -> +    t_type(get_elem(atom, Es)) ++ [" = "] ++ t_utype(get_elem(type, Es)). + +t_abstype(E, Es) -> +    Name = t_name(get_elem(erlangName, Es)), +    case get_elem(type, Es) of +        [] -> +            see(E, [Name, "()"]); +        Ts -> +            see(E, [Name]) ++ ["("] ++ seq(fun t_utype_elem/1, Ts, [")"]) +    end. + +t_abstype(Es) -> +    ([t_name(get_elem(erlangName, Es)), "("] +     ++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"])). + +t_union(Es) -> +    seq(fun t_utype_elem/1, Es, " | ", []). + +seq(F, Es, Tail) -> +    seq(F, Es, ", ", Tail). + +seq(F, [E], _Sep, Tail) -> +    F(E) ++ Tail; +seq(F, [E | Es], Sep, Tail) -> +    F(E) ++ [Sep] ++ seq(F, Es, Sep, Tail); +seq(_F, [], _Sep, Tail) -> +    Tail. + +get_elem(Name, [#xmlElement{name = Name} = E | Es]) -> +    [E | get_elem(Name, Es)]; +get_elem(Name, [_ | Es]) -> +    get_elem(Name, Es); +get_elem(_, []) -> +    []. + +get_attr(Name, [#xmlAttribute{name = Name} = A | As]) -> +    [A | get_attr(Name, As)]; +get_attr(Name, [_ | As]) -> +    get_attr(Name, As); +get_attr(_, []) -> +    []. + +get_attrval(Name, #xmlElement{attributes = As}) -> +    case get_attr(Name, As) of +	[#xmlAttribute{value = V}] -> +	    V; +	[] -> "" +    end. + +get_content(Name, Es) -> +    case get_elem(Name, Es) of +	[#xmlElement{content = Es1}] -> +	    Es1; +	[] -> [] +    end. + +overview(_, _Options) -> []. + +package(_, _Options) -> []. + +type(_) -> []. + +%% --------------------------------------------------------------------- + +ot_utype([E]) -> +    ot_utype_elem(E). + +ot_utype_elem(E=#xmlElement{content = Es}) -> +    case get_attrval(name, E) of +	"" -> ot_type(Es); +	N -> +            Name = {var,0,list_to_atom(N)}, +	    T = ot_type(Es), +	    case T of +		Name -> T; +                T -> {ann_type,0,[Name, T]} +	    end +    end. + +ot_type([E=#xmlElement{name = typevar}]) -> +    ot_var(E); +ot_type([E=#xmlElement{name = atom}]) -> +    ot_atom(E); +ot_type([E=#xmlElement{name = integer}]) -> +    ot_integer(E); +ot_type([E=#xmlElement{name = range}]) -> +    ot_range(E); +ot_type([E=#xmlElement{name = binary}]) -> +    ot_binary(E); +ot_type([E=#xmlElement{name = float}]) -> +    ot_float(E); +ot_type([#xmlElement{name = nil}]) -> +    ot_nil(); +ot_type([#xmlElement{name = list, content = Es}]) -> +    ot_list(Es); +ot_type([#xmlElement{name = nonempty_list, content = Es}]) -> +    ot_nonempty_list(Es); +ot_type([#xmlElement{name = tuple, content = Es}]) -> +    ot_tuple(Es); +ot_type([#xmlElement{name = 'fun', content = Es}]) -> +    ot_fun(Es); +ot_type([#xmlElement{name = record, content = Es}]) -> +    ot_record(Es); +ot_type([#xmlElement{name = abstype, content = Es}]) -> +     ot_abstype(Es); +ot_type([#xmlElement{name = union, content = Es}]) -> +    ot_union(Es). + +ot_var(E) -> +    {var,0,list_to_atom(get_attrval(name, E))}. + +ot_atom(E) -> +    {ok, [Atom], _} = erl_scan:string(get_attrval(value, E), 0), +    Atom. + +ot_integer(E) -> +    {integer,0,list_to_integer(get_attrval(value, E))}. + +ot_range(E) -> +    [I1, I2] = string:tokens(get_attrval(value, E), "."), +    {type,0,range,[{integer,0,list_to_integer(I1)}, +                   {integer,0,list_to_integer(I2)}]}. + +ot_binary(E) -> +    {Base, Unit} = +        case string:tokens(get_attrval(value, E), ",:*><") of +            [] -> +                {0, 0}; +            ["_",B] -> +                {list_to_integer(B), 0}; +            ["_","_",U] -> +                {0, list_to_integer(U)}; +            ["_",B,_,"_",U] -> +                {list_to_integer(B), list_to_integer(U)} +        end, +    {type,0,binary,[{integer,0,Base},{integer,0,Unit}]}. + +ot_float(E) -> +    {float,0,list_to_float(get_attrval(value, E))}. + +ot_nil() -> +    {nil,0}. + +ot_list(Es) -> +    {type,0,list,[ot_utype(get_elem(type, Es))]}. + +ot_nonempty_list(Es) -> +    {type,0,nonempty_list,[ot_utype(get_elem(type, Es))]}. + +ot_tuple(Es) -> +    {type,0,tuple,[ot_utype_elem(E) || E <- Es]}. + +ot_fun(Es) -> +    Range = ot_utype(get_elem(type, Es)), +    Args = [ot_utype_elem(A) || A <- get_content(argtypes, Es)], +    {type,0,'fun',[{type,0,product,Args},Range]}. + +ot_record(Es) -> +    {type,0,record,[ot_type(get_elem(atom, Es)) | +                    [ot_field(F) || F <- get_elem(field, Es)]]}. + +ot_field(#xmlElement{content = Es}) -> +    {type,0,field_type, +     [ot_type(get_elem(atom, Es)), ot_utype(get_elem(type, Es))]}. + +ot_abstype(Es) -> +    ot_name(get_elem(erlangName, Es), +            [ot_utype_elem(Elem) || Elem <- get_elem(type, Es)]). + +ot_union(Es) -> +    {type,0,union,[ot_utype_elem(E) || E <- Es]}. + +ot_name(Es, T) -> +    case ot_name(Es) of +        [Mod, ":", Atom] -> +            {remote_type,0,[{atom,0,list_to_atom(Mod)}, +                            {atom,0,list_to_atom(Atom)},T]}; +        "tuple" when T =:= [] -> +            {type,0,tuple,any}; +        Atom -> +            {type,0,list_to_atom(Atom),T} +    end. + +ot_name([E]) -> +    Atom = get_attrval(name, E), +    case get_attrval(module, E) of +	"" -> Atom; +	M -> +	    case get_attrval(app, E) of +		"" -> +                    [M, ":", Atom]; +                A -> +                    ["//"++A++"/" ++ M, ":", Atom] % EDoc only! +	    end +    end. + +%% Returns exactly those annotations that can be referred to. Note +%% that a Dialyzer type/spec (currently) can have more annotations +%% than can be represented by EDoc types. Note also that edoc_dia +%% has annotated all type variables with themselves. +typespec_annos([]) -> [?NL]; +typespec_annos([_|Es]) -> +    annotations(clause_annos(Es)). + +clause_annos(Es) -> +    [annos(get_elem(type, Es)), local_defs_annos(get_elem(localdef, Es))]. + +typedef_annos(Es) -> +    annotations([(case get_elem(type, Es) of +                      [] -> []; +                      T -> annos(T) +                  end +                  ++ lists:flatmap(fun annos_elem/1, +                                   get_content(argtypes, Es))), +                 local_defs_annos(get_elem(localdef, Es))]). + +local_defs_annos(Es) -> +    lists:flatmap(fun localdef_annos/1, Es). + +localdef_annos(#xmlElement{content = Es}) -> +    annos(get_elem(type, Es)). + +annotations(AnnoL) -> +    Annos = lists:usort(lists:flatten(AnnoL)), +    margin(2, Annos). + +margin(N, L) -> +    lists:append([[?IND(N),E] || E <- L]) ++ [?IND(N-2)]. + +annos([E]) -> +    annos_elem(E). + +annos_elem(E=#xmlElement{content = Es}) -> +    case get_attrval(name, E) of +	"" -> annos_type(Es); +        "..." -> annos_type(Es); % compensate for a kludge in edoc_dia.erl +	N -> +            [{anno,[N]} | annos_type(Es)] +    end. + +annos_type([#xmlElement{name = list, content = Es}]) -> +    annos(get_elem(type, Es)); +annos_type([#xmlElement{name = nonempty_list, content = Es}]) -> +    annos(get_elem(type, Es)); +annos_type([#xmlElement{name = tuple, content = Es}]) -> +    lists:flatmap(fun annos_elem/1, Es); +annos_type([#xmlElement{name = 'fun', content = Es}]) -> +    (annos(get_elem(type, Es)) +     ++ lists:flatmap(fun annos_elem/1, get_content(argtypes, Es))); +annos_type([#xmlElement{name = record, content = Es}]) -> +    lists:append([annos(get_elem(type, Es1)) || +                     #xmlElement{content = Es1} <- get_elem(field, Es)]); +annos_type([#xmlElement{name = abstype, content = Es}]) -> +    lists:flatmap(fun annos_elem/1, get_elem(type, Es)); +annos_type([#xmlElement{name = union, content = Es}]) -> +    lists:flatmap(fun annos_elem/1, Es); +annos_type([E=#xmlElement{name = typevar}]) -> +    annos_elem(E); +annos_type(_) -> +    []. | 
