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