% An example program to read a TypEr output from a file and produce XML loosely
% compatible with Edoc DTD (although strict conformance is unlikely to be reached).
% Usage: in Erlang shell type ecalls:main("file.t"). This results in creation
% of file.t.xml.

-module(ecalls).

-include_lib("xmerl/include/xmerl.hrl").

-export([main/1, parse_src/1, syn_to_xml/1]).

main (F) -> 
  case file:read_file (F) of
    {error, Msg} -> erlang:throw (Msg);
    {ok, B} ->
      S = erlang:binary_to_list (B),
      P = parse_src (S), 
      case P of
        {error, Msg} -> io:format ("~p~n", [Msg]);
        {ok, Ps} -> 
          X = to_simple_xml (Ps), file:write_file (F ++ ".xml", X)
      end
    end.

to_simple_xml (X) -> 
  xmerl:export_simple (lists:map ({?MODULE, syn_to_xml}, (X)), xmerl_xml).

syn_to_xml ({attribute, _, spec, {{F, A}, Cont}}) -> 
  #xmlElement {name = spec, 
               attributes = [#xmlAttribute {name = function, value = atom_to_list (F)},
                             #xmlAttribute {name = arity, value = integer_to_list (A)}],
               content = lists:map ({?MODULE, syn_to_xml}, Cont)};

syn_to_xml ({type, N, Type, any}) -> syn_to_xml ({type, N, Type, []});

% The product type corresponds to functional type arguments.
% For better compatibility with Edoc, transform this element
% into argtypes.

syn_to_xml ({type, _, product, Cont}) ->
  #xmlElement {name = argtypes,
               attributes = [],
               content = lists:map ({?MODULE, syn_to_xml}, Cont)};

% This is different from Edoc's record, so use different tag.

syn_to_xml ({type, N, 'record', Cont}) ->
  syn_to_xml ({type, N, recref, Cont});

syn_to_xml ({type, _, Type, Cont}) ->
  mk_type (#xmlElement {name = Type,
                        attributes = [],
                        content = lists:map ({?MODULE, syn_to_xml}, Cont)});

% var _ is synonymous to "any".

syn_to_xml ({var, N, '_'}) -> syn_to_xml ({type, N, any, []});

syn_to_xml ({var, _, V}) ->
  #xmlElement {name = var,
               attributes = [#xmlAttribute {name = name, value = atom_to_list (V)}],
               content = []};

syn_to_xml ({'atom', _, ''}) -> 
  #xmlElement {name = 'atom',
               attributes = [],
               content = []};

syn_to_xml ({'atom', _, A}) ->
  mk_const (#xmlElement {name = 'atom',
                         attributes = [#xmlAttribute {name = value, value = atom_to_list (A)}],
                         content = []});

syn_to_xml ({'integer', _, ''}) ->
  #xmlElement {name = 'integer',
               attributes = [],
               content = []};

syn_to_xml ({'integer', _, I}) ->
  mk_const (#xmlElement {name = 'integer',
                         attributes = [#xmlAttribute {name = value, value = integer_to_list (I)}],
                         content = []});

syn_to_xml (Z) ->
  io:format ("ignored: ", []),
  io:write (Z),
  io:format ("~n"),
  #xmlElement {name = ignored}.

mk_const (C) ->
  #xmlElement {name = const,
               attributes = [],
               content = [C]}.

mk_type (C) ->
  #xmlElement {name = type,
               attributes = [],
               content = [C]}.

parse_src (Src) -> parse_src ([], Src, 1, []).

parse_src (_, [], _, Ps) -> {ok, Ps};

parse_src (Cont, Src, Line, Ps) ->
  R = erl_scan:tokens (Cont, Src, Line),
  case R of
    {more, _} -> {ok, Ps};                   % most likely empty lines at the end
    {done, {eof, _}, _} -> {ok, Ps};
    {done, {ok, Ts, EndLine}, LeftSrc} ->
      P = erl_parse:parse_form (Ts),
      case P of
        {ok, Frm} -> parse_src (Cont, LeftSrc, EndLine, Ps ++ [Frm]);
        {error, {L, _, D}} -> {error, lists:flatten (erl_parse:format_error (D)) ++ 
                                      " at line " ++ erlang:integer_to_list (L)}
      end
    end.    

