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-2019, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(pldoc_wiki,
   38          [ wiki_codes_to_dom/3,        % +Codes, +Args, -DOM
   39            wiki_lines_to_dom/3,        % +Lines, +Map, -DOM
   40            section_comment_header/3,   % +Lines, -Header, -RestLines
   41            summary_from_lines/2,       % +Lines, -Codes
   42            indented_lines/3,           % +Text, +PrefixChars, -Lines
   43            strip_leading_par/2,        % +DOM0, -DOM
   44            autolink_extension/2,       % ?Extension, ?Type
   45            autolink_file/2             % +FileName, -Type
   46          ]).   47:- use_module(library(lists)).   48:- use_module(library(debug)).   49:- use_module(library(error)).   50:- use_module(library(pairs)).   51:- use_module(library(option)).   52:- use_module(library(debug)).   53:- use_module(library(apply)).   54:- use_module(library(dcg/basics)).   55
   56:- 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 */
   70:- multifile
   71    prolog:doc_wiki_face//2,        % -Out, +VarNames
   72    prolog:doc_url_expansion/3,     % +Alias(Rest), -HREF, -Label
   73    prolog:url_expansion_hook/3,    % +Term, -Ref, -Label
   74    prolog:doc_autolink_extension/2.% +Extension, -Type
   75
   76
   77                 /*******************************
   78                 *          WIKI PARSING        *
   79                 *******************************/
 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.
   86wiki_lines_to_dom(Lines, Args, HTML) :-
   87    tokenize_lines(Lines, Tokens0),
   88    normalise_indentation(Tokens0, Tokens),
   89    wiki_structure(Tokens, -1, Pars),
   90    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.
   99wiki_codes_to_dom(Codes, Args, DOM) :-
  100    indented_lines(Codes, [], Lines),
  101    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.
  111wiki_structure([], _, []) :- !.
  112wiki_structure([_-[]|T], BI, Pars) :-          % empty lines
  113    !,
  114    wiki_structure(T, BI, Pars).
  115wiki_structure(Lines, _, [\tags(Tags)]) :-
  116    tags(Lines, Tags),
  117    !.
  118wiki_structure(Lines, BI, [P1|PL]) :-
  119    take_block(Lines, BI, P1, RestLines),
  120    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.
  127take_block([_-[]|Lines], BaseIndent, Block, Rest) :-
  128    !,
  129    take_block(Lines, BaseIndent, Block, Rest).
  130take_block([N-_|_], BaseIndent, _, _) :-
  131    N < BaseIndent,
  132    !,
  133    fail.                           % less indented
  134take_block(Lines, BaseIndent, List, Rest) :-
  135    list_item(Lines, Type, Indent, LI, LIT, Rest0),
  136    !,
  137    Indent > BaseIndent,
  138    rest_list(Rest0, Type, Indent, LIT, [], Rest),
  139    List0 =.. [Type, LI],
  140    (   ul_to_dl(List0, List)
  141    ->  true
  142    ;   List0 = dl(Items)
  143    ->  List = dl(class=wiki, Items)
  144    ;   List = List0
  145    ).
  146take_block([N-['|'|RL1]|LT], _, Table, Rest) :-
  147    phrase(row(R0), RL1),
  148    take_table(LT, N, R0, Table, Rest),
  149    !.
  150take_block([0-[-,-|More]|LT], _, Block, LT) :-  % separation line
  151    maplist(=(-), More),
  152    !,
  153    Block = hr([]).
  154take_block([_-Line|LT], _, Block, LT) :-        % separation line
  155    ruler(Line),
  156    !,
  157    Block = hr([]).
  158take_block([_-[@|_]], _, _, _) :-              % starts @tags section
  159    !,
  160    fail.
  161take_block(Lines, _BaseIndent, Section, RestLines) :-
  162    section_header(Lines, Section, RestLines),
  163    !.
  164take_block([_-Verb|Lines], _, Verb, Lines) :-
  165    verbatim_term(Verb),
  166    !.
  167take_block([I-L1|LT], BaseIndent, Elem, Rest) :-
  168    !,
  169    append(L1, PT, Par),
  170    rest_par(LT, PT, I, BaseIndent, MaxI, Rest),
  171    (   MaxI >= BaseIndent+16
  172    ->  Elem = center(Par)
  173    ;   phrase(blockquote(BQ), Par)
  174    ->  Elem = blockquote(BQ)
  175    ;   Elem = p(Par)
  176    ).
  177take_block([Verb|Lines], _, Verb, Lines).
  178
  179blockquote(Clean) -->
  180    [>, ' '],
  181    bq_lines(Clean).
  182
  183bq_lines([' '|Par]) -->
  184    ['\n'], !, [>,' '],
  185    bq_lines(Par).
  186bq_lines([H|T]) -->
  187    [H],
  188    bq_lines(T).
  189bq_lines([]) -->
  190    [].
 ruler(+Line) is semidet
True if Line contains 3 ruler chars and otherwise spaces.
  197ruler([C0|Line]) :-
  198    rule_char(C0),
  199    phrase(ruler(C0, 1), Line).
  200
  201ruler(C, N) --> [C], !, { N2 is N+1 }, ruler(C, N2).
  202ruler(C, N) --> [' '], !, ruler(C, N).
  203ruler(_, N) --> { N >= 3 }.
  204
  205rule_char('-').
  206rule_char('_').
  207rule_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
  217list_item([Indent-Line|LT], Type, Indent, Items, ItemT, Rest) :-
  218    !,
  219    list_item_prefix(Type, Line, L1),
  220    (   Type == dl
  221    ->  split_dt(L1, DT0, DD1),
  222        append(DD1, LIT, DD),
  223        strip_ws_tokens(DT0, DT),
  224        Items = [dt(DT),dd(DD)|ItemT]
  225    ;   append(L1, LIT, LI0),
  226        Items = [li(LI0)|ItemT]
  227    ),
  228    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.
  234rest_list_item(Lines, _Type, Indent, RestItem, RestLines) :-
  235    take_blocks_at_indent(Lines, Indent, Blocks, RestLines),
  236    (   Blocks = [p(Par)|MoreBlocks]
  237    ->  append(['\n'|Par], MoreBlocks, RestItem)
  238    ;   RestItem = Blocks
  239    ).
 take_blocks_at_indent(+Lines, +Indent, -Pars, -RestLines) is det
Process paragraphs and verbatim blocks (==..==) in bullet-lists.
  245take_blocks_at_indent(Lines, _, [], Lines) :-
  246    skip_empty_lines(Lines, Lines1),
  247    section_header(Lines1, _, _),
  248    !.
  249take_blocks_at_indent(Lines, N, [Block|RestBlocks], RestLines) :-
  250    take_block(Lines, N, Block, Rest0),
  251    !,
  252    take_blocks_at_indent(Rest0, N, RestBlocks, RestLines).
  253take_blocks_at_indent(Lines, _, [], Lines).
 rest_list(+Lines, +Type, +Indent, -Items, -ItemTail, -RestLines) is det
  259rest_list(Lines, Type, N, Items, IT, Rest) :-
  260    skip_empty_lines(Lines, Lines1),
  261    list_item(Lines1, Type, N, Items, IT0, Rest0),
  262    !,
  263    rest_list(Rest0, Type, N, IT0, IT, Rest).
  264rest_list(Rest, _, _, IT, IT, Rest).
 list_item_prefix(?Type, +Line, -Rest) is det
  268list_item_prefix(ul, [*, ' '|T], T) :- !.
  269list_item_prefix(ul, [-, ' '|T], T) :- !.
  270list_item_prefix(dl, [$, ' '|T], T) :-
  271    split_dt(T, _, _),
  272    !.
  273list_item_prefix(ol, [w(N), '.', ' '|T], T) :-
  274    atom_codes(N, [D]),
  275    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
  282split_dt(In, DT, []) :-
  283    append(DT, [':'], In),
  284    !.
  285split_dt(In, DT, Rest) :-
  286    append(DT, [':'|Rest0], In),
  287    (   Rest0 == []
  288    ->  Rest = []
  289    ;   Rest0 = [' '|Rest]
  290    ),
  291    !.
 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]].
  301ul_to_dl(ul(Items), Description) :-
  302    term_items(Items, DLItems, []),
  303    (   terms_to_predicate_includes(DLItems, Preds)
  304    ->  Description = dl(class(predicates), Preds)
  305    ;   member(dd(DD), DLItems), DD \== []
  306    ->  Description = dl(class(termlist), DLItems)
  307    ).
  308
  309term_items([], T, T).
  310term_items([LI|LIs], DLItems, Tail) :-
  311    term_item(LI, DLItems, Tail1),
  312    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).
  321term_item(li(Tokens),
  322          [ dt(class=term, \term(Text, Term, Bindings)),
  323            dd(Descr)
  324          | Tail
  325          ], Tail) :-
  326    (   (   append(TermTokens, ['\n'|Descr], Tokens)
  327        ->  true
  328        ;   TermTokens = Tokens,
  329            Descr = []
  330        )
  331    ->  with_output_to(string(Tmp),
  332                       ( forall(member(T, TermTokens),
  333                                write_token(T)),
  334                         write(' .\n'))),
  335        E = error(_,_),
  336        catch(setup_call_cleanup(
  337                  open_string(Tmp, In),
  338                  ( read_dt_term(In, Term, Bindings),
  339                    read_dt_term(In, end_of_file, []),
  340                    atom_string(Text, Tmp)
  341                  ),
  342                  close(In)),
  343              E, fail)
  344    ).
  345
  346write_token(w(X)) :-
  347    !,
  348    write(X).
  349write_token(X) :-
  350    write(X).
  351
  352read_dt_term(In, Term, Bindings) :-
  353    read_term(In, Term,
  354              [ variable_names(Bindings),
  355                module(pldoc_modes)
  356              ]).
  357
  358terms_to_predicate_includes([], []).
  359terms_to_predicate_includes([dt(class=term, \term(_, [[PI]], [])), dd([])|T0],
  360                            [\include(PI, predicate, [])|T]) :-
  361    is_pi(PI),
  362    terms_to_predicate_includes(T0, T).
  363
  364is_pi(Name/Arity) :-
  365    atom(Name),
  366    integer(Arity),
  367    between(0, 20, Arity).
  368is_pi(Name//Arity) :-
  369    atom(Name),
  370    integer(Arity),
  371    between(0, 20, Arity).
 row(-Cells)// is det
  376row([C0|CL]) -->
  377    cell(C0),
  378    !,
  379    row(CL).
  380row([]) -->
  381    [].
  382
  383cell(td(C)) -->
  384    face_tokens(C0),
  385    ['|'],
  386    !,
  387    { strip_ws_tokens(C0, C)
  388    }.
  389
  390face_tokens([]) -->
  391    [].
  392face_tokens(Tokens) -->
  393    face_token(H),                          % Deal with embedded *|...|*, etc.
  394    token('|'),
  395    face_tokens(Face),
  396    token('|'),
  397    face_token(H),
  398    !,
  399    { append([[H,'|'], Face, ['|', H], Rest], Tokens) },
  400    face_tokens(Rest).
  401face_tokens([H|T]) -->
  402    token(H),
  403    face_tokens(T).
  404
  405face_token(=) --> [=].
  406face_token(*) --> [*].
  407face_token('_') --> ['_'].
  408
  409take_table(Lines, Indent, Row0, Table, Rest) :-
  410    rest_table(Lines, Indent, Rows, Rest),
  411    (   Rows = [align(Align)|Rows1]
  412    ->  maplist(align_row(Align), Rows1, Rows2),
  413        (   maplist(=(td([])), Row0)        % empty header
  414        ->  Table = table(class=wiki, Rows2)
  415        ;   maplist(td_to_th, Row0, Header),
  416            Table = table(class=wiki, [tr(Header)|Rows2])
  417        )
  418    ;   Table = table(class=wiki, [tr(Row0)|Rows])
  419    ).
  420
  421td_to_th(td(X), th(X)) :- !.
  422td_to_th(X, X).
  423
  424align_row(Align, tr(Row0), tr(Row)) :-
  425    align_cells(Align, Row0, Row).
  426
  427align_cells([Align|AT], [Cell0|T0], [Cell|T]) :-
  428    align_cell(Align, Cell0, Cell),
  429    align_cells(AT, T0, T).
  430align_cells(_, Cells, Cells).
  431
  432align_cell(Align, td(Content), td(class=Align, Content)).
 rest_table(+Lines, +Indent, -Rows, -RestLines)
  436rest_table([N-Line|LT], N, [align(Align)|RL], Rest) :-
  437    phrase(column_alignment(Align), Line),
  438    !,
  439    rest_table2(LT, N, RL, Rest).
  440rest_table(Lines, N, RL, Rest) :-
  441    rest_table2(Lines, N, RL, Rest).
  442
  443rest_table2([N-['|'|RL1]|LT], N, [tr(R0)|RL], Rest) :-
  444    !,
  445    phrase(row(R0), RL1),
  446    rest_table2(LT, N, RL, Rest).
  447rest_table2(Rest, _, [], Rest).
 column_alignment(-Alignment) is semidet
Process an alignment line.
  453column_alignment([H|T]) -->
  454    ['|'],
  455    (   colspec(H)
  456    ->  column_alignment(T)
  457    ;   {T=[]}
  458    ).
  459
  460colspec(Align) -->
  461    ws_tokens, [':'], dashes3,
  462    (   [':']
  463    ->  {Align = center}
  464    ;   {Align = left}
  465    ),
  466    ws_tokens.
  467colspec(Align) -->
  468    ws_tokens, dashes3,
  469    (   [':']
  470    ->  {Align = right}
  471    ;   {Align = left}
  472    ),
  473    ws_tokens.
  474
  475dashes3 -->
  476    [-,-,-],
  477    dashes.
  478
  479dashes --> [-], !, dashes.
  480dashes --> [].
  481
  482ws_tokens --> [' '], !, ws_tokens.
  483ws_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.
  493rest_par([], [], BI, MaxI0, MaxI, []) :-
  494    !,
  495    MaxI is max(BI, MaxI0).
  496rest_par([_-[]|Rest], [], _, MaxI, MaxI, Rest) :- !.
  497rest_par(Lines, [], _, MaxI, MaxI, Lines) :-
  498    Lines = [_-Verb|_],
  499    verbatim_term(Verb),
  500    !.
  501rest_par([I-L|Rest], [], _, MaxI, MaxI, [I-L|Rest]) :-
  502    list_item_prefix(_, L, _),
  503    !.
  504rest_par([I-L1|LT], ['\n'|Par], BI, MaxI0, MaxI, Rest) :-
  505    append(L1, PT, Par),
  506    MaxI1 is max(I, MaxI0),
  507    rest_par(LT, PT, BI, MaxI1, MaxI, Rest).
 section_header(+Lines, -Section, -RestLines) is semidet
Get a section line from the input.
  514section_header([_-L1|LT], Section, LT) :-
  515    twiki_section_line(L1, Section),
  516    !.
  517section_header([0-L1|LT], Section, LT) :-
  518    md_section_line(L1, Section),
  519    !.
  520section_header([_-L1,0-L2|LT], Section, LT) :-
  521    md_section_line(L1, L2, Section),
  522    !.
 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.
  530twiki_section_line([-,-,-|Rest], Section) :-
  531    plusses(Rest, Section).
  532
  533plusses([+, ' '|Rest], h1(Attrs, Content)) :-
  534    hdr_attributes(Rest, Attrs, Content).
  535plusses([+, +, ' '|Rest], h2(Attrs, Content)) :-
  536    hdr_attributes(Rest, Attrs, Content).
  537plusses([+, +, +, ' '|Rest], h3(Attrs, Content)) :-
  538    hdr_attributes(Rest, Attrs, Content).
  539plusses([+, +, +, +, ' '|Rest], h4(Attrs, Content)) :-
  540    hdr_attributes(Rest, Attrs, Content).
  541
  542hdr_attributes(List, Attrs, Content) :-
  543    strip_leading_ws(List, List2),
  544    (   List2 = ['[',w(Name),']'|List3]
  545    ->  strip_ws_tokens(List3, Content),
  546        Attrs = [class(wiki), id(Name)]
  547    ;   Attrs = class(wiki),
  548        strip_ws_tokens(List, Content)
  549    ).
 md_section_line(+Tokens, -Section) is semidet
Handle markdown section lines staring with #
  555md_section_line([#, ' '|Rest], h1(Attrs, Content)) :-
  556    md_section_attributes(Rest, Attrs, Content).
  557md_section_line([#, #, ' '|Rest], h2(Attrs, Content)) :-
  558    md_section_attributes(Rest, Attrs, Content).
  559md_section_line([#, #, #, ' '|Rest], h3(Attrs, Content)) :-
  560    md_section_attributes(Rest, Attrs, Content).
  561md_section_line([#, #, #, #, ' '|Rest], h4(Attrs, Content)) :-
  562    md_section_attributes(Rest, Attrs, Content).
  563
  564md_section_attributes(List, Attrs, Content) :-
  565    phrase((tokens(Content), [' '], section_label(Label)), List),
  566    !,
  567    Attrs = [class(wiki), id(Label)].
  568md_section_attributes(Content, Attrs, Content) :-
  569    Attrs = [class(wiki)].
  570
  571section_label(Label) -->
  572    [ '{', '#', w(Name) ],
  573    label_conts(Cont), ['}'],
  574    !,
  575    { atomic_list_concat([Name|Cont], Label) }.
  576
  577label_conts([H|T]) --> label_cont(H), !, label_conts(T).
  578label_conts([]) --> [].
  579
  580label_cont(-) --> [-].
  581label_cont(Name) --> [w(Name)].
  582
  583
  584md_section_line(Line1, Line2, Header) :-
  585    Line1 \== [],
  586    section_underline(Line2, Type),
  587    is_list(Line1),
  588    phrase(wiki_words(_), Line1),  % Should not have structure elements
  589    !,
  590    (   phrase(labeled_section_line(Title, Attrs), Line1)
  591    ->  true
  592    ;   Title = Line1,
  593        Attrs = []
  594    ),
  595    Header =.. [Type, [class(wiki)|Attrs], Title].
  596
  597section_underline([=,=,=|T], h1) :-
  598    maplist(=(=), T),
  599    !.
  600section_underline([-,-,-|T], h2) :-
  601    maplist(=(-), T),
  602    !.
  603
  604labeled_section_line(Title, Attrs) -->
  605    tokens(Title), [' '], section_label(Label),
  606    !,
  607    { Attrs = [id(Label)] }.
 strip_ws_tokens(+Tokens, -Stripped)
Strip leading and trailing whitespace from a token list. Note the the whitespace is already normalised.
  615strip_ws_tokens([' '|T0], T) :-
  616    !,
  617    strip_ws_tokens(T0, T).
  618strip_ws_tokens(L0, L) :-
  619    append(L, [' '], L0),
  620    !.
  621strip_ws_tokens(L, L).
 strip_leading_ws(+Tokens, -Stripped) is det
Strip leading whitespace from a token list.
  628strip_leading_ws([' '|T], T) :- !.
  629strip_leading_ws(T, T).
  630
  631
  632                 /*******************************
  633                 *             TAGS             *
  634                 *******************************/
 tags(+Lines:lines, -Tags) is semidet
If the first line is a @tag, read the remainder of the lines to a list of \tag(Name, Value) terms.
  641tags(Lines, Tags) :-
  642    collect_tags(Lines, Tags0),
  643    keysort(Tags0, Tags1),
  644    pairs_values(Tags1, Tags2),
  645    combine_tags(Tags2, Tags).
 collect_tags(+IndentedLines, -Tags) is semidet
Create a list Order-tag(Tag,Tokens) for each @tag encountered. Order is the desired position as defined by tag_order/2.
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.
  656collect_tags([], []).
  657collect_tags([Indent-[@,String|L0]|Lines], [Order-tag(Tag,Value)|Tags]) :-
  658    tag_name(String, Tag, Order),
  659    !,
  660    strip_leading_ws(L0, L),
  661    rest_tag(Lines, Indent, VT, RestLines),
  662    normalise_indentation(VT, VT1),
  663    wiki_structure([0-L|VT1], -1, Value0),
  664    strip_leading_par(Value0, Value),
  665    collect_tags(RestLines, Tags).
 tag_name(+String, -Tag:atom, -Order:int) is semidet
If String denotes a know tag-name,
  672tag_name(w(Name), Tag, Order) :-
  673    (   renamed_tag(Name, Tag, Level),
  674        tag_order(Tag, Order)
  675    ->  print_message(Level, pldoc(deprecated_tag(Name, Tag)))
  676    ;   tag_order(Name, Order)
  677    ->  Tag = Name
  678    ;   print_message(warning, pldoc(unknown_tag(Name))),
  679        fail
  680    ).
  681
  682
  683rest_tag([], _, [], []) :- !.
  684rest_tag(Lines, Indent, [], Lines) :-
  685    Lines = [Indent-[@,Word|_]|_],
  686    tag_name(Word, _, _),
  687    !.
  688rest_tag([L|Lines0], Indent, [L|VT], Lines) :-
  689    rest_tag(Lines0, Indent, VT, Lines).
 renamed_tag(+DeprecatedTag:atom, -Tag:atom, -Warn) is semidet
Declaration for deprecated tags.
  696renamed_tag(exception, throws, warning).
  697renamed_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.
  706:- multifile
  707    pldoc:tag_order/2.  708
  709tag_order(Tag, Order) :-
  710    pldoc:tag_order(Tag, Order),
  711    !.
  712tag_order(arg,         100).
  713tag_order(error,       200).            % same as throw
  714tag_order(throws,      300).
  715tag_order(author,      400).
  716tag_order(version,     500).
  717tag_order(see,         600).
  718tag_order(deprecated,  700).
  719tag_order(compat,      800).            % PlDoc extension
  720tag_order(copyright,   900).
  721tag_order(license,    1000).
  722tag_order(bug,        1100).
  723tag_order(tbd,        1200).
 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.

  734combine_tags([], []).
  735combine_tags([tag(arg, V1)|T0], [\args([P1|PL])|Tags]) :-
  736    !,
  737    arg_tag(V1, P1),
  738    arg_tags(T0, PL, T1),
  739    combine_tags(T1, Tags).
  740combine_tags([tag(Tag,V0)|T0], [\tag(Tag, [V0|Vs])|T]) :-
  741    same_tag(Tag, T0, T1, Vs),
  742    combine_tags(T1, T).
  743
  744arg_tag([PT|Descr0], arg(PN, Descr)) :-
  745    word_of(PT, PN),
  746    strip_leading_ws(Descr0, Descr).
  747
  748word_of(w(W), W) :- !.                  % TBD: check non-word arg
  749word_of(W, W).
  750
  751arg_tags([tag(arg, V1)|T0], [P1|PL], T) :-
  752    !,
  753    arg_tag(V1, P1),
  754    arg_tags(T0, PL, T).
  755arg_tags(T, [], T).
  756
  757same_tag(Tag, [tag(Tag, V)|T0], T, [V|Vs]) :-
  758    !,
  759    same_tag(Tag, T0, T, Vs).
  760same_tag(_, L, L, []).
  761
  762
  763                 /*******************************
  764                 *             FACES            *
  765                 *******************************/
 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.
  772wiki_faces([dt(Class, \term(Text, Term, Bindings)), dd(Descr0)|T0],
  773           ArgNames,
  774           [dt(Class, \term(Text, Term, Bindings)), dd(Descr)|T]) :-
  775    !,
  776    varnames(Bindings, VarNames, ArgNames),
  777    wiki_faces(Descr0, VarNames, Descr),
  778    wiki_faces(T0, ArgNames, T).
  779wiki_faces(DOM0, ArgNames, DOM) :-
  780    structure_term(DOM0, Functor, Content0),
  781    !,
  782    wiki_faces_list(Content0, ArgNames, Content),
  783    structure_term(DOM, Functor, Content).
  784wiki_faces(Verb, _, Verb) :-
  785    verbatim_term(Verb),
  786    !.
  787wiki_faces(Content0, ArgNames, Content) :-
  788    assertion(is_list(Content0)),
  789    phrase(wiki_faces(Content, ArgNames), Content0),
  790    !.
  791
  792varnames([], List, List).
  793varnames([Name=_|T0], [Name|T], List) :-
  794    varnames(T0, T, List).
  795
  796wiki_faces_list([], _, []).
  797wiki_faces_list([H0|T0], Args, [H|T]) :-
  798    wiki_faces(H0, Args, H),
  799    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.
  807structure_term(\tags(Tags), tags, [Tags]) :- !.
  808structure_term(\args(Params), args, [Params]) :- !.
  809structure_term(arg(Name,Descr), arg(Name), [Descr]) :- !.
  810structure_term(\tag(Name,Value), tag(Name), [Value]) :- !.
  811structure_term(\include(What,Type,Opts), include(What,Type,Opts), []) :- !.
  812structure_term(dl(Att, Args), dl(Att), [Args]) :- !.
  813structure_term(dt(Att, Args), dt(Att), [Args]) :- !.
  814structure_term(table(Att, Args), table(Att), [Args]) :- !.
  815structure_term(td(Att, Args), td(Att), [Args]) :- !.
  816structure_term(h1(Att, Args), h1(Att), [Args]) :- !.
  817structure_term(h2(Att, Args), h2(Att), [Args]) :- !.
  818structure_term(h3(Att, Args), h3(Att), [Args]) :- !.
  819structure_term(h4(Att, Args), h4(Att), [Args]) :- !.
  820structure_term(hr(Att), hr(Att), []) :- !.
  821structure_term(p(Args), p, [Args]) :- !.
  822structure_term(Term, Functor, Args) :-
  823    structure_term_any(Term, Functor, Args).
  824
  825structure_term(Term) :-
  826    structure_term_any(Term, _Functor, _Args).
  827
  828structure_term_any(Term, Functor, Args) :-
  829    functor(Term, Functor, 1),
  830    structure_tag(Functor),
  831    !,
  832    Term =.. [Functor|Args].
  833
  834structure_tag(ul).
  835structure_tag(ol).
  836structure_tag(dl).
  837structure_tag(li).
  838structure_tag(dt).
  839structure_tag(dd).
  840structure_tag(table).
  841structure_tag(tr).
  842structure_tag(td).
  843structure_tag(th).
  844structure_tag(blockquote).
  845structure_tag(center).
 verbatim_term(?Term) is det
True if Term must be passes verbatim.
  852verbatim_term(pre(_,_)).
  853verbatim_term(\term(_,_,_)).
 matches(:Goal, -Input, -Last)//
True when Goal runs successfully on the DCG input and Input is the list of matched tokens.
  860:- meta_predicate matches(2, -, -, ?, ?).  861
  862matches(Goal, Input, Last, List, Rest) :-
  863    call(Goal, List, Rest),
  864    input(List, Rest, Input, Last).
  865
  866input([H|T0], Rest, Input, Last) :-
  867    (   T0 == Rest
  868    ->  Input = [H],
  869        Last = H
  870    ;   Input = [H|T],
  871        input(T0, Rest, T, Last)
  872    ).
 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
  884wiki_faces(WithFaces, ArgNames, List, Rest) :-
  885    default_faces_options(Options),
  886    catch(wiki_faces(WithFaces, ArgNames, Options, List, Rest),
  887          pldoc(depth_limit),
  888          failed_faces(WithFaces, List, Rest)).
  889
  890default_faces_options(_{depth:5}).
  891
  892failed_faces(WithFaces) -->
  893    { debug(markdown(overflow), 'Depth limit exceeded', []) },
  894    wiki_words(WithFaces).
  895
  896wiki_faces([EmphTerm|T], ArgNames, Options) -->
  897    emphasis_seq(EmphTerm, ArgNames, Options),
  898    !,
  899    wiki_faces_int(T, ArgNames).
  900wiki_faces(Faces, ArgNames, Options) -->
  901    wiki_faces_int(Faces, ArgNames, Options).
  902
  903wiki_faces_int(WithFaces, ArgNames) -->
  904    { default_faces_options(Options)
  905    },
  906    wiki_faces_int(WithFaces, ArgNames, Options).
  907
  908wiki_faces_int([], _, _) -->
  909    [].
  910wiki_faces_int(List, ArgNames, Options) -->
  911    wiki_face(H, ArgNames, Options),
  912    !,
  913    {   is_list(H)
  914    ->  append(H, T, List)
  915    ;   List = [H|T]
  916    },
  917    wiki_faces(T, ArgNames, Options).
  918wiki_faces_int([Before,EmphTerm|T], ArgNames, Options) -->
  919    emphasis_before(Before),
  920    emphasis_seq(EmphTerm, ArgNames, Options),
  921    !,
  922    wiki_faces_int(T, ArgNames, Options).
  923wiki_faces_int([H|T], ArgNames, Options) -->
  924    wiki_face_simple(H, ArgNames, Options),
  925    !,
  926    wiki_faces_int(T, ArgNames, Options).
  927
  928next_level(Options0, Options) -->
  929    {   succ(NewDepth, Options0.depth)
  930    ->  Options = Options0.put(depth, NewDepth)
  931    ;   throw(pldoc(depth_limit))
  932    }.
 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.

  951wiki_face(Out, Args, _) -->
  952    prolog:doc_wiki_face(Out, Args),
  953    !.
  954wiki_face(var(Arg), ArgNames, _) -->
  955    [w(Arg)],
  956    { memberchk(Arg, ArgNames)
  957    },
  958    !.
  959wiki_face(b(Bold), ArgNames, Options) -->
  960    [*,'|'], string(Tokens), ['|',*],
  961    !,
  962    { phrase(wiki_faces(Bold, ArgNames, Options), Tokens) }.
  963wiki_face(i(Italic), ArgNames, Options) -->
  964    ['_','|'], string(Tokens), ['|','_'],
  965    !,
  966    { phrase(wiki_faces(Italic, ArgNames, Options), Tokens) }.
  967wiki_face(code(Code), _, _) -->
  968    [=], eq_code_words(Words), [=],
  969    !,
  970    { atomic_list_concat(Words, Code) }.
  971wiki_face(code(Code), _, _) -->
  972    [=,'|'], wiki_words(Code), ['|',=],
  973    !.
  974wiki_face(PredRef, _, _) -->
  975    ['`'], take_predref(PredRef), ['`'],
  976    !.
  977wiki_face(\nopredref(Pred), _, _) -->
  978    ['`', '`'], take_predref(\predref(Pred)), ['`', '`'],
  979    !.
  980wiki_face([flag, ' ', \flagref(Flag)], _, _) -->
  981    [ w('flag'), ' ', '`', w(Flag), '`' ],
  982    { current_prolog_flag(Flag, _) },
  983    !.
  984wiki_face(code(Code), _, _) -->
  985    ['`','`'], wiki_words(Code), ['`','`'],
  986    !.
  987wiki_face(Code, _, _) -->
  988    (   ['`'], code_words(Words), ['`']
  989    ->  { atomic_list_concat(Words, Text),
  990          E = error(_,_),
  991          catch(atom_to_term(Text, Term, Vars), E, fail),
  992          !,
  993          code_face(Text, Term, Vars, Code)
  994        }
  995    ).
  996wiki_face(Face, _, Options) -->
  997    [ w(Name) ], arg_list(List),
  998    { atomic_list_concat([Name|List], Text),
  999      E = error(_,_),
 1000      catch(atom_to_term(Text, Term, Vars), E, fail),
 1001      term_face(Text, Term, Vars, Face, Options)
 1002    },
 1003    !.
 1004wiki_face(br([]), _, _) -->
 1005    [<,w(br),>,'\n'], !.
 1006wiki_face(br([]), _, _) -->
 1007    [<,w(br),/,>,'\n'], !.
 1008        % Below this, we only do links.
 1009wiki_face(_, _, Options) -->
 1010    { Options.get(link) == false,
 1011      !,
 1012      fail
 1013    }.
 1014wiki_face(PredRef, _, _) -->
 1015    take_predref(PredRef),
 1016    !.
 1017wiki_face(\cite(Citations), _, _) -->
 1018    ['['], citations(Citations), [']'].
 1019wiki_face(\include(Name, Type, Options), _, _) -->
 1020    ['[','['], file_name(Base, Ext), [']',']'],
 1021    { autolink_extension(Ext, Type),
 1022      !,
 1023      file_name_extension(Base, Ext, Name),
 1024      resolve_file(Name, Options, [])
 1025    },
 1026    !.
 1027wiki_face(\include(Name, Type, [caption(Caption)|Options]), _, _) -->
 1028    (   ['!','['], tokens(100, Caption), [']','(']
 1029    ->  file_name(Base, Ext), [')'],
 1030        { autolink_extension(Ext, Type),
 1031          !,
 1032          file_name_extension(Base, Ext, Name),
 1033          resolve_file(Name, Options, [])
 1034        }
 1035    ),
 1036    !.
 1037wiki_face(Link, ArgNames, Options) -->          % TWiki: [[Label][Link]]
 1038    (   ['[','['], wiki_label(Label, ArgNames, Options), [']','[']
 1039    ->  wiki_link(Link, [label(Label), relative(true), end(']')]),
 1040        [']',']'], !
 1041    ).
 1042wiki_face(Link, ArgNames, Options) -->          % Markdown: [Label](Link)
 1043    (   ['['], wiki_label(Label, ArgNames, Options), [']','(']
 1044    ->  wiki_link(Link, [label(Label), relative(true), end(')')]),
 1045        [')'], !
 1046    ).
 1047wiki_face(Link, _ArgNames, _) -->
 1048    wiki_link(Link, []),
 1049    !.
 1050
 1051wiki_label(Label, _ArgNames, _Options) -->
 1052    image_label(Label).
 1053wiki_label(Label, ArgNames, Options) -->
 1054    next_level(Options, NOptions),
 1055    limit(40, wiki_faces(Label, ArgNames, NOptions.put(link,false))).
 wiki_face_simple(-Out, +ArgNames, +Options)
Skip simple (non-markup) wiki.
 1061wiki_face_simple(Word, _, _) -->
 1062    [ w(Word) ],
 1063    !.
 1064wiki_face_simple(SpaceOrPunct, _, _) -->
 1065    [ SpaceOrPunct ],
 1066    { atomic(SpaceOrPunct) },
 1067    !.
 1068wiki_face_simple(FT, ArgNames, _) -->
 1069    [Structure],
 1070    { wiki_faces(Structure, ArgNames, FT)
 1071    }.
 1072
 1073wiki_words([]) --> [].
 1074wiki_words([Word|T]) --> [w(Word)], !, wiki_words(T).
 1075wiki_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 `.
 1082code_words([]) --> [].
 1083code_words([Word|T]) --> [w(Word)], code_words(T).
 1084code_words(CodeL) --> ['`','`'], {CodeL = ['`'|T]}, code_words(T).
 1085code_words([Punct|T]) --> [Punct], {atomic(Punct)}, code_words(T).
 eq_code_words(-Words)//
Stuff that can be between single =. This is limited to
 1096eq_code_words([Word]) -->
 1097    [ w(Word) ].
 1098eq_code_words([Word|T]) -->
 1099    [ w(Word) ], eq_code_internals(T, [End]), [w(End)].
 1100
 1101eq_code_internals(T, T) --> [].
 1102eq_code_internals([H|T], Tail) -->
 1103    eq_code_internal(H),
 1104    eq_code_internals(T, Tail).
 1105
 1106eq_code_internal(Word) -->
 1107    [w(Word)].
 1108eq_code_internal(Punct) -->
 1109    [Punct],
 1110    { eq_code_internal_punct(Punct) }.
 1111
 1112eq_code_internal_punct('.').
 1113eq_code_internal_punct('-').
 1114eq_code_internal_punct(':').
 1115eq_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.
 1124code_face(Text, Var, _, Code) :-
 1125    var(Var),
 1126    !,
 1127    Code = var(Text).
 1128code_face(Text, _, _, code(Text)).
 emphasis_seq(-Out, +ArgNames, +Options) is semidet
Recognise emphasis sequences
 1135emphasis_seq(EmphTerm, ArgNames, Options) -->
 1136    emphasis_start(C),
 1137    next_level(Options, NOptions),
 1138    matches(limit(100, wiki_faces(Emph, ArgNames, NOptions)), Input, Last),
 1139    emphasis_end(C),
 1140    { emph_markdown(Last, Input),
 1141      emphasis_term(C, Emph, EmphTerm)
 1142    },
 1143    !.
 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.
 1153emphasis_term('_',   Term, i(Term)).
 1154emphasis_term('*',   Term, b(Term)).
 1155emphasis_term('__',  Term, strong(Term)).
 1156emphasis_term('**',  Term, strong(Term)).
 1157
 1158emph_markdown(_, [w(_)]) :- !.
 1159emph_markdown(Last, Tokens) :-
 1160    \+ emphasis_after_sep(Last),
 1161    E = error(_,_),
 1162    catch(b_getval(pldoc_object, Obj), E, Obj = '??'),
 1163    debug(markdown(emphasis), '~q: additionally emphasis: ~p',
 1164          [Obj, Tokens]).
 1165
 1166emphasis_before(Before) -->
 1167    [Before],
 1168    { emphasis_start_sep(Before) }.
 1169
 1170emphasis_start_sep('\n').
 1171emphasis_start_sep(' ').
 1172emphasis_start_sep('<').
 1173emphasis_start_sep('{').
 1174emphasis_start_sep('(').
 1175emphasis_start_sep('[').
 1176emphasis_start_sep(',').
 1177emphasis_start_sep(':').
 1178emphasis_start_sep(';').
 1179
 1180emphasis_start(Which), [w(Word)] -->
 1181    emphasis(Which),
 1182    [w(Word)].
 1183
 1184emphasis(**)   --> [*, *].
 1185emphasis(*)    --> [*].
 1186emphasis('__') --> ['_', '_'].
 1187emphasis('_')  --> ['_'].
 1188
 1189emphasis_end(Which), [After] -->
 1190    emphasis(Which),
 1191    [ After ],
 1192    !,
 1193    { emphasis_close_sep(After) -> true }.
 1194emphasis_end(Which) -->
 1195    emphasis(Which).
 1196
 1197% these characters should not be before a closing * or _.
 1198
 1199emphasis_after_sep('\n').
 1200emphasis_after_sep(' ').
 1201emphasis_after_sep('(').
 1202emphasis_after_sep('[').
 1203emphasis_after_sep('<').
 1204emphasis_after_sep('=').
 1205emphasis_after_sep('+').
 1206emphasis_after_sep('\\').
 1207emphasis_after_sep('@').
 1208
 1209emphasis_close_sep('\n').                       % white
 1210emphasis_close_sep(' ').                        % white
 1211emphasis_close_sep(',').                        % sentence punctuation
 1212emphasis_close_sep('.').
 1213emphasis_close_sep('!').
 1214emphasis_close_sep('?').
 1215emphasis_close_sep(':').
 1216emphasis_close_sep(';').
 1217emphasis_close_sep(']').                        % [**label**](link)
 1218emphasis_close_sep(')').                        % ... _italic_)
 1219emphasis_close_sep('}').                        % ... _italic_}
 1220emphasis_close_sep(Token) :-
 1221    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.
 1232arg_list(['('|T]) -->
 1233    ['('], arg_list_close(T, 1).
 1234
 1235arg_list_close(Tokens, Depth) -->
 1236    [')'],
 1237    !,
 1238    (   { Depth == 1 }
 1239    ->  { Tokens = [')'] }
 1240    ;   { Depth > 1 }
 1241    ->  { Tokens = [')'|More],
 1242          NewDepth is Depth - 1
 1243        },
 1244        arg_list_close(More, NewDepth)
 1245    ).
 1246arg_list_close(['('|T], Depth) -->
 1247    ['('], { NewDepth is Depth+1 },
 1248    arg_list_close(T, NewDepth).
 1249arg_list_close([H|T], Depth) -->
 1250    [w(H)],
 1251    !,
 1252    arg_list_close(T, Depth).
 1253arg_list_close([H|T], Depth) -->
 1254    [H],
 1255    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.
 1264term_face(_Text, Term, _Vars, \file(Name, FileOptions), Options) :-
 1265    ground(Term),
 1266    compound(Term),
 1267    compound_name_arity(Term, Alias, 1),
 1268    user:file_search_path(Alias, _),
 1269    existing_file(Term, FileOptions, [], Options),
 1270    !,
 1271    format(atom(Name), '~q', [Term]).
 1272term_face(Text, Term, Vars, Face, _Options) :-
 1273    code_face(Text, Term, Vars, Face).
 1274
 1275untag([], []).
 1276untag([w(W)|T0], [W|T]) :-
 1277    !,
 1278    untag(T0, T).
 1279untag([H|T0], [H|T]) :-
 1280    untag(T0, T).
 image_label(-Label)//
Match File[;param=value[,param=value]*]
 1286image_label(\include(Name, image, Options)) -->
 1287    file_name(Base, Ext),
 1288    { autolink_extension(Ext, image),
 1289      file_name_extension(Base, Ext, Name),
 1290      resolve_file(Name, Options, RestOptions)
 1291    },
 1292    file_options(RestOptions).
 1293
 1294
 1295take_predref(\predref(Name/Arity)) -->
 1296    [ w(Name), '/' ], arity(Arity),
 1297    { functor_name(Name)
 1298    }.
 1299take_predref(\predref(Module:(Name/Arity))) -->
 1300    [ w(Module), ':', w(Name), '/' ], arity(Arity),
 1301    { functor_name(Name)
 1302    }.
 1303take_predref(\predref(Name/Arity)) -->
 1304    prolog_symbol_char(S0),
 1305    symbol_string(SRest), [ '/' ], arity(Arity),
 1306    !,
 1307    { atom_chars(Name, [S0|SRest])
 1308    }.
 1309take_predref(\predref(Name//Arity)) -->
 1310    [ w(Name), '/', '/' ], arity(Arity),
 1311    { functor_name(Name)
 1312    }.
 1313take_predref(\predref(Module:(Name//Arity))) -->
 1314    [ w(Module), ':', w(Name), '/', '/' ], arity(Arity),
 1315    { functor_name(Name)
 1316    }.
 file_options(-Options) is det
Extracts additional processing options for files. The format is ;name="value",name2=value2,... Spaces are not allowed.
 1323file_options(Options) -->
 1324    [;], nv_pairs(Options),
 1325    !.
 1326file_options([]) -->
 1327    [].
 1328
 1329nv_pairs([H|T]) -->
 1330    nv_pair(H),
 1331    (   [',']
 1332    ->  nv_pairs(T)
 1333    ;   {T=[]}
 1334    ).
 1335
 1336nv_pair(Option) -->
 1337    [ w(Name), =,'"'], tokens(Tokens), ['"'],
 1338    !,
 1339    { untag(Tokens, Atoms),
 1340      atomic_list_concat(Atoms, Value0),
 1341      (   atom_number(Value0, Value)
 1342      ->  true
 1343      ;   Value = Value0
 1344      ),
 1345      Option =.. [Name,Value]
 1346    }.
 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.
 1362:- multifile
 1363    user:url_path/2. 1364
 1365wiki_link(\file(Name, FileOptions), Options) -->
 1366    file_name(Base, Ext),
 1367    { file_name_extension(Base, Ext, Name),
 1368      (   autolink_file(Name, _)
 1369      ;   autolink_extension(Ext, _)
 1370      ),
 1371      !,
 1372      resolve_file(Name, FileOptions, Options)
 1373    }.
 1374wiki_link(\file(Name, FileOptions), Options) -->
 1375    [w(Name)],
 1376    { autolink_file(Name, _),
 1377      !,
 1378      resolve_file(Name, FileOptions, Options)
 1379    },
 1380    !.
 1381wiki_link(a(href(Ref), Label), Options) -->
 1382    [ w(Prot),:,/,/], { url_protocol(Prot) },
 1383    { option(end(End), Options, space)
 1384    },
 1385    tokens_no_whitespace(Rest), peek_end_url(End),
 1386    !,
 1387    { atomic_list_concat([Prot, :,/,/ | Rest], Ref),
 1388      option(label(Label), Options, Ref)
 1389    }.
 1390wiki_link(a(href(Ref), Label), _Options) -->
 1391    [<, w(Alias), :],
 1392    tokens_no_whitespace(Rest), [>],
 1393    { Term = (Alias:Rest),
 1394      prolog:url_expansion_hook(Term, Ref, Label), !
 1395    }.
 1396wiki_link(a(href(Ref), Label), Options) -->
 1397    [<, w(Alias), :],
 1398    { user:url_path(Alias, _)
 1399    },
 1400    tokens_no_whitespace(Rest), [>],
 1401    { atomic_list_concat(Rest, Local),
 1402      (   Local == ''
 1403      ->  Term =.. [Alias,'.']
 1404      ;   Term =.. [Alias,Local]
 1405      ),
 1406      E = error(_,_),
 1407      catch(expand_url_path(Term, Ref), E, fail),
 1408      option(label(Label), Options, Ref)
 1409    }.
 1410wiki_link(a(href(Ref), Label), Options) -->
 1411    [#, w(First)],
 1412    { option(end(End), Options) },
 1413    tokens_no_whitespace(Rest),
 1414    peek_end_url(End),
 1415    !,
 1416    { atomic_list_concat([#,First|Rest], Ref),
 1417      option(label(Label), Options, Ref)
 1418    }.
 1419wiki_link(a(href(Ref), Label), Options) -->
 1420    [<],
 1421    (   { option(relative(true), Options),
 1422          Parts = Rest
 1423        }
 1424    ->  tokens_no_whitespace(Rest)
 1425    ;   { Parts = [Prot, : | Rest]
 1426        },
 1427        [w(Prot), :], tokens_no_whitespace(Rest)
 1428    ),
 1429    [>],
 1430    !,
 1431    { atomic_list_concat(Parts, Ref),
 1432      option(label(Label), Options, Ref)
 1433    }.
 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>.
 1447file_name(FileBase, Extension) -->
 1448    segment(S1),
 1449    segments(List),
 1450    ['.'], file_extension(Extension),
 1451    !,
 1452    { atomic_list_concat([S1|List], '/', FileBase) }.
 1453file_name(FileBase, Extension) -->
 1454    [w(Alias), '('],
 1455    { once(user:file_search_path(Alias, _)) },
 1456    segment(S1),
 1457    segments(List),
 1458    [')'],
 1459    !,
 1460    { atomic_list_concat([S1|List], '/', Base),
 1461      Spec =.. [Alias,Base],
 1462      absolute_file_name(Spec, Path,
 1463                         [ access(read),
 1464                           extensions([pl]),
 1465                           file_type(prolog),
 1466                           file_errors(fail)
 1467                         ]),
 1468      file_name_extension(FileBase, Extension, Path)
 1469    }.
 1470
 1471
 1472segment(..) -->
 1473    ['.','.'],
 1474    !.
 1475segment(Word) -->
 1476    [w(Word)].
 1477segment(Dir) -->
 1478    [w(Word),'.',w(d)],
 1479    { atom_concat(Word, '.d', Dir) }.
 1480
 1481segments([H|T]) -->
 1482    ['/'],
 1483    !,
 1484    segment(H),
 1485    segments(T).
 1486segments([]) -->
 1487    [].
 1488
 1489file_extension(Ext) -->
 1490    [w(Ext)],
 1491    { autolink_extension(Ext, _)
 1492    }.
 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.
 1502resolve_file(Name, FileOptions, Rest) :-
 1503    existing_file(Name, FileOptions, Rest, []),
 1504    !.
 1505resolve_file(_, Options, Options).
 1506
 1507
 1508existing_file(Name, FileOptions, Rest, Options) :-
 1509    \+ Options.get(link) == false,
 1510    E = error(_,_),
 1511    catch(existing_file_p(Name, FileOptions, Rest), E, fail).
 1512
 1513existing_file_p(Name, FileOptions, Rest) :-
 1514    (   nb_current(pldoc_file, RelativeTo),
 1515        RelativeTo \== []
 1516    ->  Extra = [relative_to(RelativeTo)|Extra1]
 1517    ;   Extra = Extra1
 1518    ),
 1519    (   compound(Name)
 1520    ->  Extra1 = [file_type(prolog)]
 1521    ;   Extra1 = []
 1522    ),
 1523    absolute_file_name(Name, Path,
 1524                       [ access(read),
 1525                         file_errors(fail)
 1526                       | Extra
 1527                       ]),
 1528    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.
 1537arity(Arity) -->
 1538    [ w(Word) ],
 1539    { E = error(_,_),
 1540      catch(atom_number(Word, Arity), E, fail),
 1541      Arity >= 0, Arity < 20
 1542    }.
 symbol_string(-String)// is nondet
Accept a sequence of Prolog symbol characters, starting with the shortest (empty) match.
 1549symbol_string([]) -->
 1550    [].
 1551symbol_string([H|T]) -->
 1552    [H],
 1553    { prolog_symbol_char(H) },
 1554    symbol_string(T).
 1555
 1556prolog_symbol_char(C) -->
 1557    [C],
 1558    { prolog_symbol_char(C) }.
 prolog_symbol_char(?Char)
True if char is classified by Prolog as a symbol char.
 1564prolog_symbol_char(#).
 1565prolog_symbol_char($).
 1566prolog_symbol_char(&).
 1567prolog_symbol_char(*).
 1568prolog_symbol_char(+).
 1569prolog_symbol_char(-).
 1570prolog_symbol_char(.).
 1571prolog_symbol_char(/).
 1572prolog_symbol_char(:).
 1573prolog_symbol_char(<).
 1574prolog_symbol_char(=).
 1575prolog_symbol_char(>).
 1576prolog_symbol_char(?).
 1577prolog_symbol_char(@).
 1578prolog_symbol_char(\).
 1579prolog_symbol_char(^).
 1580prolog_symbol_char(~).
 1581
 1582
 1583functor_name(String) :-
 1584    sub_atom(String, 0, 1, _, Char),
 1585    char_type(Char, lower).
 1586
 1587url_protocol(http).
 1588url_protocol(https).
 1589url_protocol(ftp).
 1590url_protocol(mailto).
 1591
 1592peek_end_url(space) -->
 1593    peek(Punct, End),
 1594    { punct_token(Punct),
 1595      space_token(End)
 1596    },
 1597    !.
 1598peek_end_url(space) -->
 1599    peek(End),
 1600    { space_token(End) },
 1601    !.
 1602peek_end_url(space, [], []) :- !.
 1603peek_end_url(Token) -->
 1604    peek(Token),
 1605    !.
 1606
 1607punct_token('.').
 1608punct_token('!').
 1609punct_token('?').
 1610punct_token(',').
 1611punct_token(';').
 1612
 1613space_token(' ') :- !.
 1614space_token('\r') :- !.
 1615space_token('\n') :- !.
 1616space_token(T) :-
 1617    \+ atom(T),                     % high level format like p(...)
 1618    \+ T = w(_).
 autolink_extension(?Ext, ?Type) is nondet
True if Ext is a filename extensions that create automatic links in the documentation.
 1625autolink_extension(Ext, Type) :-
 1626    prolog:doc_autolink_extension(Ext, Type),
 1627    !.
 1628autolink_extension(Ext, prolog) :-
 1629    user:prolog_file_type(Ext,prolog),
 1630    !.
 1631autolink_extension(txt, wiki).
 1632autolink_extension(md,  wiki).
 1633autolink_extension(gif, image).
 1634autolink_extension(png, image).
 1635autolink_extension(jpg, image).
 1636autolink_extension(jpeg, image).
 1637autolink_extension(svg, image).
 autolink_file(?File, -Type) is nondet
Files to which we automatically create links, regardless of the extension.
 1644autolink_file('README', wiki).
 1645autolink_file('TODO', wiki).
 1646autolink_file('ChangeLog', wiki).
 citations(-List)//
Parse @cite1[;@cite2]* into a list of citations.
 1652citations([H|T]) -->
 1653    citation(H),
 1654    (   [';']
 1655    ->  citations(T)
 1656    ;   {T=[]}
 1657    ).
 1658
 1659citation(Atom) -->
 1660    [@], wiki_words(Atoms),
 1661    { length(Atoms, Len),
 1662      Len > 10, !,
 1663      fail
 1664    ; true
 1665    },
 1666    end_citation,
 1667    !,
 1668    { atomic_list_concat(Atoms, Atom)
 1669    }.
 1670
 1671end_citation, [';'] --> [';'].
 1672end_citation, ['@'] --> ['@'].
 1673end_citation, [']'] --> [']'].
 1674
 1675
 1676                 /*******************************
 1677                 *           SECTIONS           *
 1678                 *******************************/
 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.
 1691section_comment_header([_-Line|Lines], Header, Lines) :-
 1692    phrase(section_line(Header), Line).
 1693
 1694section_line(\section(Type, Title)) -->
 1695    ws, "<", word(Codes), ">", normalise_white_space(TitleCodes),
 1696    { atom_codes(Type, Codes),
 1697      atom_codes(Title, TitleCodes)
 1698    }.
 1699
 1700                 /*******************************
 1701                 *           TOKENIZER          *
 1702                 *******************************/
 tokenize_lines(+Lines:lines, -TokenLines) is det
Convert Indent-Codes into Indent-Tokens
 1708tokenize_lines(Lines, TokenLines) :-
 1709    tokenize_lines(Lines, -1, TokenLines).
 1710
 1711tokenize_lines([], _, []) :- !.
 1712tokenize_lines(Lines, Indent, [Pre|T]) :-
 1713    verbatim(Lines, Indent, Pre, RestLines),
 1714    !,
 1715    tokenize_lines(RestLines, Indent, T).
 1716tokenize_lines([I-H0|T0], Indent0, [I-H|T]) :-
 1717    phrase(line_tokens(H), H0),
 1718    (   H == []
 1719    ->  Indent = Indent0
 1720    ;   Indent = I
 1721    ),
 1722    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), '_'].
 1733line_tokens([H|T]) -->
 1734    line_token(H),
 1735    !,
 1736    line_tokens(T).
 1737line_tokens([]) -->
 1738    [].
 1739
 1740line_token(T) -->
 1741    [C],
 1742    (   { code_type(C, space) }
 1743    ->  ws,
 1744        { T = ' ' }
 1745    ;   { code_type(C, alnum) },
 1746        word(Rest),
 1747        { atom_codes(W, [C|Rest]),
 1748          T = w(W)
 1749        }
 1750    ;   { char_code(T, C) }
 1751    ).
 1752
 1753word([C0|T]) -->
 1754    [C0],  { code_type(C0, alnum) },
 1755    !,
 1756    word(T).
 1757word([0'_, C1|T]) -->
 1758    [0'_, C1],  { code_type(C1, alnum) },
 1759    !,
 1760    word(T).
 1761word([]) -->
 1762    [].
 1763
 1764alphas([C0|T]) -->
 1765    [C0],  { code_type(C0, alpha) },
 1766    !,
 1767    alphas(T).
 1768alphas([]) -->
 1769    [].
 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.

 1791verbatim(Lines, _,
 1792         Indent-pre([class(code), ext(Ext)],Pre),
 1793         RestLines) :-
 1794    skip_empty_lines(Lines, [Indent-FenceLine|CodeLines]),
 1795    verbatim_fence(FenceLine, Fence, Ext),
 1796    verbatim_body(CodeLines, Indent, [10|PreCodes], [],
 1797                  [Indent-Fence|RestLines]),
 1798    !,
 1799    atom_codes(Pre, PreCodes).
 1800verbatim([_-[],Indent-Line|Lines], EnvIndent,
 1801         Indent-pre(class(code),Pre),
 1802         RestLines) :-
 1803    EnvIndent >= 0,
 1804    Indent >= EnvIndent+4, Indent =< EnvIndent+8,
 1805    valid_verbatim_opening(Line),
 1806    indented_verbatim_body([Indent-Line|Lines], Indent,
 1807                           CodeLines, RestLines),
 1808    !,
 1809    lines_code_text(CodeLines, Indent, [10|PreCodes]),
 1810    atom_codes(Pre, PreCodes).
 1811
 1812verbatim_body(Lines, _, PreT, PreT, Lines).
 1813verbatim_body([I-L|Lines], Indent, [10|Pre], PreT, RestLines) :-
 1814    PreI is I - Indent,
 1815    phrase(pre_indent(PreI), Pre, PreT0),
 1816    verbatim_line(L, PreT0, PreT1),
 1817    verbatim_body(Lines, Indent, PreT1, PreT, RestLines).
 1818
 1819verbatim_fence(Line, Fence, '') :-
 1820    Line == [0'=,0'=],
 1821    !,
 1822    Fence = Line.
 1823verbatim_fence(Line, Fence, Ext) :-
 1824    tilde_fence(Line, Fence, 0, Ext).
 1825verbatim_fence(Line, Fence, Ext) :-
 1826    md_fence(Line, Fence, 0, Ext).
 1827
 1828tilde_fence([0'~|T0], [0'~|F0], C0, Ext) :-
 1829    !,
 1830    C1 is C0+1,
 1831    tilde_fence(T0, F0, C1, Ext).
 1832tilde_fence(List, [], C, Ext) :-
 1833    C >= 3,
 1834    (   List == []
 1835    ->  Ext = ''
 1836    ;   phrase(tilde_fence_ext(ExtCodes), List)
 1837    ->  atom_codes(Ext, ExtCodes)
 1838    ).
 tilde_fence_ext(-Ext)// is semidet
Detect `{.prolog} (Doxygen) or `{prolog} (GitHub)
 1844tilde_fence_ext(Ext) -->
 1845    "{.", !, alphas(Ext), "}".
 1846tilde_fence_ext(Ext) -->
 1847    "{", alphas(Ext), "}".
 1848
 1849md_fence([0'`|T0], [0'`|F0], C0, Ext) :-
 1850    !,
 1851    C1 is C0+1,
 1852    md_fence(T0, F0, C1, Ext).
 1853md_fence(List, [], C, Ext) :-
 1854    C >= 3,
 1855    (   List == []
 1856    ->  Ext = ''
 1857    ;   phrase(md_fence_ext(ExtCodes), List),
 1858        atom_codes(Ext, ExtCodes)
 1859    ).
 1860
 1861% Also support Doxygen's curly bracket notation.
 1862md_fence_ext(Ext) -->
 1863    tilde_fence_ext(Ext),
 1864    !.
 1865% In Markdown language names appear without brackets.
 1866md_fence_ext(Ext) -->
 1867    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.
 1875indented_verbatim_body([I-L|T0], Indent, [I-L|T], RestLines) :-
 1876    L \== [], I >= Indent,
 1877    !,
 1878    indented_verbatim_body(T0, Indent, T, RestLines).
 1879indented_verbatim_body([I0-[],I-L|T0], Indent, [I0-[],I-L|T], RestLines) :-
 1880    I >= Indent,
 1881    valid_verbatim_opening(L),
 1882    indented_verbatim_body(T0, Indent, T, RestLines).
 1883indented_verbatim_body(Lines, _, [], Lines).
 valid_verbatim_opening(+Line) is semidet
Tests that line does not look like a list item or table.
 1889valid_verbatim_opening([0'||_]) :- !, fail.
 1890valid_verbatim_opening(Line) :-
 1891    Line \== [],
 1892    \+ ( phrase(line_tokens(Tokens), Line),
 1893         list_item_prefix(_Type, Tokens, _Rest)
 1894       ).
 lines_code_text(+Lines, +Indent, -Codes) is det
Extract the actual code content from a list of line structures.
 1900lines_code_text([], _, []).
 1901lines_code_text([_-[]|T0], Indent, [10|T]) :-
 1902    !,
 1903    lines_code_text(T0, Indent, T).
 1904lines_code_text([I-Line|T0], Indent, [10|T]) :-
 1905    PreI is I-Indent,
 1906    phrase(pre_indent(PreI), T, T1),
 1907    verbatim_line(Line, T1, T2),
 1908    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.
 1916pre_indent(N) -->
 1917    { N > 0,
 1918      !,
 1919      N2 is N - 1
 1920    }, " ",
 1921    pre_indent(N2).
 1922pre_indent(_) -->
 1923    "".
 1924
 1925verbatim_line(Line, Pre, PreT) :-
 1926    append(Line, PreT, Pre).
 1927
 1928
 1929                 /*******************************
 1930                 *            SUMMARY           *
 1931                 *******************************/
 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.
 1940summary_from_lines(Lines, Sentence) :-
 1941    skip_empty_lines(Lines, Lines1),
 1942    summary2(Lines1, Sentence0),
 1943    end_sentence(Sentence0, Sentence).
 1944
 1945summary2(_, Sentence) :-
 1946    Sentence == [],
 1947    !.              % we finished our sentence
 1948summary2([], []) :- !.
 1949summary2([_-[]|_], []) :- !.            % empty line
 1950summary2([_-[0'@|_]|_], []) :- !.       % keyword line
 1951summary2([_-L0|Lines], Sentence) :-
 1952    phrase(sentence(Sentence, Tail), L0, _),
 1953    summary2(Lines, Tail).
 1954
 1955sentence([C,End], []) -->
 1956    [C,End],
 1957    { \+ code_type(C, period),
 1958      code_type(End, period)                % ., !, ?
 1959    },
 1960    space_or_eos,
 1961    !.
 1962sentence([0' |T0], T) -->
 1963    space,
 1964    !,
 1965    ws,
 1966    sentence(T0, T).
 1967sentence([H|T0], T) -->
 1968    [H],
 1969    sentence(T0, T).
 1970sentence([0' |T], T) -->                % '
 1971    eos.
 1972
 1973space_or_eos -->
 1974    [C],
 1975    !,
 1976    {code_type(C, space)}.
 1977space_or_eos -->
 1978    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.
 1985skip_empty_lines([], []).
 1986skip_empty_lines([_-[]|Lines0], Lines) :-
 1987    !,
 1988    skip_empty_lines(Lines0, Lines).
 1989skip_empty_lines(Lines, Lines).
 1990
 1991end_sentence([], []).
 1992end_sentence([0'\s], [0'.]) :- !.
 1993end_sentence([H|T0], [H|T]) :-
 1994    end_sentence(T0, T).
 1995
 1996
 1997                 /*******************************
 1998                 *        CREATE LINES          *
 1999                 *******************************/
 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.
 2008indented_lines(Comment, Prefixes, Lines) :-
 2009    must_be(codes, Comment),
 2010    phrase(split_lines(Prefixes, Lines), Comment),
 2011    !.
 2012
 2013split_lines(_, []) -->
 2014    end_of_comment.
 2015split_lines(Prefixes, [Indent-L1|Ls]) -->
 2016    take_prefix(Prefixes, 0, Indent0),
 2017    white_prefix(Indent0, Indent),
 2018    take_line(L1),
 2019    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.
 2028end_of_comment -->
 2029    eos.
 2030end_of_comment -->
 2031    ws, stars, "*/".
 2032
 2033stars --> [].
 2034stars --> "*", !, 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.
 2042take_prefix(Prefixes, I0, I) -->
 2043    { member(Prefix, Prefixes),
 2044      string_codes(Prefix, PrefixCodes)
 2045    },
 2046    prefix(PrefixCodes),
 2047    !,
 2048    { string_update_linepos(PrefixCodes, I0, I) }.
 2049take_prefix(_, I, I) -->
 2050    [].
 2051
 2052prefix([]) --> [].
 2053prefix([H|T]) --> [H], prefix(T).
 2054
 2055white_prefix(I0, I) -->
 2056    [C],
 2057    {  code_type(C, white),
 2058       !,
 2059       update_linepos(C, I0, I1)
 2060    },
 2061    white_prefix(I1, I).
 2062white_prefix(I, I) -->
 2063    [].
 string_update_linepos(+Codes, +Pos0, -Pos) is det
Update line-position after adding Codes at Pos0.
 2069string_update_linepos([], I, I).
 2070string_update_linepos([H|T], I0, I) :-
 2071    update_linepos(H, I0, I1),
 2072    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.
 2080update_linepos(0'\t, I0, I) :-
 2081    !,
 2082    I is (I0\/7)+1.
 2083update_linepos(0'\b, I0, I) :-
 2084    !,
 2085    I is max(0, I0-1).
 2086update_linepos(0'\r, _, 0) :- !.
 2087update_linepos(0'\n, _, 0) :- !.
 2088update_linepos(_, I0, I) :-
 2089    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.
 2096take_line([]) -->
 2097    "\r\n",
 2098    !.                      % DOS file
 2099take_line([]) -->
 2100    "\n",
 2101    !.                        % Unix file
 2102take_line(Line) -->
 2103    [H], { code_type(H, white) },
 2104    !,
 2105    take_white(White, WT),
 2106    (   nl
 2107    ->  { Line = [] }
 2108    ;   { Line = [H|White] },
 2109        take_line(WT)
 2110    ).
 2111take_line([H|T]) -->
 2112    [H],
 2113    !,
 2114    take_line(T).
 2115take_line([]) -->                       % end of string
 2116    [].
 2117
 2118take_white([H|T0], T) -->
 2119    [H],  { code_type(H, white) },
 2120    !,
 2121    take_white(T0, T).
 2122take_white(T, T) -->
 2123    [].
 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.
 2130normalise_indentation(Lines0, Lines) :-
 2131    skip_empty_lines(Lines0, Lines1),
 2132    Lines1 = [I0-_|Lines2],
 2133    !,
 2134    smallest_indentation(Lines2, I0, Subtract),
 2135    (   Subtract == 0
 2136    ->  Lines = Lines0
 2137    ;   maplist(substract_indent(Subtract), Lines0, Lines)
 2138    ).
 2139normalise_indentation(Lines, Lines).
 2140
 2141smallest_indentation([], I, I).
 2142smallest_indentation([_-[]|T], I0, I) :-
 2143    !,
 2144    smallest_indentation(T, I0, I).
 2145smallest_indentation([X-_|T], I0, I) :-
 2146    I1 is min(I0, X),
 2147    smallest_indentation(T, I1, I).
 2148
 2149substract_indent(Subtract, I0-L, I-L) :-
 2150    I is max(0,I0-Subtract).
 2151
 2152
 2153                 /*******************************
 2154                 *             MISC             *
 2155                 *******************************/
 strip_leading_par(+Dom0, -Dom) is det
Remove the leading paragraph for environments where a paragraph is not required.
 2162strip_leading_par([p(C)|T], L) :-
 2163    !,
 2164    append(C, T, L).
 2165strip_leading_par(L, L).
 2166
 2167
 2168                 /*******************************
 2169                 *           DCG BASICS         *
 2170                 *******************************/
 ws// is det
Eagerly skip layout characters
 2176ws -->
 2177    [C], {code_type(C, space)},
 2178    !,
 2179    ws.
 2180ws -->
 2181    [].
 2182
 2183%       space// is det
 2184%
 2185%       True if then next code is layout.
 2186
 2187space -->
 2188    [C],
 2189    {code_type(C, space)}.
 nl//
Get end-of-line
 2195nl -->
 2196    "\r\n",
 2197    !.
 2198nl -->
 2199    "\n".
 peek(H)//
True if next token is H without eating it.
 2205peek(H, L, L) :-
 2206    L = [H|_].
 2207
 2208peek(H1, H2, L, L) :-
 2209    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.
 2217tokens([]) --> [].
 2218tokens([H|T]) --> token(H), tokens(T).
 2219
 2220tokens(_, []) --> [].
 2221tokens(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.
 2229tokens_no_whitespace([]) -->
 2230    [].
 2231tokens_no_whitespace([Word|T]) -->
 2232    [ w(Word) ],
 2233    !,
 2234    tokens_no_whitespace(T).
 2235tokens_no_whitespace([H|T]) -->
 2236    [H],
 2237    { \+ space_token(H) },
 2238    tokens_no_whitespace(T).
 2239
 2240token(Token) -->
 2241    [Token],
 2242    { token(Token) }.
 2243
 2244token(w(_)) :- !.
 2245token(Token) :- atom(Token).
 limit(+Count, :Rule)//
As limit/2, but for grammar rules.
 2251:- meta_predicate limit(+,2,?,?). 2252
 2253limit(Count, Rule, Input, Rest) :-
 2254    Count > 0,
 2255    State = count(0),
 2256    call(Rule, Input, Rest),
 2257    arg(1, State, N0),
 2258    N is N0+1,
 2259    (   N =:= Count
 2260    ->  !
 2261    ;   nb_setarg(1, State, N)
 2262    ).
 2263
 2264
 2265                 /*******************************
 2266                 *           MESSAGES           *
 2267                 *******************************/
 2268
 2269:- multifile
 2270    prolog:message//1. 2271
 2272prolog:message(pldoc(deprecated_tag(Name, Tag))) -->
 2273    [ 'PlDoc: Deprecated tag @~w (use @~w)'-[Name, Tag]
 2274    ].
 2275prolog:message(pldoc(unknown_tag(Name))) -->
 2276    [ 'PlDoc: unknown tag @~w'-[Name]
 2277    ]