1%% Copyright (c) 2016 Eric Bailey
2%%
3%% Licensed under the Apache License, Version 2.0 (the "License");
4%% you may not use this file except in compliance with the License.
5%% You may obtain a copy of the License at
6%%
7%%     http://www.apache.org/licenses/LICENSE-2.0
8%%
9%% Unless required by applicable law or agreed to in writing, software
10%% distributed under the License is distributed on an "AS IS" BASIS,
11%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12%% See the License for the specific language governing permissions and
13%% limitations under the License.
14
15%% File    : lfe_doc.erl
16%% Author  : Eric Bailey
17%% Purpose : Lisp Flavoured Erlang (LFE) documentation parser.
18
19%% There is no guarantee the internal formats will not change
20%% but the interface functions should stay the same.
21
22-module(lfe_doc).
23
24-export([format_error/1]).
25
26-export([extract_module_docs/1,extract_module_docs/2,save_module_docs/3]).
27
28%% Access functions for documentation in modules.
29-export([get_module_docs/1,module_doc/1,mf_docs/1,mf_doc_type/1,
30         function_docs/3,macro_docs/2,
31         function_name/1,function_arity/1,function_line/1,
32         function_patterns/1, function_doc/1,
33         macro_name/1,macro_line/1,macro_patterns/1,macro_doc/1]).
34
35-export([format_docs/1]).
36
37-import(lists, [member/2,filter/2,foldl/3,foldr/3,reverse/1]).
38
39-include("lfe_comp.hrl").
40-include("lfe_doc.hrl").
41
42-ifdef(EUNIT).
43-export([collect_docs/2,pprint/2]).             %Used by prop_lfe_doc
44
45-include_lib("eunit/include/eunit.hrl").
46
47-define(QC_OPTS, [{on_output,fun pprint/2},{numtests,1000},{max_size,10}]).
48-define(QC(T,P), {timeout,30,{T,?_assert(proper:quickcheck(P, ?QC_OPTS))}}).
49-endif.
50
51%% Errors
52format_error(_) -> "doc error".
53
54%% extract_module_docs(Defs, CompInfo) -> {ok,Docs} | {error,Errors,[]}.
55%%  Parse a module's docstrings and return the docs.
56
57-spec extract_module_docs(Defs, Cinfo) -> {ok,Docs} | {error,Errors,[]} when
58      Defs   :: [{Form,Line}],
59      Form   :: [_],
60      Line   :: non_neg_integer(),
61      Cinfo  :: #cinfo{},
62      Docs   :: [doc()],
63      Errors :: nonempty_list({error,Line,Error}),
64      Error  :: any().
65
66extract_module_docs(Defs) ->                    %Just give a default #cinfo{}
67    extract_module_docs(Defs, #cinfo{}).
68
69extract_module_docs([], _Ci)  -> {ok,[]};
70extract_module_docs(Defs, Ci) ->
71    {Mdoc,Docs} = do_forms(Defs),
72    Errors = filter(fun (#doc{}) -> false;
73                        (_)      -> true
74                    end, Docs),
75    ?DEBUG("#doc: ~p\n", [{Mdoc,Docs}], Ci#cinfo.opts),
76    ?IF([] =:= Errors,
77        {ok,{Mdoc,Docs}},
78        {error,Errors,[]}).
79
80do_forms(Fs) -> foldl(fun do_form/2, {[],[]}, Fs).
81
82do_form({['define-module',_,Meta,Atts],_}, {Mdoc,Docs}) ->
83    {do_module_def(Meta, Atts, Mdoc),Docs};
84do_form({['extend-module',Meta,Atts],_}, {Mdoc,Docs}) ->
85    {do_module_def(Meta, Atts, Mdoc),Docs};
86do_form({['define-function',Name,Meta,Def],Line}, {Mdoc,Docs}) ->
87    {Mdoc,do_function(Name, Def, Meta, Line, Docs)};
88do_form({['define-macro',Name,Meta,Def],Line}, {Mdoc,Docs}) ->
89    {Mdoc,do_macro(Name, Def, Meta, Line, Docs)};
90do_form(_, Doc) -> Doc.                         %Ignore eval-when-compile
91
92do_module_def(Meta, Atts, Mdoc0) ->
93    Mdoc1 = collect_docs(Meta, Mdoc0),          %First take meta docs
94    collect_docs(Atts, Mdoc1).                  %then the attribute docs
95
96collect_docs(As, Mdoc) ->
97    %% Collect all the docs in all the doc metas/attributes.
98    Afun = fun ([doc|Docs], Md) ->
99                   Dfun = fun (D, M) -> M ++ [string_to_binary(D)] end,
100                   foldl(Dfun, Md, Docs);
101               (_, Md) -> Md
102           end,
103    foldl(Afun, Mdoc, As).
104
105do_function(Name, Def, Meta, Line, Docs) ->
106    %% Must get patterns and arity before we can check if excluded.
107    {Arity,Pats} = get_function_patterns(Def),
108    ?IF(exclude(Name, Arity, Meta),
109        Docs,
110        begin
111            Fdoc = make_function_doc(Name, Arity, Pats, Meta, Line),
112            [Fdoc|Docs]
113        end).
114
115do_macro(Name, Def, Meta, Line, Docs) ->
116    %% We only need the name to check for exclusion.
117    ?IF(exclude(Name, Meta),
118        Docs,
119        begin
120            Pats = get_macro_patterns(Def),
121            Mdoc = make_macro_doc(Name, Pats, Meta, Line),
122            [Mdoc|Docs]
123        end).
124
125%% exclude(Name, Arity, Meta) -> boolean().
126%% exclude(Name, Meta) -> boolean().
127%%  Return true if a function should be excluded from the docs chunk.
128%%  $handle_undefined_function/2 needs special handling as it is
129%%  automatically generated but can also be defined by the user. So we
130%%  only include it is it has user documentation.
131
132-spec exclude(Name, Arity, Meta) -> boolean() when
133      Name  :: atom(),
134      Arity :: non_neg_integer(),
135      Meta  :: list().
136-spec exclude(Name, Meta) -> boolean() when
137      Name  :: atom(),
138      Meta  :: list().
139
140exclude('LFE-EXPAND-EXPORTED-MACRO', 3, _)  -> true;
141exclude('$handle_undefined_function', 2, _) ->  %Should check for doc string
142    true;
143exclude(_, _, _) -> false.
144
145exclude('MODULE', _) -> true;
146exclude(_, _)        -> false.
147
148%% get_function_patterns(LambdaForm) -> {Arity,Patterns}.
149%% get_macro_patterns(LambdaForm)    -> Patterns.
150%%  Given a {match-,}lambda form, attempt to return its patterns (or
151%%  arglist).  N.B. A guard is appended to its pattern and Patterns is
152%%  a list of lists.  A macro definition must have two args, the pattern
153%%  and the environment.
154
155-spec get_function_patterns(LambdaForm) -> {Arity,Patterns} when
156      LambdaForm :: nonempty_list(),
157      Arity      :: non_neg_integer(),
158      Patterns   :: nonempty_list({pattern(),guard()}).
159-spec get_macro_patterns(LambdaForm) -> Patterns when
160      LambdaForm :: nonempty_list(),
161      Patterns   :: nonempty_list({pattern(),guard()}).
162
163get_function_patterns([lambda,Args|_]) -> {length(Args),[{Args,[]}]};
164get_function_patterns(['match-lambda',[Pat|_]=Cl|Cls]) ->
165    {length(Pat),do_function_patterns([Cl|Cls], [])}.
166
167do_function_patterns([[Pat,['when'|Guard]|_]|Cls], Acc) ->
168    do_function_patterns(Cls, [{Pat,Guard}|Acc]);
169do_function_patterns([[Pat|_]|Cls], Acc) ->
170    do_function_patterns(Cls, [{Pat,[]}|Acc]);
171do_function_patterns([], Acc) -> reverse(Acc).
172
173get_macro_patterns([lambda,[Args,_Env]|_]) -> [Args];
174get_macro_patterns(['match-lambda'|Cls])   -> do_macro_patterns(Cls, []).
175
176do_macro_patterns([[[Pat,_Env],['when'|Guard]|_]|Cls], Acc) ->
177    do_macro_patterns(Cls, [{Pat,Guard}|Acc]);
178do_macro_patterns([[[Pat,_Env]|_]|Cls], Acc) ->
179    do_macro_patterns(Cls, [{Pat,[]}|Acc]);
180do_macro_patterns([], Acc) -> reverse(Acc).
181
182%% make_function_doc(Name, Arity, Patterns, Doc, Line) -> doc().
183%% make_macro_doc(Name, Patterns, Doc, Line) -> doc().
184%%  Convenience constructor for #doc{}, which is defined in src/lfe_doc.hrl.
185
186-spec make_function_doc(Name, Arity, Patterns, Meta, Line) -> doc() when
187      Name     :: atom(),
188      Arity    :: non_neg_integer(),
189      Patterns :: [{[],[]}],
190      Meta     :: [any()],
191      Line     :: pos_integer().
192
193-spec make_macro_doc(Name, Patterns, Meta, Line) -> doc() when
194      Name     :: atom(),
195      Patterns :: [{[],[]}],
196      Meta     :: [any()],
197      Line     :: pos_integer().
198
199make_function_doc(Name, Arity, Patterns, Meta, Line) ->
200    Docs = collect_docs(Meta, []),
201    #doc{type=function,name={Name,Arity},patterns=Patterns,doc=Docs,line=Line}.
202
203make_macro_doc(Name, Patterns, Meta, Line) ->
204    Docs = collect_docs(Meta, []),
205    #doc{type=macro,name=Name,patterns=Patterns,doc=Docs,line=Line}.
206
207string_to_binary(Str) when is_list(Str) ->
208    unicode:characters_to_binary(Str, utf8, utf8);
209string_to_binary(Bin) -> Bin.
210
211%% save_module_docs(Beam, ModDocs, CompInfo) -> Mod.
212%%  Add the "LDoc" chunk to a module's .beam binary.
213
214-spec save_module_docs(Beam, Docs, Cinfo) -> {ok,Beam} | {error,Errors} when
215      Beam   :: binary(),
216      Docs   :: {[doc()],[doc()]},
217      Cinfo  :: #cinfo{},
218      Errors :: [{_,_,_}].
219
220save_module_docs(Beam, {Mdoc,Fdocs0}, _Ci) ->
221    Fdocs1 = exports_attributes(Beam, Fdocs0),
222    %% Modified from elixir_module
223    LDoc = term_to_binary(#lfe_docs_v1{
224                             docs=Fdocs1,
225                             moduledoc=format_docs(Mdoc)
226                            }),
227    {ok,add_beam_chunk(Beam, "LDoc", LDoc)};
228save_module_docs(_, _, _) -> {error,[{none,lfe_doc,save_chunk}]}.
229
230%% exports_attributes(Beam, MacFuncDocs) -> MacFuncDocs.
231%%  Return the exported macro and function docs seeting exported=true.
232
233exports_attributes(Beam, Fdocs) ->
234    Crefs = [exports,attributes],
235    {ok,{_,[{exports,Expf},{attributes,Atts}]}} = beam_lib:chunks(Beam, Crefs),
236    Expm  = proplists:get_value('export-macro', Atts, []),
237    foldl(do_exports(Expf, Expm), [], Fdocs).
238
239%% do_exports(Expf, Expm) -> Fun.
240%%  Close over Expf and Expm then return the folding function for
241%%  exports/1.  We only included exported functions and macros.  The
242%%  export-macro attribute is not necessarily sorted.
243
244do_exports(Expf, Expm) ->
245    fun (#doc{type=function,name=FA,doc=Ds}=Doc, Docs) ->
246            ?IF(member(FA, Expf),
247                [Doc#doc{exported=true,doc=format_docs(Ds)}|Docs],
248                Docs);
249        (#doc{type=macro,name=M,doc=Ds}=Doc, Docs) ->
250            ?IF(member(M, Expm),
251                [Doc#doc{exported=true,doc=format_docs(Ds)}|Docs],
252                Docs)
253    end.
254
255%% add_beam_chunk(Bin, Id, ChunkData) -> Bin.
256%%  Add a custom chunk to a .beam binary. Modified from elixir_module.
257
258add_beam_chunk(Bin, Id, ChunkData)
259  when is_binary(Bin), is_list(Id), is_binary(ChunkData) ->
260    {ok,_,Chunks} = beam_lib:all_chunks(Bin),
261    {ok,NewBin}   = beam_lib:build_module([{Id,ChunkData}|Chunks]),
262    NewBin.
263
264%% format_docs([DocString]) -> [DocLine].
265%%  Take a list of doc strings and generate a list of indented doc
266%%  lines. Each doc string is indented separately. Should it be so?
267
268format_docs(Ds) -> lists:flatmap(fun format_doc/1, Ds).
269
270format_doc(D) ->
271    %% Split the string into separate lines, also trims trailing blanks.
272    Ls = re:split(D, <<"[ \t]*\n">>, [trim]),
273    format_doc_lines(Ls).
274
275format_doc_lines([<<>>|Ls0]) ->                 %First line empty
276    case skip_empty_lines(Ls0) of               %Skip lines until text
277        {_,[L|_]=Ls1} ->
278            C = count_spaces(L),                %Use indentation of this line
279            format_doc_lines(Ls1, C);
280        {_,[]} -> []
281    end;
282format_doc_lines([L1|Ls0]) ->                   %First line not empty
283    case skip_empty_lines(Ls0) of
284        {Els,[L|_]=Ls1} ->
285            C = count_spaces(L),                %Use indentation of this line
286            %% Include first line as is.
287            [L1] ++ Els ++ format_doc_lines(Ls1, C);
288        {Els,[]} -> [L1|Els]
289    end;
290format_doc_lines([]) -> [].
291
292format_doc_lines(Ls, C) -> lists:map(fun (L) -> skip_spaces(L, C) end, Ls).
293
294count_spaces(L) ->
295    {match,[{_,C}]} = re:run(L, <<"^ *">>, []),
296    C.
297
298skip_spaces(<<$\s,L/binary>>, C) when C > 0 ->
299    skip_spaces(L, C-1);
300skip_spaces(L, _) -> L.                         %C =:= 0 or no space
301
302skip_empty_lines(Ls) -> lists:splitwith(fun (L) -> L =:= <<>> end, Ls).
303
304%% Access functions for the module doc chunk.
305
306%% get_module_docs(Module | Binary) -> {ok,Chunk} | {error,What}.
307
308get_module_docs(Mod) when is_atom(Mod) ->
309    case code:get_object_code(Mod) of
310        {Mod,Bin,_} ->
311            get_module_chunk(Bin);
312        error -> {error,module}                 %Could not find the module
313    end;
314get_module_docs(Bin) when is_binary(Bin) ->
315    get_module_chunk(Bin).
316
317get_module_chunk(Bin) ->
318    case beam_lib:chunks(Bin, ["LDoc"], []) of
319        {ok,{_,[{"LDoc",Chunk}]}} ->
320            {ok,binary_to_term(Chunk)};
321        _ -> {error,docs}                       %Could not find the docs chunk
322    end.
323
324%% module_doc(Chunk) -> [binary()].
325%% mf_docs(Chunk) -> [MacFuncDoc].
326%% mf_doc_type(MacFuncDoc) -> function | macro.
327%% function_docs(Chunk) -> [FunctionDoc].
328%% macro_docs(Chunk) -> [MacroDoc].
329
330module_doc(#lfe_docs_v1{moduledoc=Moddoc}) -> Moddoc.
331
332mf_docs(#lfe_docs_v1{docs=Docs}) -> Docs.
333
334mf_doc_type(#doc{name={_,_}}) -> function;
335mf_doc_type(#doc{name=N}) when is_atom(N) -> macro.
336
337function_docs(Fun, Ar, #lfe_docs_v1{docs=Docs}) ->
338    case lists:keysearch({Fun,Ar}, #doc.name, Docs) of
339        {value,Fdoc} -> {ok,Fdoc};
340        false -> error
341    end.
342
343macro_docs(Mac, #lfe_docs_v1{docs=Docs}) ->
344    case lists:keysearch(Mac, #doc.name, Docs) of
345        {value,Mdoc} -> {ok,Mdoc};
346        false -> error
347    end.
348
349%% function_name(FunctionDoc) -> Name.
350%% function_arity(FunctionDoc) -> Arity.
351%% function_line(FunctionDoc) -> LineNo.
352%% function_patterns(FunctionDoc) -> [Pattern].
353%% function_doc(FunctionDoc) -> [DocString].
354%%  Extract fields from a function doc structure.
355
356function_name(#doc{name={Name,_}}) -> Name.
357function_arity(#doc{name={_,Ar}}) -> Ar.
358function_line(#doc{line=Line}) -> Line.
359function_patterns(#doc{name={_,_},patterns=Ps}) -> Ps.
360function_doc(#doc{name={_,_},doc=Ds}) -> Ds.
361
362%% macro_name(MacroDoc) -> Name.
363%% macro_line(MacroDoc) -> LineNo.
364%% macro_patterns(MacroDoc) -> [Pattern].
365%% macro_doc(MacroDoc) -> [DocString].
366%%  Extract fields from a macr doc structure.
367
368macro_name(#doc{name=Name}) -> Name.
369macro_line(#doc{line=Line}) -> Line.
370macro_patterns(#doc{name=N,patterns=Ps}) when is_atom(N) -> Ps.
371macro_doc(#doc{name=N,doc=Ds}) when is_atom(N) -> Ds.
372
373%%%===================================================================
374%%% EUnit tests
375%%%===================================================================
376
377-ifdef(EUNIT).
378parse_test_() ->
379    [ ?QC(<<"A lambda definition is parsed correctly.">>,
380          prop_lfe_doc:prop_define_lambda())
381    , ?QC(<<"A match-lambda definition is parsed correctly.">>,
382          prop_lfe_doc:prop_define_match())
383    ].
384
385pprint(_Format, [{Def,_Line}]) -> lfe_io:format(user, "~p\n", [Def]);
386pprint(Format, Data) -> lfe_io:format(user, Format, Data).
387-endif.
388