View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2024, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(pldoc_wiki,
   39          [ wiki_codes_to_dom/3,        % +Codes, +Args, -DOM
   40            wiki_lines_to_dom/3,        % +Lines, +Map, -DOM
   41            section_comment_header/3,   % +Lines, -Header, -RestLines
   42            summary_from_lines/2,       % +Lines, -Codes
   43            indented_lines/3,           % +Text, +PrefixChars, -Lines
   44            strip_leading_par/2,        % +DOM0, -DOM
   45            autolink_extension/2,       % ?Extension, ?Type
   46            autolink_file/2             % +FileName, -Type
   47          ]).   48:- use_module(library(lists)).   49:- use_module(library(debug)).   50:- use_module(library(error)).   51:- use_module(library(pairs)).   52:- use_module(library(option)).   53:- use_module(library(debug)).   54:- use_module(library(apply)).   55:- use_module(library(dcg/basics)).   56
   57:- use_module(doc_util).

PlDoc wiki parser

This file defines the PlDoc wiki parser, which parses both comments and wiki text files. The original version of this SWI-Prolog wiki format was largely modeled after Twiki (http://twiki.org/). The current version is extended to take many aspects from markdown, in particular the doxygen refinement thereof.

See also
- http://www.stack.nl/~dimitri/doxygen/manual/markdown.html
   71:- multifile
   72    prolog:doc_wiki_face//2,        % -Out, +VarNames
   73    prolog:doc_url_expansion/3,     % +Alias(Rest), -HREF, -Label
   74    prolog:url_expansion_hook/3,    % +Term, -Ref, -Label
   75    prolog:doc_autolink_extension/2.% +Extension, -Type
   76
   77
   78                 /*******************************
   79                 *          WIKI PARSING        *
   80                 *******************************/
 wiki_lines_to_dom(+Lines:lines, +Args:list(atom), -Term) is det
Translate a Wiki text into an HTML term suitable for html//1 from the html_write library.
   87wiki_lines_to_dom(Lines, Args, HTML) :-
   88    tokenize_lines(Lines, Tokens0),
   89    normalise_indentation(Tokens0, Tokens),
   90    wiki_structure(Tokens, -1, Pars),
   91    wiki_faces(Pars, Args, HTML).
 wiki_codes_to_dom(+String, +Args, -DOM) is det
Translate a plain text into a DOM term.
Arguments:
String- Plain text. Either a string or a list of codes.
  100wiki_codes_to_dom(Codes, Args, DOM) :-
  101    indented_lines(Codes, [], Lines),
  102    wiki_lines_to_dom(Lines, Args, DOM).
 wiki_structure(+Lines:lines, +BaseIndent, -Blocks:list(block)) is det
Get the structure in terms of block-level elements: paragraphs, lists and tables. This processing uses a mixture of layout and punctuation.
  112wiki_structure([], _, []) :- !.
  113wiki_structure([_-[]|T], BI, Pars) :-          % empty lines
  114    !,
  115    wiki_structure(T, BI, Pars).
  116wiki_structure(Lines, BI, [\tags(Tags)|Rest]) :-
  117    tags(Lines, Tags, RestLines),
  118    !,
  119    wiki_structure(RestLines, BI, Rest).
  120wiki_structure(Lines, BI, [P1|PL]) :-
  121    take_block(Lines, BI, P1, RestLines),
  122    wiki_structure(RestLines, BI, PL).
 take_block(+Lines, +BaseIndent, ?Block, -RestLines) is semidet
Take a block-structure from the input. Defined block elements are lists, table, hrule, section header and paragraph.
  129take_block([_-[]|Lines], BaseIndent, Block, Rest) :-
  130    !,
  131    take_block(Lines, BaseIndent, Block, Rest).
  132take_block([N-_|_], BaseIndent, _, _) :-
  133    N < BaseIndent,
  134    !,
  135    fail.                           % less indented
  136take_block(Lines, BaseIndent, List, Rest) :-
  137    list_item(Lines, Type, Indent, LI, LIT, Rest0),
  138    !,
  139    Indent > BaseIndent,
  140    rest_list(Rest0, Type, Indent, LIT, [], Rest),
  141    List0 =.. [Type, LI],
  142    (   ul_to_dl(List0, List)
  143    ->  true
  144    ;   List0 = dl(Items)
  145    ->  List = dl(class=wiki, Items)
  146    ;   List = List0
  147    ).
  148take_block([N-['|'|RL1]|LT], _, Table, Rest) :-
  149    phrase(row(R0), RL1),
  150    take_table(LT, N, R0, Table, Rest),
  151    !.
  152take_block([0-[-,-|More]|LT], _, Block, LT) :-  % separation line
  153    maplist(=(-), More),
  154    !,
  155    Block = hr([]).
  156take_block([_-Line|LT], _, Block, LT) :-        % separation line
  157    ruler(Line),
  158    !,
  159    Block = hr([]).
  160take_block([_-[@|_]], _, _, _) :-              % starts @tags section
  161    !,
  162    fail.
  163take_block(Lines, _BaseIndent, Section, RestLines) :-
  164    section_header(Lines, Section, RestLines),
  165    !.
  166take_block([_-Verb|Lines], _, Verb, Lines) :-
  167    verbatim_term(Verb),
  168    !.
  169take_block([I-L1|LT], BaseIndent, Elem, Rest) :-
  170    !,
  171    append(L1, PT, Par),
  172    rest_par(LT, PT, I, BaseIndent, MaxI, Rest),
  173    (   MaxI >= BaseIndent+16
  174    ->  Elem = center(Par)
  175    ;   phrase(blockquote(BQ), Par)
  176    ->  Elem = blockquote(BQ)
  177    ;   Elem = p(Par)
  178    ).
  179take_block([Verb|Lines], _, Verb, Lines).
  180
  181blockquote(Clean) -->
  182    [>, ' '],
  183    bq_lines(Clean).
  184
  185bq_lines([' '|Par]) -->
  186    ['\n'], !, [>,' '],
  187    bq_lines(Par).
  188bq_lines([H|T]) -->
  189    [H],
  190    bq_lines(T).
  191bq_lines([]) -->
  192    [].
 ruler(+Line) is semidet
True if Line contains 3 ruler chars and otherwise spaces.
  199ruler([C0|Line]) :-
  200    rule_char(C0),
  201    phrase(ruler(C0, 1), Line).
  202
  203ruler(C, N) --> [C], !, { N2 is N+1 }, ruler(C, N2).
  204ruler(C, N) --> [' '], !, ruler(C, N).
  205ruler(_, N) --> { N >= 3 }.
  206
  207rule_char('-').
  208rule_char('_').
  209rule_char('*').
 list_item(+Lines, ?Type, ?Indent, -LI0, -LIT, -RestLines) is det
Create a list-item. Naturally this should produce a single item, but DL lists produce two items, so we create the list of items as a difference list.
To be done
- Pass base-indent
  219list_item([Indent-Line|LT], Type, Indent, Items, ItemT, Rest) :-
  220    !,
  221    list_item_prefix(Type, Line, L1),
  222    (   Type == dl
  223    ->  split_dt(L1, DT0, DD1),
  224        append(DD1, LIT, DD),
  225        strip_ws_tokens(DT0, DT),
  226        Items = [dt(DT),dd(DD)|ItemT]
  227    ;   append(L1, LIT, LI0),
  228        Items = [li(LI0)|ItemT]
  229    ),
  230    rest_list_item(LT, Type, Indent, LIT, Rest).
 rest_list_item(+Lines, +Type, +Indent, -RestItem, -RestLines) is det
Extract the remainder (after the first line) of a list item.
  236rest_list_item(Lines, _Type, Indent, RestItem, RestLines) :-
  237    take_blocks_at_indent(Lines, Indent, Blocks, RestLines),
  238    (   Blocks = [p(Par)|MoreBlocks]
  239    ->  append(['\n'|Par], MoreBlocks, RestItem)
  240    ;   RestItem = Blocks
  241    ).
 take_blocks_at_indent(+Lines, +Indent, -Pars, -RestLines) is det
Process paragraphs and verbatim blocks (==..==) in bullet-lists.
  247take_blocks_at_indent(Lines, _, [], Lines) :-
  248    skip_empty_lines(Lines, Lines1),
  249    section_header(Lines1, _, _),
  250    !.
  251take_blocks_at_indent(Lines, N, [Block|RestBlocks], RestLines) :-
  252    take_block(Lines, N, Block, Rest0),
  253    !,
  254    take_blocks_at_indent(Rest0, N, RestBlocks, RestLines).
  255take_blocks_at_indent(Lines, _, [], Lines).
 rest_list(+Lines, +Type, +Indent, -Items, -ItemTail, -RestLines) is det
  261rest_list(Lines, Type, N, Items, IT, Rest) :-
  262    skip_empty_lines(Lines, Lines1),
  263    list_item(Lines1, Type, N, Items, IT0, Rest0),
  264    !,
  265    rest_list(Rest0, Type, N, IT0, IT, Rest).
  266rest_list(Rest, _, _, IT, IT, Rest).
 list_item_prefix(?Type, +Line, -Rest) is det
  270list_item_prefix(ul, [*, ' '|T], T) :- !.
  271list_item_prefix(ul, [-, ' '|T], T) :- !.
  272list_item_prefix(dl, [$, ' '|T], T) :-
  273    split_dt(T, _, _),
  274    !.
  275list_item_prefix(ol, [w(N), '.', ' '|T], T) :-
  276    atom_codes(N, [D]),
  277    between(0'0, 0'9, D).
 split_dt(+LineAfterDollar, -DT, -Rest)
First see whether the entire line is the item. This allows creating items holding : by using $ <tokens> :\n
  284split_dt(In, DT, []) :-
  285    append(DT, [':'], In),
  286    !.
  287split_dt(In, DT, Rest) :-
  288    append(DT, [':'|Rest0], In),
  289    (   Rest0 == []
  290    ->  Rest = []
  291    ;   Rest0 = [' '|Rest]
  292    ),
  293    !.
 ul_to_dl(+UL, -DL) is semidet
Translate an UL list into a DL list if all entries are of the form "* <term> nl, <description>" and at least one <description> is non-empty, or all items are of the form [[PredicateIndicator]].
  303ul_to_dl(ul(Items), Description) :-
  304    term_items(Items, DLItems, []),
  305    (   terms_to_predicate_includes(DLItems, Preds)
  306    ->  Description = dl(class(predicates), Preds)
  307    ;   member(dd(DD), DLItems), DD \== []
  308    ->  Description = dl(class(termlist), DLItems)
  309    ).
  310
  311term_items([], T, T).
  312term_items([LI|LIs], DLItems, Tail) :-
  313    term_item(LI, DLItems, Tail1),
  314    term_items(LIs, Tail1, Tail).
 term_item(+LI, -DLItem, ?Tail) is semidet
If LI is of the form <Term> followed by a newline, return it as dt-dd tuple. The <dt> item contains a term
\term(Text, Term, Bindings).
  323term_item(li(Tokens),
  324          [ dt(class=term, \term(Text, Term, Bindings)),
  325            dd(Descr)
  326          | Tail
  327          ], Tail) :-
  328    (   (   append(TermTokens, ['\n'|Descr], Tokens)
  329        ->  true
  330        ;   TermTokens = Tokens,
  331            Descr = []
  332        )
  333    ->  with_output_to(string(Tmp),
  334                       ( forall(member(T, TermTokens),
  335                                write_token(T)),
  336                         write(' .\n'))),
  337        E = error(_,_),
  338        catch(setup_call_cleanup(
  339                  open_string(Tmp, In),
  340                  ( read_dt_term(In, Term, Bindings),
  341                    read_dt_term(In, end_of_file, []),
  342                    atom_string(Text, Tmp)
  343                  ),
  344                  close(In)),
  345              E, fail)
  346    ).
  347
  348write_token(w(X)) :-
  349    !,
  350    write(X).
  351write_token(X) :-
  352    write(X).
  353
  354read_dt_term(In, Term, Bindings) :-
  355    read_term(In, Term,
  356              [ variable_names(Bindings),
  357                module(pldoc_modes)
  358              ]).
  359
  360terms_to_predicate_includes([], []).
  361terms_to_predicate_includes([dt(class=term, \term(_, [[PI]], [])), dd([])|T0],
  362                            [\include(PI, predicate, [])|T]) :-
  363    is_pi(PI),
  364    terms_to_predicate_includes(T0, T).
  365
  366is_pi(Name/Arity) :-
  367    atom(Name),
  368    integer(Arity),
  369    between(0, 20, Arity).
  370is_pi(Name//Arity) :-
  371    atom(Name),
  372    integer(Arity),
  373    between(0, 20, Arity).
 row(-Cells)// is det
  378row([C0|CL]) -->
  379    cell(C0),
  380    !,
  381    row(CL).
  382row([]) -->
  383    [].
  384
  385cell(td(C)) -->
  386    face_tokens(C0),
  387    ['|'],
  388    !,
  389    { strip_ws_tokens(C0, C)
  390    }.
  391
  392face_tokens([]) -->
  393    [].
  394face_tokens(Tokens) -->
  395    face_token(H),                          % Deal with embedded *|...|*, etc.
  396    token('|'),
  397    face_tokens(Face),
  398    token('|'),
  399    face_token(H),
  400    !,
  401    { append([[H,'|'], Face, ['|', H], Rest], Tokens) },
  402    face_tokens(Rest).
  403face_tokens([H|T]) -->
  404    token(H),
  405    face_tokens(T).
  406
  407face_token(=) --> [=].
  408face_token(*) --> [*].
  409face_token('_') --> ['_'].
  410
  411take_table(Lines, Indent, Row0, Table, Rest) :-
  412    rest_table(Lines, Indent, Rows, Rest),
  413    (   Rows = [align(Align)|Rows1]
  414    ->  maplist(align_row(Align), Rows1, Rows2),
  415        (   maplist(=(td([])), Row0)        % empty header
  416        ->  Table = table(class=wiki, Rows2)
  417        ;   maplist(td_to_th, Row0, Header),
  418            Table = table(class=wiki, [tr(Header)|Rows2])
  419        )
  420    ;   Table = table(class=wiki, [tr(Row0)|Rows])
  421    ).
  422
  423td_to_th(td(X), th(X)) :- !.
  424td_to_th(X, X).
  425
  426align_row(Align, tr(Row0), tr(Row)) :-
  427    align_cells(Align, Row0, Row).
  428
  429align_cells([Align|AT], [Cell0|T0], [Cell|T]) :-
  430    align_cell(Align, Cell0, Cell),
  431    align_cells(AT, T0, T).
  432align_cells(_, Cells, Cells).
  433
  434align_cell(Align, td(Content), td(class=Align, Content)).
 rest_table(+Lines, +Indent, -Rows, -RestLines)
  438rest_table([N-Line|LT], N, [align(Align)|RL], Rest) :-
  439    phrase(column_alignment(Align), Line),
  440    !,
  441    rest_table2(LT, N, RL, Rest).
  442rest_table(Lines, N, RL, Rest) :-
  443    rest_table2(Lines, N, RL, Rest).
  444
  445rest_table2([N-['|'|RL1]|LT], N, [tr(R0)|RL], Rest) :-
  446    !,
  447    phrase(row(R0), RL1),
  448    rest_table2(LT, N, RL, Rest).
  449rest_table2(Rest, _, [], Rest).
 column_alignment(-Alignment) is semidet
Process an alignment line.
  455column_alignment([H|T]) -->
  456    ['|'],
  457    (   colspec(H)
  458    ->  column_alignment(T)
  459    ;   {T=[]}
  460    ).
  461
  462colspec(Align) -->
  463    ws_tokens, [':'], dashes3,
  464    (   [':']
  465    ->  {Align = center}
  466    ;   {Align = left}
  467    ),
  468    ws_tokens.
  469colspec(Align) -->
  470    ws_tokens, dashes3,
  471    (   [':']
  472    ->  {Align = right}
  473    ;   {Align = left}
  474    ),
  475    ws_tokens.
  476
  477dashes3 -->
  478    [-,-,-],
  479    dashes.
  480
  481dashes --> [-], !, dashes.
  482dashes --> [].
  483
  484ws_tokens --> [' '], !, ws_tokens.
  485ws_tokens --> [].
 rest_par(+Lines, -Par, +BaseIndent, +MaxI0, -MaxI, -RestLines) is det
Take the rest of a paragraph. Paragraphs are ended by a blank line or the start of a list-item. The latter is a bit dubious. Why not a general block-level object? The current definition allows for writing lists without a blank line between the items.
  495rest_par([], [], BI, MaxI0, MaxI, []) :-
  496    !,
  497    MaxI is max(BI, MaxI0).
  498rest_par([_-[]|Rest], [], _, MaxI, MaxI, Rest) :- !.
  499rest_par(Lines, [], _, MaxI, MaxI, Lines) :-
  500    Lines = [_-Verb|_],
  501    verbatim_term(Verb),
  502    !.
  503rest_par([I-L|Rest], [], _, MaxI, MaxI, [I-L|Rest]) :-
  504    list_item_prefix(_, L, _),
  505    !.
  506rest_par([I-L1|LT], ['\n'|Par], BI, MaxI0, MaxI, Rest) :-
  507    append(L1, PT, Par),
  508    MaxI1 is max(I, MaxI0),
  509    rest_par(LT, PT, BI, MaxI1, MaxI, Rest).
 section_header(+Lines, -Section, -RestLines) is semidet
Get a section line from the input.
  516section_header([_-L1|LT], Section, LT) :-
  517    twiki_section_line(L1, Section),
  518    !.
  519section_header([0-L1|LT], Section, LT) :-
  520    md_section_line(L1, Section),
  521    !.
  522section_header([_-L1,0-L2|LT], Section, LT) :-
  523    md_section_line(L1, L2, Section),
  524    !.
 twiki_section_line(+Tokens, -Section) is semidet
Extract a section using the Twiki conventions. The section may be preceeded by [Word], in which case we generate an anchor name Word for the section.
  532twiki_section_line([-,-,-|Rest], Section) :-
  533    plusses(Rest, Section).
  534
  535plusses([+, ' '|Rest], h1(Attrs, Content)) :-
  536    hdr_attributes(Rest, Attrs, Content).
  537plusses([+, +, ' '|Rest], h2(Attrs, Content)) :-
  538    hdr_attributes(Rest, Attrs, Content).
  539plusses([+, +, +, ' '|Rest], h3(Attrs, Content)) :-
  540    hdr_attributes(Rest, Attrs, Content).
  541plusses([+, +, +, +, ' '|Rest], h4(Attrs, Content)) :-
  542    hdr_attributes(Rest, Attrs, Content).
  543
  544hdr_attributes(List, Attrs, Content) :-
  545    strip_leading_ws(List, List2),
  546    (   List2 = ['[',w(Name),']'|List3]
  547    ->  strip_ws_tokens(List3, Content),
  548        Attrs = [class(wiki), id(Name)]
  549    ;   Attrs = class(wiki),
  550        strip_ws_tokens(List, Content)
  551    ).
 md_section_line(+Tokens, -Section) is semidet
Handle markdown section lines staring with #
  557md_section_line([#, ' '|Rest], h1(Attrs, Content)) :-
  558    md_section_attributes(Rest, Attrs, Content).
  559md_section_line([#, #, ' '|Rest], h2(Attrs, Content)) :-
  560    md_section_attributes(Rest, Attrs, Content).
  561md_section_line([#, #, #, ' '|Rest], h3(Attrs, Content)) :-
  562    md_section_attributes(Rest, Attrs, Content).
  563md_section_line([#, #, #, #, ' '|Rest], h4(Attrs, Content)) :-
  564    md_section_attributes(Rest, Attrs, Content).
  565
  566md_section_attributes(List, Attrs, Content) :-
  567    phrase((tokens(Content), [' '], section_label(Label)), List),
  568    !,
  569    Attrs = [class(wiki), id(Label)].
  570md_section_attributes(Content, Attrs, Content) :-
  571    Attrs = [class(wiki)].
  572
  573section_label(Label) -->
  574    [ '{', '#', w(Name) ],
  575    label_conts(Cont), ['}'],
  576    !,
  577    { atomic_list_concat([Name|Cont], Label) }.
  578
  579label_conts([H|T]) --> label_cont(H), !, label_conts(T).
  580label_conts([]) --> [].
  581
  582label_cont(-)    --> [-].
  583label_cont('_')  --> ['_'].
  584label_cont(Name) --> [w(Name)].
  585
  586
  587md_section_line(Line1, Line2, Header) :-
  588    Line1 \== [],
  589    section_underline(Line2, Type),
  590    is_list(Line1),
  591    phrase(wiki_words(_), Line1),  % Should not have structure elements
  592    !,
  593    (   phrase(labeled_section_line(Title, Attrs), Line1)
  594    ->  true
  595    ;   Title = Line1,
  596        Attrs = []
  597    ),
  598    Header =.. [Type, [class(wiki)|Attrs], Title].
  599
  600section_underline([=,=,=|T], h1) :-
  601    maplist(=(=), T),
  602    !.
  603section_underline([-,-,-|T], h2) :-
  604    maplist(=(-), T),
  605    !.
  606
  607labeled_section_line(Title, Attrs) -->
  608    tokens(Title), [' '], section_label(Label),
  609    !,
  610    { Attrs = [id(Label)] }.
 strip_ws_tokens(+Tokens, -Stripped)
Strip leading and trailing whitespace from a token list. Note the the whitespace is already normalised.
  618strip_ws_tokens([' '|T0], T) :-
  619    !,
  620    strip_ws_tokens(T0, T).
  621strip_ws_tokens(L0, L) :-
  622    append(L, [' '], L0),
  623    !.
  624strip_ws_tokens(L, L).
 strip_leading_ws(+Tokens, -Stripped) is det
Strip leading whitespace from a token list.
  631strip_leading_ws([' '|T], T) :- !.
  632strip_leading_ws(T, T).
  633
  634
  635                 /*******************************
  636                 *             TAGS             *
  637                 *******************************/
 tags(+Lines:lines, -Tags, -RestLines) is semidet
If the first line is a @tag, read the leading run of @tag lines into Tags and return the remaining lines (e.g., a section header ending the tag block) as RestLines. Fails if no tag is collected, so the caller falls through to normal block parsing.
  646tags(Lines, Tags, RestLines) :-
  647    collect_tags(Lines, Tags0, RestLines),
  648    Tags0 \== [],
  649    keysort(Tags0, Tags1),
  650    pairs_values(Tags1, Tags2),
  651    combine_tags(Tags2, Tags).
 collect_tags(+IndentedLines, -Tags, -RestLines) is semidet
Create a list Order-tag(Tag,Tokens) for each @tag encountered. Order is the desired position as defined by tag_order/2. Stops at the first non-@ line (returned in RestLines) so that a following section header or block element closes the tag list instead of being slurped into the last tag's content.
To be done
- Tag content is often poorly aligned. We now find the alignment of subsequent lines and assume the first line is alligned with the remaining lines.
  665collect_tags([], [], []) :- !.
  666collect_tags([Indent-[@,String|L0]|Lines], [Order-tag(Tag,Value)|Tags], RestLines) :-
  667    tag_name(String, Tag, Order),
  668    !,
  669    strip_leading_ws(L0, L),
  670    rest_tag(Lines, Indent, VT, RestLines0),
  671    normalise_indentation(VT, VT1),
  672    wiki_structure([0-L|VT1], -1, Value0),
  673    strip_leading_par(Value0, Value),
  674    collect_tags(RestLines0, Tags, RestLines).
  675collect_tags(Lines, [], Lines) :-
  676    Lines \= [_-[@,_|_]|_].
 tag_name(+String, -Tag:atom, -Order:int) is semidet
If String denotes a know tag-name,
  683tag_name(w(Name), Tag, Order) :-
  684    (   renamed_tag(Name, Tag, Level),
  685        tag_order(Tag, Order)
  686    ->  print_message(Level, pldoc(deprecated_tag(Name, Tag)))
  687    ;   tag_order(Name, Order)
  688    ->  Tag = Name
  689    ;   print_message(warning, pldoc(unknown_tag(Name))),
  690        fail
  691    ).
  692
  693
  694rest_tag([], _, [], []) :- !.
  695rest_tag(Lines, Indent, [], Lines) :-
  696    Lines = [Indent-[@,Word|_]|_],
  697    tag_name(Word, _, _),
  698    !.
  699rest_tag(Lines, _, [], Lines) :-
  700    section_header(Lines, _, _),
  701    !.
  702rest_tag([L|Lines0], Indent, [L|VT], Lines) :-
  703    rest_tag(Lines0, Indent, VT, Lines).
 renamed_tag(+DeprecatedTag:atom, -Tag:atom, -Warn) is semidet
Declaration for deprecated tags.
  710renamed_tag(exception, throws, warning).
  711renamed_tag(param,     arg,    silent).
 tag_order(+Tag:atom, -Order:int) is semidet
Both declares the know tags and their expected order. Currently the tags are forced into this order without warning. Future versions may issue a warning if the order is inconsistent.
  720:- multifile
  721    pldoc:tag_order/2.  722
  723tag_order(Tag, Order) :-
  724    pldoc:tag_order(Tag, Order),
  725    !.
  726tag_order(arg,         100).
  727tag_order(error,       200).            % same as throw
  728tag_order(throws,      300).
  729tag_order(author,      400).
  730tag_order(version,     500).
  731tag_order(see,         600).
  732tag_order(deprecated,  700).
  733tag_order(compat,      800).            % PlDoc extension
  734tag_order(copyright,   900).
  735tag_order(license,    1000).
  736tag_order(bug,        1100).
  737tag_order(tbd,        1200).
  738tag_order(since,      1300).
 combine_tags(+Tags:list(tag(Key,Value)), -Tags:list) is det
Creates the final tag-list. Tags is a list of

Descr is a list of tokens.

  749combine_tags([], []).
  750combine_tags([tag(arg, V1)|T0], [\args([P1|PL])|Tags]) :-
  751    !,
  752    arg_tag(V1, P1),
  753    arg_tags(T0, PL, T1),
  754    combine_tags(T1, Tags).
  755combine_tags([tag(Tag,V0)|T0], [\tag(Tag, [V0|Vs])|T]) :-
  756    same_tag(Tag, T0, T1, Vs),
  757    combine_tags(T1, T).
  758
  759arg_tag([PT|Descr0], arg(PN, Descr)) :-
  760    word_of(PT, PN),
  761    strip_leading_ws(Descr0, Descr).
  762
  763word_of(w(W), W) :- !.                  % TBD: check non-word arg
  764word_of(W, W).
  765
  766arg_tags([tag(arg, V1)|T0], [P1|PL], T) :-
  767    !,
  768    arg_tag(V1, P1),
  769    arg_tags(T0, PL, T).
  770arg_tags(T, [], T).
  771
  772same_tag(Tag, [tag(Tag, V)|T0], T, [V|Vs]) :-
  773    !,
  774    same_tag(Tag, T0, T, Vs).
  775same_tag(_, L, L, []).
  776
  777
  778                 /*******************************
  779                 *             FACES            *
  780                 *******************************/
 wiki_faces(+Structure, +ArgNames, -HTML) is det
Given the wiki structure, analyse the content of the paragraphs, list items and table cells and apply font faces and links.
  787wiki_faces([dt(Class, \term(Text, Term, Bindings)), dd(Descr0)|T0],
  788           ArgNames,
  789           [dt(Class, \term(Text, Term, Bindings)), dd(Descr)|T]) :-
  790    !,
  791    varnames(Bindings, VarNames, ArgNames),
  792    wiki_faces(Descr0, VarNames, Descr),
  793    wiki_faces(T0, ArgNames, T).
  794wiki_faces(DOM0, ArgNames, DOM) :-
  795    structure_term(DOM0, Functor, Content0),
  796    !,
  797    wiki_faces_list(Content0, ArgNames, Content),
  798    structure_term(DOM, Functor, Content).
  799wiki_faces(Verb, _, Verb) :-
  800    verbatim_term(Verb),
  801    !.
  802wiki_faces(Content0, ArgNames, Content) :-
  803    assertion(is_list(Content0)),
  804    phrase(wiki_faces(Content, ArgNames), Content0),
  805    !.
  806
  807varnames([], List, List).
  808varnames([Name=_|T0], [Name|T], List) :-
  809    varnames(T0, T, List).
  810
  811wiki_faces_list([], _, []).
  812wiki_faces_list([H0|T0], Args, [H|T]) :-
  813    wiki_faces(H0, Args, H),
  814    wiki_faces_list(T0, Args, T).
 structure_term(+Term, -Functor, -Content) is semidet
structure_term(-Term, +Functor, +Content) is det
(Un)pack a term describing structure, so we can process Content and re-pack the structure.
  822structure_term(\tags(Tags), tags, [Tags]) :- !.
  823structure_term(\args(Params), args, [Params]) :- !.
  824structure_term(arg(Name,Descr), arg(Name), [Descr]) :- !.
  825structure_term(\tag(Name,Value), tag(Name), [Value]) :- !.
  826structure_term(\include(What,Type,Opts), include(What,Type,Opts), []) :- !.
  827structure_term(dl(Att, Args), dl(Att), [Args]) :- !.
  828structure_term(dt(Att, Args), dt(Att), [Args]) :- !.
  829structure_term(table(Att, Args), table(Att), [Args]) :- !.
  830structure_term(td(Att, Args), td(Att), [Args]) :- !.
  831structure_term(h1(Att, Args), h1(Att), [Args]) :- !.
  832structure_term(h2(Att, Args), h2(Att), [Args]) :- !.
  833structure_term(h3(Att, Args), h3(Att), [Args]) :- !.
  834structure_term(h4(Att, Args), h4(Att), [Args]) :- !.
  835structure_term(hr(Att), hr(Att), []) :- !.
  836structure_term(p(Args), p, [Args]) :- !.
  837structure_term(Term, Functor, Args) :-
  838    structure_term_any(Term, Functor, Args).
  839
  840structure_term(Term) :-
  841    structure_term_any(Term, _Functor, _Args).
  842
  843structure_term_any(Term, Functor, Args) :-
  844    functor(Term, Functor, 1),
  845    structure_tag(Functor),
  846    !,
  847    Term =.. [Functor|Args].
  848
  849structure_tag(ul).
  850structure_tag(ol).
  851structure_tag(dl).
  852structure_tag(li).
  853structure_tag(dt).
  854structure_tag(dd).
  855structure_tag(table).
  856structure_tag(tr).
  857structure_tag(td).
  858structure_tag(th).
  859structure_tag(blockquote).
  860structure_tag(center).
 verbatim_term(?Term) is det
True if Term must be passes verbatim.
  867verbatim_term(pre(_,_)).
  868verbatim_term(\term(_,_,_)).
 matches(:Goal, -Input, -Last)//
True when Goal runs successfully on the DCG input and Input is the list of matched tokens.
  875:- meta_predicate matches(2, -, -, ?, ?).  876
  877matches(Goal, Input, Last, List, Rest) :-
  878    call(Goal, List, Rest),
  879    input(List, Rest, Input, Last).
  880
  881input([H|T0], Rest, Input, Last) :-
  882    (   T0 == Rest
  883    ->  Input = [H],
  884        Last = H
  885    ;   Input = [H|T],
  886        input(T0, Rest, T, Last)
  887    ).
 wiki_faces(-WithFaces, +ArgNames)// is nondet
 wiki_faces(-WithFaces, +ArgNames, +Options)// is nondet
Apply font-changes and automatic links to running text. The faces are applied after discovering the structure (paragraphs, lists, tables, keywords).
Arguments:
Options- is a dict, minimally containing depth
  899wiki_faces(WithFaces, ArgNames, List, Rest) :-
  900    default_faces_options(Options),
  901    catch(wiki_faces(WithFaces, ArgNames, Options, List, Rest),
  902          pldoc(depth_limit),
  903          failed_faces(WithFaces, List, Rest)).
  904
  905default_faces_options(_{depth:5}).
  906
  907failed_faces(WithFaces) -->
  908    { debug(markdown(overflow), 'Depth limit exceeded', []) },
  909    wiki_words(WithFaces).
  910
  911wiki_faces([EmphTerm|T], ArgNames, Options) -->
  912    emphasis_seq(EmphTerm, ArgNames, Options),
  913    !,
  914    wiki_faces_int(T, ArgNames).
  915wiki_faces(Faces, ArgNames, Options) -->
  916    wiki_faces_int(Faces, ArgNames, Options).
  917
  918wiki_faces_int(WithFaces, ArgNames) -->
  919    { default_faces_options(Options)
  920    },
  921    wiki_faces_int(WithFaces, ArgNames, Options).
  922
  923wiki_faces_int([], _, _) -->
  924    [].
  925wiki_faces_int(List, ArgNames, Options) -->
  926    wiki_face(H, ArgNames, Options),
  927    !,
  928    {   is_list(H)
  929    ->  append(H, T, List)
  930    ;   List = [H|T]
  931    },
  932    wiki_faces(T, ArgNames, Options).
  933wiki_faces_int([Before,EmphTerm|T], ArgNames, Options) -->
  934    emphasis_before(Before),
  935    emphasis_seq(EmphTerm, ArgNames, Options),
  936    !,
  937    wiki_faces_int(T, ArgNames, Options).
  938wiki_faces_int([H|T], ArgNames, Options) -->
  939    wiki_face_simple(H, ArgNames, Options),
  940    !,
  941    wiki_faces_int(T, ArgNames, Options).
  942
  943next_level(Options0, Options) -->
  944    {   succ(NewDepth, Options0.depth)
  945    ->  Options = Options0.put(depth, NewDepth)
  946    ;   throw(pldoc(depth_limit))
  947    }.
 prolog:doc_wiki_face(-Out, +VarNames)// is semidet
prolog:doc_wiki_face(-Out, +VarNames, +Options0)// is semidet
Hook that can be used to provide additional processing for additional inline wiki constructs. The DCG list is a list of tokens. Defined tokens are:
w(Atom)
Recognised word (alphanumerical)
Atom
Single character atom representing punctuation marks or the atom ' ' (space), representing white-space.

The Out variable is input for the backends defined in doc_latex.pl and doc_html.pl. Roughly, these are terms similar to what html//1 from library(http/html_write) accepts.

  966wiki_face(Out, Args, _) -->
  967    prolog:doc_wiki_face(Out, Args),
  968    !.
  969wiki_face(var(Arg), ArgNames, _) -->
  970    [w(Arg)],
  971    { memberchk(Arg, ArgNames)
  972    },
  973    !.
  974wiki_face(b(Bold), ArgNames, Options) -->
  975    [*,'|'], string(Tokens), ['|',*],
  976    !,
  977    { phrase(wiki_faces(Bold, ArgNames, Options), Tokens) }.
  978wiki_face(i(Italic), ArgNames, Options) -->
  979    ['_','|'], string(Tokens), ['|','_'],
  980    !,
  981    { phrase(wiki_faces(Italic, ArgNames, Options), Tokens) }.
  982wiki_face(strong(Strong), ArgNames, Options) -->
  983    ['_','_'], string(Tokens), ['_','_'],
  984    !,
  985    { phrase(wiki_faces(Strong, ArgNames, Options), Tokens) }.
  986wiki_face(strong(Strong), ArgNames, Options) -->
  987    ['*','*'], string(Tokens), ['*','*'],
  988    !,
  989    { phrase(wiki_faces(Strong, ArgNames, Options), Tokens) }.
  990wiki_face(code(Code), _, _) -->
  991    [=], eq_code_words(Words), [=],
  992    !,
  993    { atomic_list_concat(Words, Code) }.
  994wiki_face(code(Code), _, _) -->
  995    [=,'|'], wiki_words(Code), ['|',=],
  996    !.
  997wiki_face(PredRef, _, _) -->
  998    ['`'], take_predref(PredRef), ['`'],
  999    !.
 1000wiki_face(\nopredref(Pred), _, _) -->
 1001    ['`', '`'], take_predref(\predref(Pred)), ['`', '`'],
 1002    !.
 1003wiki_face([flag, ' ', \flagref(Flag)], _, _) -->
 1004    [ w('flag'), ' ', '`', w(Flag), '`' ],
 1005    { current_prolog_flag(Flag, _) },
 1006    !.
 1007wiki_face(code(Code), _, _) -->
 1008    ['`','`'], wiki_words(Code), ['`','`'],
 1009    !.
 1010wiki_face(Code, _, _) -->
 1011    (   ['`'], code_words(Words), ['`']
 1012    ->  { atomic_list_concat(Words, Text),
 1013          E = error(_,_),
 1014          catch(atom_to_term(Text, Term, Vars), E, fail),
 1015          !,
 1016          code_face(Text, Term, Vars, Code)
 1017        }
 1018    ).
 1019wiki_face(Face, _, Options) -->
 1020    [ w(Name) ], arg_list(List),
 1021    { atomic_list_concat([Name|List], Text),
 1022      E = error(_,_),
 1023      catch(atom_to_term(Text, Term, Vars), E, fail),
 1024      term_face(Text, Term, Vars, Face, Options)
 1025    },
 1026    !.
 1027wiki_face(br([]), _, _) -->
 1028    [<,w(br),>,'\n'], !.
 1029wiki_face(br([]), _, _) -->
 1030    [<,w(br),/,>,'\n'], !.
 1031        % Below this, we only do links.
 1032wiki_face(_, _, Options) -->
 1033    { Options.get(link) == false,
 1034      !,
 1035      fail
 1036    }.
 1037wiki_face(PredRef, _, _) -->
 1038    take_predref(PredRef),
 1039    !.
 1040wiki_face(\cite(Citations), _, _) -->
 1041    ['['], citations(Citations), [']'].
 1042wiki_face(\include(Name, Type, Options), _, _) -->
 1043    ['[','['], file_name(Base, Ext), [']',']'],
 1044    { autolink_extension(Ext, Type),
 1045      !,
 1046      file_name_extension(Base, Ext, Name),
 1047      resolve_file(Name, Options, [])
 1048    },
 1049    !.
 1050wiki_face(\include(Name, Type, [caption(Caption)|Options]), _, _) -->
 1051    (   ['!','['], tokens(100, Caption), [']','(']
 1052    ->  file_name(Base, Ext), [')'],
 1053        { autolink_extension(Ext, Type),
 1054          !,
 1055          file_name_extension(Base, Ext, Name),
 1056          resolve_file(Name, Options, [])
 1057        }
 1058    ),
 1059    !.
 1060wiki_face(Link, ArgNames, Options) -->          % TWiki: [[Label][Link]]
 1061    (   ['[','['], wiki_label(Label, ArgNames, Options), [']','[']
 1062    ->  wiki_link(Link, [label(Label), relative(true), end(']')]),
 1063        [']',']'], !
 1064    ).
 1065wiki_face(Link, ArgNames, Options) -->          % Markdown: [Label](Link)
 1066    (   ['['], wiki_label(Label, ArgNames, Options), [']','(']
 1067    ->  wiki_link(Link, [label(Label), relative(true), end(')')]),
 1068        [')'], !
 1069    ).
 1070wiki_face(Link, _ArgNames, _) -->
 1071    wiki_link(Link, []),
 1072    !.
 1073
 1074wiki_label(Label, _ArgNames, _Options) -->
 1075    image_label(Label).
 1076wiki_label(Label, ArgNames, Options) -->
 1077    next_level(Options, NOptions),
 1078    limit(40, wiki_faces(Label, ArgNames, NOptions.put(link,false))).
 wiki_face_simple(-Out, +ArgNames, +Options)
Skip simple (non-markup) wiki.
 1084wiki_face_simple(Word, _, _) -->
 1085    [ w(Word) ],
 1086    !.
 1087wiki_face_simple(SpaceOrPunct, _, _) -->
 1088    [ SpaceOrPunct ],
 1089    { atomic(SpaceOrPunct) },
 1090    !.
 1091wiki_face_simple(FT, ArgNames, _) -->
 1092    [Structure],
 1093    { wiki_faces(Structure, ArgNames, FT)
 1094    }.
 1095
 1096wiki_words([]) --> [].
 1097wiki_words([Word|T]) --> [w(Word)], !, wiki_words(T).
 1098wiki_words([Punct|T]) --> [Punct], {atomic(Punct)}, wiki_words(T).
 code_words(-Words)//
True when Words is the content as it appears in `code`, where `` is mapped to `.
 1105code_words([]) --> [].
 1106code_words([Word|T]) --> [w(Word)], code_words(T).
 1107code_words(CodeL) --> ['`','`'], {CodeL = ['`'|T]}, code_words(T).
 1108code_words([Punct|T]) --> [Punct], {atomic(Punct)}, code_words(T).
 eq_code_words(-Words)//
Stuff that can be between single =. This is limited to
 1119eq_code_words([Word]) -->
 1120    [ w(Word) ].
 1121eq_code_words([Word|T]) -->
 1122    [ w(Word) ], eq_code_internals(T, [End]), [w(End)].
 1123
 1124eq_code_internals(T, T) --> [].
 1125eq_code_internals([H|T], Tail) -->
 1126    eq_code_internal(H),
 1127    eq_code_internals(T, Tail).
 1128
 1129eq_code_internal(Word) -->
 1130    [w(Word)].
 1131eq_code_internal(Punct) -->
 1132    [Punct],
 1133    { eq_code_internal_punct(Punct) }.
 1134
 1135eq_code_internal_punct('.').
 1136eq_code_internal_punct('-').
 1137eq_code_internal_punct(':').
 1138eq_code_internal_punct('/').
 code_face(+Text, +Term, +Vars, -Code) is det
Deal with `... code ...` sequences. Text is the matched text, Term is the parsed Prolog term and Code is the resulting intermediate code.
 1147code_face(Text, Var, _, Code) :-
 1148    var(Var),
 1149    !,
 1150    Code = var(Text).
 1151code_face(Text, _, _, code(Text)).
 emphasis_seq(-Out, +ArgNames, +Options) is semidet
Recognise emphasis sequences
 1158emphasis_seq(EmphTerm, ArgNames, Options) -->
 1159    emphasis_start(C),
 1160    next_level(Options, NOptions),
 1161    matches(limit(100, wiki_faces(Emph, ArgNames, NOptions)), Input, Last),
 1162    emphasis_end(C),
 1163    { emph_markdown(Last, Input),
 1164      emphasis_term(C, Emph, EmphTerm)
 1165    },
 1166    !.
 emphasis_term(+Emphasis, +Tokens, -Term) is det
 emphasis_before(-Before)// is semidet
 emphasis_start(-Emphasis)// is semidet
 emphasis_end(+Emphasis)// is semidet
Primitives for Doxygen emphasis handling.
 1176emphasis_term('_',   Term, i(Term)).
 1177emphasis_term('*',   Term, b(Term)).
 1178
 1179emph_markdown(_, [w(_)]) :- !.
 1180emph_markdown(Last, Tokens) :-
 1181    \+ emphasis_after_sep(Last),
 1182    E = error(_,_),
 1183    catch(b_getval(pldoc_object, Obj), E, Obj = '??'),
 1184    debug(markdown(emphasis), '~q: additionally emphasis: ~p',
 1185          [Obj, Tokens]).
 1186
 1187emphasis_before(Before) -->
 1188    [Before],
 1189    { emphasis_start_sep(Before) }.
 1190
 1191emphasis_start_sep('\n').
 1192emphasis_start_sep(' ').
 1193emphasis_start_sep('<').
 1194emphasis_start_sep('{').
 1195emphasis_start_sep('(').
 1196emphasis_start_sep('[').
 1197emphasis_start_sep(',').
 1198emphasis_start_sep(':').
 1199emphasis_start_sep(';').
 1200
 1201emphasis_start(Which), [w(Word)] -->
 1202    emphasis(Which),
 1203    [w(Word)].
 1204
 1205emphasis(**)   --> [*, *].
 1206emphasis(*)    --> [*].
 1207emphasis('__') --> ['_', '_'].
 1208emphasis('_')  --> ['_'].
 1209
 1210emphasis_end(Which), [After] -->
 1211    emphasis(Which),
 1212    [ After ],
 1213    !,
 1214    { emphasis_close_sep(After) -> true }.
 1215emphasis_end(Which) -->
 1216    emphasis(Which).
 1217
 1218% these characters should not be before a closing * or _.
 1219
 1220emphasis_after_sep('\n').
 1221emphasis_after_sep(' ').
 1222emphasis_after_sep('(').
 1223emphasis_after_sep('[').
 1224emphasis_after_sep('<').
 1225emphasis_after_sep('=').
 1226emphasis_after_sep('+').
 1227emphasis_after_sep('\\').
 1228emphasis_after_sep('@').
 1229
 1230emphasis_close_sep('\n').                       % white
 1231emphasis_close_sep(' ').                        % white
 1232emphasis_close_sep(',').                        % sentence punctuation
 1233emphasis_close_sep('.').
 1234emphasis_close_sep('!').
 1235emphasis_close_sep('?').
 1236emphasis_close_sep(':').
 1237emphasis_close_sep(';').
 1238emphasis_close_sep(']').                        % [**label**](link)
 1239emphasis_close_sep(')').                        % ... _italic_)
 1240emphasis_close_sep('}').                        % ... _italic_}
 1241emphasis_close_sep(Token) :-
 1242    structure_term(Token).
 arg_list(-Atoms) is nondet
Atoms is a token-list for a Prolog argument list. An argument-list is a sequence of tokens '(' ... ')'.
bug
- the current implementation does not deal correctly with brackets that are embedded in quoted strings.
 1253arg_list(['('|T]) -->
 1254    ['('], arg_list_close(T, 1).
 1255
 1256arg_list_close(Tokens, Depth) -->
 1257    [')'],
 1258    !,
 1259    (   { Depth == 1 }
 1260    ->  { Tokens = [')'] }
 1261    ;   { Depth > 1 }
 1262    ->  { Tokens = [')'|More],
 1263          NewDepth is Depth - 1
 1264        },
 1265        arg_list_close(More, NewDepth)
 1266    ).
 1267arg_list_close(['('|T], Depth) -->
 1268    ['('], { NewDepth is Depth+1 },
 1269    arg_list_close(T, NewDepth).
 1270arg_list_close([H|T], Depth) -->
 1271    [w(H)],
 1272    !,
 1273    arg_list_close(T, Depth).
 1274arg_list_close([H|T], Depth) -->
 1275    [H],
 1276    arg_list_close(T, Depth).
 term_face(+Text, +Term, +Vars, -Face, +Options) is semidet
Process embedded Prolog-terms. Currently processes Alias(Arg) terms that refer to files. Future versions will also provide pretty-printing of Prolog terms.
 1285term_face(_Text, Term, _Vars, \file(Name, FileOptions), Options) :-
 1286    ground(Term),
 1287    compound(Term),
 1288    compound_name_arity(Term, Alias, 1),
 1289    user:file_search_path(Alias, _),
 1290    existing_file(Term, FileOptions, [], Options),
 1291    !,
 1292    format(atom(Name), '~q', [Term]).
 1293term_face(Text, Term, Vars, Face, _Options) :-
 1294    code_face(Text, Term, Vars, Face).
 1295
 1296untag([], []).
 1297untag([w(W)|T0], [W|T]) :-
 1298    !,
 1299    untag(T0, T).
 1300untag([H|T0], [H|T]) :-
 1301    untag(T0, T).
 image_label(-Label)//
Match File[;param=value[,param=value]*]
 1307image_label(\include(Name, image, Options)) -->
 1308    file_name(Base, Ext),
 1309    { autolink_extension(Ext, image),
 1310      file_name_extension(Base, Ext, Name),
 1311      resolve_file(Name, Options, RestOptions)
 1312    },
 1313    file_options(RestOptions).
 1314
 1315
 1316take_predref(\predref(Name/Arity)) -->
 1317    [ w(Name), '/' ], arity(Arity),
 1318    { functor_name(Name)
 1319    }.
 1320take_predref(\predref(Module:(Name/Arity))) -->
 1321    [ w(Module), ':', w(Name), '/' ], arity(Arity),
 1322    { functor_name(Name)
 1323    }.
 1324take_predref(\predref(Name/Arity)) -->
 1325    prolog_symbol_char(S0),
 1326    symbol_string(SRest), [ '/' ], arity(Arity),
 1327    !,
 1328    { atom_chars(Name, [S0|SRest])
 1329    }.
 1330take_predref(\predref(Name//Arity)) -->
 1331    [ w(Name), '/', '/' ], arity(Arity),
 1332    { functor_name(Name)
 1333    }.
 1334take_predref(\predref(Module:(Name//Arity))) -->
 1335    [ w(Module), ':', w(Name), '/', '/' ], arity(Arity),
 1336    { functor_name(Name)
 1337    }.
 file_options(-Options) is det
Extracts additional processing options for files. The format is ;name="value",name2=value2,... Spaces are not allowed.
 1344file_options(Options) -->
 1345    [;], nv_pairs(Options),
 1346    !.
 1347file_options([]) -->
 1348    [].
 1349
 1350nv_pairs([H|T]) -->
 1351    nv_pair(H),
 1352    (   [',']
 1353    ->  nv_pairs(T)
 1354    ;   {T=[]}
 1355    ).
 1356
 1357nv_pair(Option) -->
 1358    [ w(Name), =,'"'], tokens(Tokens), ['"'],
 1359    !,
 1360    { untag(Tokens, Atoms),
 1361      atomic_list_concat(Atoms, Value0),
 1362      (   atom_number(Value0, Value)
 1363      ->  true
 1364      ;   Value = Value0
 1365      ),
 1366      Option =.. [Name,Value]
 1367    }.
 wiki_link(-Link, +Options)// is semidet
True if we can find a link to a file or URL. Links are described as one of:
filename
A filename defined using autolink_file/2 or autolink_extension/2
<url-protocol>://<rest-url>
A fully qualified URL
'<' URL '>'
Be more relaxed on the URL specification.
 1383:- multifile
 1384    user:url_path/2. 1385
 1386wiki_link(\file(Name, FileOptions), Options) -->
 1387    file_name(Base, Ext),
 1388    { file_name_extension(Base, Ext, Name),
 1389      (   autolink_file(Name, _)
 1390      ;   autolink_extension(Ext, _)
 1391      ),
 1392      !,
 1393      resolve_file(Name, FileOptions, Options)
 1394    }.
 1395wiki_link(\file(Name, FileOptions), Options) -->
 1396    [w(Name)],
 1397    { autolink_file(Name, _),
 1398      !,
 1399      resolve_file(Name, FileOptions, Options)
 1400    },
 1401    !.
 1402wiki_link(a(href(Ref), Label), Options) -->
 1403    [ w(Prot),:,/,/], { url_protocol(Prot) },
 1404    { option(end(End), Options, space)
 1405    },
 1406    tokens_no_whitespace(Rest), peek_end_url(End),
 1407    !,
 1408    { atomic_list_concat([Prot, :,/,/ | Rest], Ref),
 1409      option(label(Label), Options, Ref)
 1410    }.
 1411wiki_link(a(href(Ref), Label), Options) -->
 1412    [ w(mailto),:],
 1413    { option(end(End), Options, space)
 1414    },
 1415    tokens_no_whitespace(Rest), peek_end_url(End),
 1416    !,
 1417    { atomic_list_concat([mailto, : | Rest], Ref),
 1418      option(label(Label), Options, Ref)
 1419    }.
 1420wiki_link(a(href(Ref), Label), _Options) -->
 1421    [<, w(Alias), :],
 1422    tokens_no_whitespace(Rest), [>],
 1423    { Term = (Alias:Rest),
 1424      prolog:url_expansion_hook(Term, Ref, Label), !
 1425    }.
 1426wiki_link(a(href(Ref), Label), Options) -->
 1427    [<, w(Alias), :],
 1428    { user:url_path(Alias, _)
 1429    },
 1430    tokens_no_whitespace(Rest), [>],
 1431    { atomic_list_concat(Rest, Local),
 1432      (   Local == ''
 1433      ->  Term =.. [Alias,'.']
 1434      ;   Term =.. [Alias,Local]
 1435      ),
 1436      E = error(_,_),
 1437      catch(expand_url_path(Term, Ref), E, fail),
 1438      option(label(Label), Options, Ref)
 1439    }.
 1440wiki_link(a(href(Ref), Label), Options) -->
 1441    [#, w(First)],
 1442    { option(end(End), Options) },
 1443    tokens_no_whitespace(Rest),
 1444    peek_end_url(End),
 1445    !,
 1446    { atomic_list_concat([#,First|Rest], Ref),
 1447      option(label(Label), Options, Ref)
 1448    }.
 1449wiki_link(a(href(Ref), Label), Options) -->
 1450    [<],
 1451    (   { option(relative(true), Options),
 1452          Parts = Rest
 1453        }
 1454    ->  tokens_no_whitespace(Rest)
 1455    ;   { Parts = [Prot, : | Rest]
 1456        },
 1457        [w(Prot), :], tokens_no_whitespace(Rest)
 1458    ),
 1459    [>],
 1460    !,
 1461    { atomic_list_concat(Parts, Ref),
 1462      option(label(Label), Options, Ref)
 1463    }.
 prolog:url_expansion_hook(+Term, -HREF, -Label) is semidet
This hook is called after recognising <Alias:Rest>, where Term is of the form Alias(Rest). If it succeeds, it must bind HREF to an atom or string representing the link target and Label to an html//1 expression for the label.
 file_name(-Name:atom, -Ext:atom)// is semidet
Matches a filename. A filename is defined as a sequence <segment>{/<segment}.<ext>.
 1477file_name(FileBase, Extension) -->
 1478    segment(S1),
 1479    segments(List),
 1480    ['.'], file_extension(Extension),
 1481    !,
 1482    { atomic_list_concat([S1|List], '/', FileBase) }.
 1483file_name(FileBase, Extension) -->
 1484    [w(Alias), '('],
 1485    { once(user:file_search_path(Alias, _)) },
 1486    segment(S1),
 1487    segments(List),
 1488    [')'],
 1489    !,
 1490    { atomic_list_concat([S1|List], '/', Base),
 1491      Spec =.. [Alias,Base],
 1492      absolute_file_name(Spec, Path,
 1493                         [ access(read),
 1494                           extensions([pl]),
 1495                           file_type(prolog),
 1496                           file_errors(fail)
 1497                         ]),
 1498      file_name_extension(FileBase, Extension, Path)
 1499    }.
 1500
 1501
 1502segment(..) -->
 1503    ['.','.'],
 1504    !.
 1505segment(Word) -->
 1506    [w(Word)].
 1507segment(Dir) -->
 1508    [w(Word),'.',w(d)],
 1509    { atom_concat(Word, '.d', Dir) }.
 1510
 1511segments([H|T]) -->
 1512    ['/'],
 1513    !,
 1514    segment(H),
 1515    segments(T).
 1516segments([]) -->
 1517    [].
 1518
 1519file_extension(Ext) -->
 1520    [w(Ext)],
 1521    { autolink_extension(Ext, _)
 1522    }.
 resolve_file(+Name, -FileOptions, ?RestOptions, +Options) is det
Find the actual file based on the pldoc_file global variable. If present and the file is resolvable, add an option absolute_path(Path) that reflects the current location of the file.
 1532resolve_file(Name, FileOptions, Rest) :-
 1533    existing_file(Name, FileOptions, Rest, []),
 1534    !.
 1535resolve_file(_, Options, Options).
 1536
 1537
 1538existing_file(Name, FileOptions, Rest, Options) :-
 1539    \+ Options.get(link) == false,
 1540    E = error(_,_),
 1541    catch(existing_file_p(Name, FileOptions, Rest), E, fail).
 1542
 1543existing_file_p(Name, FileOptions, Rest) :-
 1544    (   nb_current(pldoc_file, RelativeTo),
 1545        RelativeTo \== []
 1546    ->  Extra = [relative_to(RelativeTo)|Extra1]
 1547    ;   Extra = Extra1
 1548    ),
 1549    (   compound(Name)
 1550    ->  Extra1 = [file_type(prolog)]
 1551    ;   Extra1 = []
 1552    ),
 1553    absolute_file_name(Name, Path,
 1554                       [ access(read),
 1555                         file_errors(fail)
 1556                       | Extra
 1557                       ]),
 1558    FileOptions = [ absolute_path(Path) | Rest ].
 arity(-Arity:int)// is semidet
True if the next token can be interpreted as an arity. That is, refers to a non-negative integers of at most 20. Although Prolog allows for higher arities, we assume 20 is a fair maximum for user-created predicates that are documented.
 1567arity(Arity) -->
 1568    [ w(Word) ],
 1569    { E = error(_,_),
 1570      catch(atom_number(Word, Arity), E, fail),
 1571      Arity >= 0, Arity < 20
 1572    }.
 symbol_string(-String)// is nondet
Accept a sequence of Prolog symbol characters, starting with the shortest (empty) match.
 1579symbol_string([]) -->
 1580    [].
 1581symbol_string([H|T]) -->
 1582    [H],
 1583    { prolog_symbol_char(H) },
 1584    symbol_string(T).
 1585
 1586prolog_symbol_char(C) -->
 1587    [C],
 1588    { prolog_symbol_char(C) }.
 prolog_symbol_char(?Char)
True if char is classified by Prolog as a symbol char.
 1594prolog_symbol_char(#).
 1595prolog_symbol_char($).
 1596prolog_symbol_char(&).
 1597prolog_symbol_char(*).
 1598prolog_symbol_char(+).
 1599prolog_symbol_char(-).
 1600prolog_symbol_char(.).
 1601prolog_symbol_char(/).
 1602prolog_symbol_char(:).
 1603prolog_symbol_char(<).
 1604prolog_symbol_char(=).
 1605prolog_symbol_char(>).
 1606prolog_symbol_char(?).
 1607prolog_symbol_char(@).
 1608prolog_symbol_char(\).
 1609prolog_symbol_char(^).
 1610prolog_symbol_char(~).
 1611
 1612
 1613functor_name(String) :-
 1614    sub_atom(String, 0, 1, _, Char),
 1615    char_type(Char, lower).
 1616
 1617url_protocol(http).
 1618url_protocol(https).
 1619url_protocol(ftp).
 1620
 1621peek_end_url(space) -->
 1622    peek(Punct, End),
 1623    { punct_token(Punct),
 1624      space_token(End)
 1625    },
 1626    !.
 1627peek_end_url(space) -->
 1628    peek(End),
 1629    { space_token(End) },
 1630    !.
 1631peek_end_url(space, [], []) :- !.
 1632peek_end_url(Token) -->
 1633    peek(Token),
 1634    !.
 1635
 1636punct_token('.').
 1637punct_token('!').
 1638punct_token('?').
 1639punct_token(',').
 1640punct_token(';').
 1641
 1642space_token(' ') :- !.
 1643space_token('\r') :- !.
 1644space_token('\n') :- !.
 1645space_token(T) :-
 1646    \+ atom(T),                     % high level format like p(...)
 1647    \+ T = w(_).
 autolink_extension(?Ext, ?Type) is nondet
True if Ext is a filename extensions that create automatic links in the documentation.
 1654autolink_extension(Ext, Type) :-
 1655    prolog:doc_autolink_extension(Ext, Type),
 1656    !.
 1657autolink_extension(Ext, prolog) :-
 1658    user:prolog_file_type(Ext,prolog),
 1659    !.
 1660autolink_extension(txt, wiki).
 1661autolink_extension(md,  wiki).
 1662autolink_extension(gif, image).
 1663autolink_extension(png, image).
 1664autolink_extension(jpg, image).
 1665autolink_extension(jpeg, image).
 1666autolink_extension(svg, image).
 autolink_file(?File, -Type) is nondet
Files to which we automatically create links, regardless of the extension.
 1673autolink_file('README', wiki).
 1674autolink_file('TODO', wiki).
 1675autolink_file('ChangeLog', wiki).
 citations(-List)//
Parse @cite1[;@cite2]* into a list of citations.
 1681citations([H|T]) -->
 1682    citation(H),
 1683    (   [';']
 1684    ->  citations(T)
 1685    ;   {T=[]}
 1686    ).
 1687
 1688citation(Atom) -->
 1689    [@], wiki_words(Atoms),
 1690    { length(Atoms, Len),
 1691      Len > 10, !,
 1692      fail
 1693    ; true
 1694    },
 1695    end_citation,
 1696    !,
 1697    { atomic_list_concat(Atoms, Atom)
 1698    }.
 1699
 1700end_citation, [';'] --> [';'].
 1701end_citation, ['@'] --> ['@'].
 1702end_citation, [']'] --> [']'].
 1703
 1704
 1705                 /*******************************
 1706                 *           SECTIONS           *
 1707                 *******************************/
 section_comment_header(+Lines, -Header, -RestLines) is semidet
Processes /** <section> comments. Header is a term \section(Type, Title), where Title is an atom holding the section title and Type is an atom holding the text between <>.
Arguments:
Lines- List of Indent-Codes.
Header- DOM term of the format \section(Type, Title), where Type is an atom from <type> and Title is a string holding the type.
 1720section_comment_header([_-Line|Lines], Header, Lines) :-
 1721    phrase(section_line(Header), Line).
 1722
 1723section_line(\section(Type, Title)) -->
 1724    ws, "<", word(Codes), ">", normalise_white_space(TitleCodes),
 1725    { atom_codes(Type, Codes),
 1726      atom_codes(Title, TitleCodes)
 1727    }.
 1728
 1729                 /*******************************
 1730                 *           TOKENIZER          *
 1731                 *******************************/
 tokenize_lines(+Lines:lines, -TokenLines) is det
Convert Indent-Codes into Indent-Tokens
 1737tokenize_lines(Lines, TokenLines) :-
 1738    tokenize_lines(Lines, -1, TokenLines).
 1739
 1740tokenize_lines([], _, []) :- !.
 1741tokenize_lines(Lines, Indent, [Pre|T]) :-
 1742    verbatim(Lines, Indent, Pre, RestLines),
 1743    !,
 1744    tokenize_lines(RestLines, Indent, T).
 1745tokenize_lines([I-H0|T0], Indent0, [I-H|T]) :-
 1746    phrase(line_tokens(H), H0),
 1747    (   H == []
 1748    ->  Indent = Indent0
 1749    ;   Indent = I
 1750    ),
 1751    tokenize_lines(T0, Indent, T).
 line_tokens(-Tokens:list)// is det
Create a list of tokens, where is token is either a ' ' to denote spaces, a term w(Word) denoting a word or an atom denoting a punctuation character. Underscores (_) appearing inside an alphanumerical string are considered part of the word. E.g., "hello_world_" tokenizes into [w(hello_world), '_'].
 1762line_tokens([H|T]) -->
 1763    line_token(H),
 1764    !,
 1765    line_tokens(T).
 1766line_tokens([]) -->
 1767    [].
 1768
 1769line_token(T) -->
 1770    [C],
 1771    (   { code_type(C, space) }
 1772    ->  ws,
 1773        { T = ' ' }
 1774    ;   { code_type(C, alnum) },
 1775        word(Rest),
 1776        { atom_codes(W, [C|Rest]),
 1777          T = w(W)
 1778        }
 1779    ;   { char_code(T, C) }
 1780    ).
 1781
 1782word([C0|T]) -->
 1783    [C0],  { code_type(C0, alnum) },
 1784    !,
 1785    word(T).
 1786word([0'_, C1|T]) -->
 1787    [0'_, C1],  { code_type(C1, alnum) },
 1788    !,
 1789    word(T).
 1790word([]) -->
 1791    [].
 1792
 1793alphas([C0|T]) -->
 1794    [C0],  { code_type(C0, alpha) },
 1795    !,
 1796    alphas(T).
 1797alphas([]) -->
 1798    [].
 verbatim(+Lines, +EnvIndent, -Pre, -RestLines) is det
Extract a verbatim environment. The returned Pre is of the format pre(Attributes, String). The indentation of the leading fence is substracted from the indentation of the verbatim lines. Two types of fences are supported: the traditional == and the Doxygen ~~~ (minimum 3 ~ characters), optionally followed by {.ext} to indicate the language.

Verbatim environment is delimited as

  ...,
  verbatim(Lines, Pre, Rest)
  ...,

In addition, a verbatim environment may simply be indented. The restrictions are described in the documentation.

 1820verbatim(Lines, _,
 1821         Indent-pre([class(code), ext(Ext)],Pre),
 1822         RestLines) :-
 1823    skip_empty_lines(Lines, [Indent-FenceLine|CodeLines]),
 1824    verbatim_fence(FenceLine, Fence, Ext),
 1825    verbatim_body(CodeLines, Indent, [10|PreCodes], [],
 1826                  [Indent-Fence|RestLines]),
 1827    !,
 1828    atom_codes(Pre, PreCodes).
 1829verbatim([_-[],Indent-Line|Lines], EnvIndent,
 1830         Indent-pre(class(code),Pre),
 1831         RestLines) :-
 1832    EnvIndent >= 0,
 1833    Indent >= EnvIndent+4, Indent =< EnvIndent+8,
 1834    valid_verbatim_opening(Line),
 1835    indented_verbatim_body([Indent-Line|Lines], Indent,
 1836                           CodeLines, RestLines),
 1837    !,
 1838    lines_code_text(CodeLines, Indent, [10|PreCodes]),
 1839    atom_codes(Pre, PreCodes).
 1840
 1841verbatim_body(Lines, _, PreT, PreT, Lines).
 1842verbatim_body([I-L|Lines], Indent, [10|Pre], PreT, RestLines) :-
 1843    PreI is I - Indent,
 1844    phrase(pre_indent(PreI), Pre, PreT0),
 1845    verbatim_line(L, PreT0, PreT1),
 1846    verbatim_body(Lines, Indent, PreT1, PreT, RestLines).
 1847
 1848verbatim_fence(Line, Fence, '') :-
 1849    Line == [0'=,0'=],
 1850    !,
 1851    Fence = Line.
 1852verbatim_fence(Line, Fence, Ext) :-
 1853    tilde_fence(Line, Fence, 0, Ext).
 1854verbatim_fence(Line, Fence, Ext) :-
 1855    md_fence(Line, Fence, 0, Ext).
 1856
 1857tilde_fence([0'~|T0], [0'~|F0], C0, Ext) :-
 1858    !,
 1859    C1 is C0+1,
 1860    tilde_fence(T0, F0, C1, Ext).
 1861tilde_fence(List, [], C, Ext) :-
 1862    C >= 3,
 1863    (   List == []
 1864    ->  Ext = ''
 1865    ;   phrase(tilde_fence_ext(ExtCodes), List)
 1866    ->  atom_codes(Ext, ExtCodes)
 1867    ).
 tilde_fence_ext(-Ext)// is semidet
Detect `{.prolog} (Doxygen) or `{prolog} (GitHub)
 1873tilde_fence_ext(Ext) -->
 1874    "{.", !, alphas(Ext), "}".
 1875tilde_fence_ext(Ext) -->
 1876    "{", alphas(Ext), "}".
 1877
 1878md_fence([0'`|T0], [0'`|F0], C0, Ext) :-
 1879    !,
 1880    C1 is C0+1,
 1881    md_fence(T0, F0, C1, Ext).
 1882md_fence(List, [], C, Ext) :-
 1883    C >= 3,
 1884    (   List == []
 1885    ->  Ext = ''
 1886    ;   phrase(md_fence_ext(ExtCodes), List),
 1887        atom_codes(Ext, ExtCodes)
 1888    ).
 1889
 1890% Also support Doxygen's curly bracket notation.
 1891md_fence_ext(Ext) -->
 1892    tilde_fence_ext(Ext),
 1893    !.
 1894% In Markdown language names appear without brackets.
 1895md_fence_ext(Ext) -->
 1896    alphas(Ext).
 indented_verbatim_body(+Lines, +Indent, -CodeLines, -RestLines)
Takes more verbatim lines. The input ends with the first line that is indented less than Indent. There cannot be more than one consequtive empty line in the verbatim body.
 1904indented_verbatim_body([I-L|T0], Indent, [I-L|T], RestLines) :-
 1905    L \== [], I >= Indent,
 1906    !,
 1907    indented_verbatim_body(T0, Indent, T, RestLines).
 1908indented_verbatim_body([I0-[],I-L|T0], Indent, [I0-[],I-L|T], RestLines) :-
 1909    I >= Indent,
 1910    valid_verbatim_opening(L),
 1911    indented_verbatim_body(T0, Indent, T, RestLines).
 1912indented_verbatim_body(Lines, _, [], Lines).
 valid_verbatim_opening(+Line) is semidet
Tests that line does not look like a list item or table.
 1918valid_verbatim_opening([0'||_]) :- !, fail.
 1919valid_verbatim_opening(Line) :-
 1920    Line \== [],
 1921    \+ ( phrase(line_tokens(Tokens), Line),
 1922         list_item_prefix(_Type, Tokens, _Rest)
 1923       ).
 lines_code_text(+Lines, +Indent, -Codes) is det
Extract the actual code content from a list of line structures.
 1929lines_code_text([], _, []).
 1930lines_code_text([_-[]|T0], Indent, [10|T]) :-
 1931    !,
 1932    lines_code_text(T0, Indent, T).
 1933lines_code_text([I-Line|T0], Indent, [10|T]) :-
 1934    PreI is I-Indent,
 1935    phrase(pre_indent(PreI), T, T1),
 1936    verbatim_line(Line, T1, T2),
 1937    lines_code_text(T0, Indent, T2).
 pre_indent(+Indent)// is det
Insert Indent leading spaces. Note we cannot use tabs as these are not expanded by the HTML <pre> element.
 1945pre_indent(N) -->
 1946    { N > 0,
 1947      !,
 1948      N2 is N - 1
 1949    }, " ",
 1950    pre_indent(N2).
 1951pre_indent(_) -->
 1952    "".
 1953
 1954verbatim_line(Line, Pre, PreT) :-
 1955    append(Line, PreT, Pre).
 1956
 1957
 1958                 /*******************************
 1959                 *            SUMMARY           *
 1960                 *******************************/
 summary_from_lines(+Lines:lines, -Summary:list(codes)) is det
Produce a summary for Lines. Similar to JavaDoc, the summary is defined as the first sentence of the documentation. In addition, a sentence is also ended by an empty line or the end of the comment.
 1969summary_from_lines(Lines, Sentence) :-
 1970    skip_empty_lines(Lines, Lines1),
 1971    summary2(Lines1, Sentence0),
 1972    end_sentence(Sentence0, Sentence).
 1973
 1974summary2(_, Sentence) :-
 1975    Sentence == [],
 1976    !.              % we finished our sentence
 1977summary2([], []) :- !.
 1978summary2([_-[]|_], []) :- !.            % empty line
 1979summary2([_-[0'@|_]|_], []) :- !.       % keyword line
 1980summary2([_-L0|Lines], Sentence) :-
 1981    phrase(sentence(Sentence, Tail), L0, _),
 1982    summary2(Lines, Tail).
 1983
 1984sentence([C,End], []) -->
 1985    [C,End],
 1986    { \+ code_type(C, period),
 1987      code_type(End, period)                % ., !, ?
 1988    },
 1989    space_or_eos,
 1990    !.
 1991sentence([0' |T0], T) -->
 1992    space,
 1993    !,
 1994    ws,
 1995    sentence(T0, T).
 1996sentence([H|T0], T) -->
 1997    [H],
 1998    sentence(T0, T).
 1999sentence([0' |T], T) -->                % '
 2000    eos.
 2001
 2002space_or_eos -->
 2003    [C],
 2004    !,
 2005    {code_type(C, space)}.
 2006space_or_eos -->
 2007    eos.
 skip_empty_lines(+LinesIn, -LinesOut) is det
Remove empty lines from the start of the input. Note that this is used both to process character and token data.
 2014skip_empty_lines([], []).
 2015skip_empty_lines([_-[]|Lines0], Lines) :-
 2016    !,
 2017    skip_empty_lines(Lines0, Lines).
 2018skip_empty_lines(Lines, Lines).
 2019
 2020end_sentence([], []).
 2021end_sentence([0'\s], [0'.]) :- !.
 2022end_sentence([H|T0], [H|T]) :-
 2023    end_sentence(T0, T).
 2024
 2025
 2026                 /*******************************
 2027                 *        CREATE LINES          *
 2028                 *******************************/
 indented_lines(+Text:list(codes), +Prefixes:list(codes), -Lines:list) is det
Extract a list of lines without leading blanks or characters from Prefix from Text. Each line is a term Indent-Codes, where Indent specifies the line_position of the real text of the line.
 2037indented_lines(Comment, Prefixes, Lines) :-
 2038    must_be(codes, Comment),
 2039    phrase(split_lines(Prefixes, Lines), Comment),
 2040    !.
 2041
 2042split_lines(_, []) -->
 2043    end_of_comment.
 2044split_lines(Prefixes, [Indent-L1|Ls]) -->
 2045    take_prefix(Prefixes, 0, Indent0),
 2046    white_prefix(Indent0, Indent),
 2047    take_line(L1),
 2048    split_lines(Prefixes, Ls).
 end_of_comment//
Succeeds if we hit the end of the comment.
bug
- %*/ will be seen as the end of the comment.
 2057end_of_comment -->
 2058    eos.
 2059end_of_comment -->
 2060    ws, stars, "*/", ws.
 2061
 2062stars --> [].
 2063stars --> "*", !, stars.
 take_prefix(+Prefixes:list(codes), +Indent0:int, -Indent:int)// is det
Get the leading characters from the input and compute the line-position at the end of the leading characters.
 2071take_prefix(Prefixes, I0, I) -->
 2072    { member(Prefix, Prefixes),
 2073      string_codes(Prefix, PrefixCodes)
 2074    },
 2075    prefix(PrefixCodes),
 2076    !,
 2077    { string_update_linepos(PrefixCodes, I0, I) }.
 2078take_prefix(_, I, I) -->
 2079    [].
 2080
 2081prefix([]) --> [].
 2082prefix([H|T]) --> [H], prefix(T).
 2083
 2084white_prefix(I0, I) -->
 2085    [C],
 2086    {  code_type(C, white),
 2087       !,
 2088       update_linepos(C, I0, I1)
 2089    },
 2090    white_prefix(I1, I).
 2091white_prefix(I, I) -->
 2092    [].
 string_update_linepos(+Codes, +Pos0, -Pos) is det
Update line-position after adding Codes at Pos0.
 2098string_update_linepos([], I, I).
 2099string_update_linepos([H|T], I0, I) :-
 2100    update_linepos(H, I0, I1),
 2101    string_update_linepos(T, I1, I).
 update_linepos(+Code, +Pos0, -Pos) is det
Update line-position after adding Code.
To be done
- Currently assumes tab-width of 8.
 2109update_linepos(0'\t, I0, I) :-
 2110    !,
 2111    I is (I0\/7)+1.
 2112update_linepos(0'\b, I0, I) :-
 2113    !,
 2114    I is max(0, I0-1).
 2115update_linepos(0'\r, _, 0) :- !.
 2116update_linepos(0'\n, _, 0) :- !.
 2117update_linepos(_, I0, I) :-
 2118    I is I0 + 1.
 take_line(-Line:codes)// is det
Take a line from the input. Line does not include the terminating \r or \n character(s), nor trailing whitespace.
 2125take_line([]) -->
 2126    "\r\n",
 2127    !.                      % DOS file
 2128take_line([]) -->
 2129    "\n",
 2130    !.                        % Unix file
 2131take_line(Line) -->
 2132    [H], { code_type(H, white) },
 2133    !,
 2134    take_white(White, WT),
 2135    (   nl
 2136    ->  { Line = [] }
 2137    ;   { Line = [H|White] },
 2138        take_line(WT)
 2139    ).
 2140take_line([H|T]) -->
 2141    [H],
 2142    !,
 2143    take_line(T).
 2144take_line([]) -->                       % end of string
 2145    [].
 2146
 2147take_white([H|T0], T) -->
 2148    [H],  { code_type(H, white) },
 2149    !,
 2150    take_white(T0, T).
 2151take_white(T, T) -->
 2152    [].
 normalise_indentation(+LinesIn, -LinesOut) is det
Re-normalise the indentation, such that the lef-most line is at zero. Note that we skip empty lines in the computation.
 2159normalise_indentation(Lines0, Lines) :-
 2160    skip_empty_lines(Lines0, Lines1),
 2161    Lines1 = [I0-_|Lines2],
 2162    !,
 2163    smallest_indentation(Lines2, I0, Subtract),
 2164    (   Subtract == 0
 2165    ->  Lines = Lines0
 2166    ;   maplist(substract_indent(Subtract), Lines0, Lines)
 2167    ).
 2168normalise_indentation(Lines, Lines).
 2169
 2170smallest_indentation([], I, I).
 2171smallest_indentation([_-[]|T], I0, I) :-
 2172    !,
 2173    smallest_indentation(T, I0, I).
 2174smallest_indentation([X-_|T], I0, I) :-
 2175    I1 is min(I0, X),
 2176    smallest_indentation(T, I1, I).
 2177
 2178substract_indent(Subtract, I0-L, I-L) :-
 2179    I is max(0,I0-Subtract).
 2180
 2181
 2182                 /*******************************
 2183                 *             MISC             *
 2184                 *******************************/
 strip_leading_par(+Dom0, -Dom) is det
Remove the leading paragraph for environments where a paragraph is not required.
 2191strip_leading_par([p(C)|T], L) :-
 2192    !,
 2193    append(C, T, L).
 2194strip_leading_par(L, L).
 2195
 2196
 2197                 /*******************************
 2198                 *           DCG BASICS         *
 2199                 *******************************/
 ws// is det
Eagerly skip layout characters
 2205ws -->
 2206    [C], {code_type(C, space)},
 2207    !,
 2208    ws.
 2209ws -->
 2210    [].
 2211
 2212%       space// is det
 2213%
 2214%       True if then next code is layout.
 2215
 2216space -->
 2217    [C],
 2218    {code_type(C, space)}.
 nl//
Get end-of-line
 2224nl -->
 2225    "\r\n",
 2226    !.
 2227nl -->
 2228    "\n".
 peek(H)//
True if next token is H without eating it.
 2234peek(H, L, L) :-
 2235    L = [H|_].
 2236
 2237peek(H1, H2, L, L) :-
 2238    L = [H1, H2|_].
 tokens(-Tokens:list)// is nondet
 tokens(+Max, -Tokens:list)// is nondet
Defensively take tokens from the input. Backtracking takes more tokens. Do not include structure terms.
 2246tokens([]) --> [].
 2247tokens([H|T]) --> token(H), tokens(T).
 2248
 2249tokens(_, []) --> [].
 2250tokens(C, [H|T]) --> token(H), {succ(C1, C)}, tokens(C1, T).
 tokens_no_whitespace(-Tokens:list(atom))// is nondet
Defensively take tokens from the input. Backtracking takes more tokens. Tokens cannot include whitespace. Word tokens are returned as their represented words.
 2258tokens_no_whitespace([]) -->
 2259    [].
 2260tokens_no_whitespace([Word|T]) -->
 2261    [ w(Word) ],
 2262    !,
 2263    tokens_no_whitespace(T).
 2264tokens_no_whitespace([H|T]) -->
 2265    [H],
 2266    { \+ space_token(H) },
 2267    tokens_no_whitespace(T).
 2268
 2269token(Token) -->
 2270    [Token],
 2271    { token(Token) }.
 2272
 2273token(w(_)) :- !.
 2274token(Token) :- atom(Token).
 limit(+Count, :Rule)//
As limit/2, but for grammar rules.
 2280:- meta_predicate limit(+,2,?,?). 2281
 2282limit(Count, Rule, Input, Rest) :-
 2283    Count > 0,
 2284    State = count(0),
 2285    call(Rule, Input, Rest),
 2286    arg(1, State, N0),
 2287    N is N0+1,
 2288    (   N =:= Count
 2289    ->  !
 2290    ;   nb_setarg(1, State, N)
 2291    ).
 2292
 2293
 2294                 /*******************************
 2295                 *           MESSAGES           *
 2296                 *******************************/
 2297
 2298:- multifile
 2299    prolog:message//1. 2300
 2301prolog:message(pldoc(deprecated_tag(Name, Tag))) -->
 2302    [ 'PlDoc: Deprecated tag @~w (use @~w)'-[Name, Tag]
 2303    ].
 2304prolog:message(pldoc(unknown_tag(Name))) -->
 2305    [ 'PlDoc: unknown tag @~w'-[Name]
 2306    ]