View source with formatted 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)  2020-2026, VU University Amsterdam
    7                              CWI, Amsterdam
    8                              SWI-Prolog Solutions b.v.
    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(prolog_deps,
   38          [ file_autoload_directives/3,      % +File, -Directives, +Options
   39            file_auto_import/2               % +File, +Options
   40          ]).   41:- autoload(library(apply), [convlist/3, maplist/3, exclude/3]).   42:- if(exists_source(library(filesex))).   43:- autoload(library(filesex), [copy_file/2]).   44:- endif.   45:- autoload(library(lists), [select/3, append/3, member/2]).   46:- autoload(library(option), [option/2, option/3]).   47:- autoload(library(pairs), [group_pairs_by_key/2]).   48:- autoload(library(pprint), [print_term/2]).   49:- autoload(library(prolog_code), [pi_head/2]).   50:- autoload(library(prolog_source),
   51            [ file_name_on_path/2,
   52              path_segments_atom/2,
   53              prolog_open_source/2,
   54              prolog_read_source_term/4,
   55              prolog_close_source/1
   56            ]).   57:- autoload(library(prolog_xref),
   58            [ xref_source/1,
   59              xref_module/2,
   60              xref_called/4,
   61              xref_defined/3,
   62              xref_built_in/1,
   63              xref_public_list/3
   64            ]).   65:- autoload(library(readutil), [read_file_to_string/3]).   66:- autoload(library(solution_sequences), [distinct/2]).   67:- autoload(library(error), [existence_error/2]).   68
   69/** <module> Compute file dependencies
   70
   71This module computes  file  dependencies  for   _modules_  as  a  set of
   72directives.
   73*/
   74
   75:- multifile user:file_search_path/2.   76
   77user:file_search_path(noautoload, library(.)).
   78user:file_search_path(noautoload, library(semweb)).
   79user:file_search_path(noautoload, library(lynx)).
   80user:file_search_path(noautoload, library(tipc)).
   81user:file_search_path(noautoload, library(cql)).
   82user:file_search_path(noautoload, library(http)).
   83user:file_search_path(noautoload, library(dcg)).
   84user:file_search_path(noautoload, library(unicode)).
   85user:file_search_path(noautoload, library(clp)).
   86user:file_search_path(noautoload, library(pce(prolog/lib))).
   87
   88
   89%!  file_autoload_directives(+File, -Directives, +Options) is det.
   90%
   91%   Compute the dependencies as autoload/2 directives.  Options
   92%
   93%     - missing(+Bool)
   94%       If `true` (default `false`), only generate directives
   95%       for called predicates that are not already imported.
   96%
   97%     - directive(+Directive)
   98%       Directive to use for adding dependencies.  Defined
   99%	options are:
  100%
  101%       - use_autoload/2
  102%         (Default).  This uses use_module/2 for files that
  103%         cannot be imported using use_autoload/2.
  104%       - use_autoload/1
  105%         This uses use_module/1 for files that cannot be
  106%	  imported using use_autoload/1.
  107%       - use_module/2
  108%       - use_module/1
  109%
  110%     - update(Old)
  111%       Updated an existing set of directives.  The returned
  112%       set of Directive starts with copies of Old.  If a
  113%       member of Old is autoload/2 or use_module/2, new
  114%       dependencies are added at the end of this list.
  115%       New dependent files are added after the modified
  116%       copies of Old.  Declared dependencies are never
  117%       removed, even if no proof of usage is found.
  118%
  119%       If no directive(+Directive) option is provided a
  120%       default is determined from the given directives.
  121
  122file_autoload_directives(File, Directives, Options) :-
  123    xref_source(File),
  124    findall(Head, distinct(Head, undefined(File, Head, Options)), Missing0),
  125    clean_missing(Missing0, Missing),
  126    option(update(Old), Options, []),
  127    convlist(missing_autoload(File, Old), Missing, Pairs),
  128    keysort(Pairs, Pairs1),
  129    group_pairs_by_key(Pairs1, Grouped),
  130    directives(File, Grouped, Directives, Options).
  131
  132%!  undefined(+File, -Callable, +Options)
  133%
  134%   Callable is called in File, but no   definition can be found. If
  135%   File is not a module file we   consider other files that are not
  136%   module files.
  137
  138undefined(File, Undef, Options) :-
  139    xref_module(File, _),
  140    !,
  141    xref_called_cond(File, Undef, Cond),
  142    \+ (   available(File, Undef, How, Options),
  143           How \== plain_file
  144       ),
  145    included_if_defined(Cond, Undef),
  146    Undef \= (_:_).
  147undefined(File, Undef, Options) :-
  148    xref_called_cond(File, Undef, Cond),
  149    \+ available(File, Undef, _, Options),
  150    included_if_defined(Cond, Undef),
  151    Undef \= (_:_).
  152
  153%!  included_if_defined(+Condition, +Callable) is semidet.
  154
  155included_if_defined(true, _)  :- !.
  156included_if_defined(false, _) :- !, fail.
  157included_if_defined(fail, _)  :- !, fail.
  158included_if_defined(current_predicate(Name/Arity), Callable) :-
  159    \+ functor(Callable, Name, Arity),
  160    !.
  161included_if_defined(\+ Cond, Callable) :-
  162    !,
  163    \+ included_if_defined(Cond, Callable).
  164included_if_defined((A,B), Callable) :-
  165    !,
  166    included_if_defined(A, Callable),
  167    included_if_defined(B, Callable).
  168included_if_defined((A;B), Callable) :-
  169    !,
  170    (   included_if_defined(A, Callable)
  171    ;   included_if_defined(B, Callable)
  172    ).
  173
  174xref_called_cond(Source, Callable, Cond) :-
  175    xref_called(Source, Callable, By, Cond),
  176    By \= Callable.                 % recursive calls
  177
  178%!  available(+File, +Callable, -HowDefined, +Options)
  179%
  180%   True if Callable is available in File.
  181
  182available(File, Called, How, Options) :-
  183    xref_defined(File, Called, How0),
  184    (   How0 = imported(_)
  185    ->  option(missing(true), Options)
  186    ;   true
  187    ),
  188    !,
  189    How = How0.
  190available(_, Called, How, _) :-
  191    built_in_predicate(Called),
  192    !,
  193    How = builtin.
  194available(_, Called, How, _) :-
  195    Called = _:_,
  196    defined(_, Called),
  197    !,
  198    How = module_qualified.
  199available(_, M:G, How, _) :-
  200    defined(ExportFile, G),
  201    xref_module(ExportFile, M),
  202    !,
  203    How = module_overruled.
  204available(_, Called, How, _) :-
  205    defined(ExportFile, Called),
  206    \+ xref_module(ExportFile, _),
  207    !,
  208    How == plain_file.
  209
  210%!  built_in_predicate(+Callable)
  211%
  212%   True if Callable is a built-in
  213
  214built_in_predicate(Goal) :-
  215    strip_module(Goal, _, Plain),
  216    xref_built_in(Plain).
  217
  218%!  defined(?File, ?Callable)
  219%
  220%   True if Callable is defined in File and not imported.
  221
  222defined(File, Callable) :-
  223    xref_defined(File, Callable, How),
  224    How \= imported(_).
  225
  226%!  clean_missing(+Missing0, -Missing) is det.
  227%
  228%   Hack to deal with library(main) and library(optparse) issues.
  229%
  230%   @tbd Needs a more fundamental solution.
  231
  232clean_missing(Missing0, Missing) :-
  233    memberchk(main, Missing0),
  234    memberchk(argv_options(_,_,_), Missing0),
  235    !,
  236    exclude(argv_option_hook, Missing0, Missing).
  237clean_missing(Missing, Missing).
  238
  239argv_option_hook(opt_type(_,_,_)).
  240argv_option_hook(opt_help(_,_)).
  241argv_option_hook(opt_meta(_,_)).
  242
  243
  244		 /*******************************
  245		 *       GENERATE OUTPUT	*
  246		 *******************************/
  247
  248missing_autoload(Src, _, Head, From-Head) :-
  249    xref_defined(Src, Head, imported(From)),
  250    !.
  251missing_autoload(Src, Directives, Head, File-Head) :-
  252    src_file(Src, SrcFile),
  253    member(:-(Dir), Directives),
  254    directive_file(Dir, FileSpec),
  255    absolute_file_name(FileSpec, File,
  256                       [ file_type(prolog),
  257                         file_errors(fail),
  258                         relative_to(SrcFile),
  259                         access(read)
  260                       ]),
  261    xref_public_list(File, SrcFile, [exports(Exports)]),
  262    member(PI, Exports),
  263    is_pi(PI),
  264    pi_head(PI, Head),
  265    !.
  266missing_autoload(_Src, _, Head, File-Head) :-
  267    predicate_property(Head, autoload(File0)),
  268    !,
  269    (   absolute_file_name(File0, File1,
  270                           [ access(read),
  271                             file_type(prolog),
  272                             file_errors(fail)
  273                           ])
  274    ->  qlf_pl_file(File1, File)
  275    ;   File = File0
  276    ).
  277missing_autoload(_Src, _, Head, File-Head) :-
  278    noautoload(Head, File),
  279    !.
  280missing_autoload(_Src, _, Head, _) :-
  281    pi_head(PI, Head),
  282    print_message(warning,
  283                  error(existence_error(procedure, PI), _)),
  284    fail.
  285
  286:- if(exists_source(library(pce))).  287:- autoload(library(pce), [get/3,object/1]).  288src_file(Ref, File) =>
  289    object(Ref),
  290    get(?(Ref, file), absolute_path, File).
  291:- endif.  292src_file(File0, File) =>
  293    File = File0.
  294
  295%!  directives(+File, +FileAndHeads, -Directives, +Options) is det.
  296%
  297%   Assemble the final set of directives. Uses the option update(Old).
  298
  299directives(File, FileAndHeads, Directives, Options) :-
  300    option(update(Old), Options, []),
  301    phrase(update_directives(Old, FileAndHeads, RestDeps, File),
  302           Directives, Rest),
  303    update_style(Old, Options, Options1),
  304    maplist(directive(Options1), RestDeps, Rest0),
  305    sort(Rest0, Rest).
  306
  307update_directives([], Deps, Deps, _) -->
  308    [].
  309update_directives([:-(H)|T], Deps0, Deps, File) -->
  310    { update_directive(File, H, Deps0, Deps1, Directive) },
  311    !,
  312    [ :-(Directive) ],
  313    update_directives(T, Deps1, Deps, File).
  314update_directives([H|T], Deps0, Deps, File) -->
  315    [ H ],
  316    update_directives(T, Deps0, Deps, File).
  317
  318update_directive(Src, Dir0, Deps0, Deps, Dir) :-
  319    src_file(Src, SrcFile),
  320    directive_file(Dir0, FileSpec),
  321    absolute_file_name(FileSpec, File,
  322                       [ file_type(prolog),
  323                         file_errors(fail),
  324                         relative_to(SrcFile),
  325                         access(read)
  326                       ]),
  327    qlf_pl_file(File, PlFile),
  328    select(DepFile-Heads, Deps0, Deps),
  329    same_dep_file(DepFile, PlFile),
  330    !,
  331    (   Dir0 =.. [Pred,File0,Imports]
  332    ->  xref_public_list(PlFile, SrcFile, [exports(Exports)]),
  333        maplist(head_pi(Exports), Heads, PIs),
  334        subtract_pis(PIs, Imports, New),
  335        append(Imports, New, NewImports),
  336        Dir =.. [Pred,File0,NewImports]
  337    ;   Dir = Dir0
  338    ).
  339
  340directive_file(use_module(File),   File).
  341directive_file(use_module(File,_), File).
  342directive_file(autoload(File),     File).
  343directive_file(autoload(File,_),   File).
  344
  345qlf_pl_file(File, PlFile) :-
  346    file_name_extension(_Base, Ext, File),
  347    user:prolog_file_type(Ext, qlf),
  348    !,
  349    '$qlf_module'(File, Info),
  350    PlFile = Info.get(file).
  351qlf_pl_file(File, File).
  352
  353same_dep_file(File, File) :-
  354    !.
  355same_dep_file(Dep, _File) :-
  356    exists_file(Dep),
  357    !,
  358    fail.
  359same_dep_file(Dep, File) :-
  360    user:prolog_file_type(Ext, prolog),
  361    file_name_extension(Dep, Ext, DepFile),
  362    same_file(DepFile, File),
  363    !.
  364
  365is_pi(Name/Arity), atom(Name), integer(Arity) => true.
  366is_pi(Name//Arity), atom(Name), integer(Arity) => true.
  367is_pi(_) => fail.
  368
  369%!  head_pi(+Exports, +Head, -PI) is det.
  370
  371head_pi(PIs, Head, PI) :-
  372    head_pi(Head, PI),
  373    memberchk(PI, PIs),
  374    !.
  375head_pi(_PIs, Head, PI) :-
  376    pi_head(PI, Head).
  377
  378head_pi(Head, PI) :-
  379    pi_head(PI0, Head),
  380    (   PI = PI0
  381    ;   dcg_pi(PI0, PI)
  382    ).
  383
  384dcg_pi(Module:Name/Arity, PI), integer(Arity), Arity >= 2 =>
  385    DCGArity is Arity - 2,
  386    PI = Module:Name//DCGArity.
  387dcg_pi(Name/Arity, PI), integer(Arity), Arity >= 2 =>
  388    DCGArity is Arity - 2,
  389    PI = Name//DCGArity.
  390dcg_pi(_/Arity, _), integer(Arity) =>
  391    fail.
  392
  393%!  subtract_pis(+Set, +Delete, -Result) is det.
  394
  395subtract_pis([], _, R) =>
  396    R = [].
  397subtract_pis([H|T], D, R) =>
  398    (   member(E, D),
  399        same_pi(H, E)
  400    ->  subtract_pis(T, D, R)
  401    ;   R = [H|R1],
  402        subtract_pis(T, D, R1)
  403    ).
  404
  405same_pi(PI, PI) => true.
  406same_pi(Name/A1, Name//A2) => A1 =:= A2+2.
  407same_pi(Name//A1, Name/A2) => A1 =:= A2-2.
  408same_pi(_,_) => fail.
  409
  410
  411%!  update_style(+OldDirectives, +Options0, -Options)
  412%
  413%   Determine  the  directive  to  use    for   new  dependencies.  This
  414%   establishes a default based on existing dependencies.
  415
  416update_style(_Old, Options, Options) :-
  417    option(directive(_), Options),
  418    !.
  419update_style(Old, Options, [directive(autoload/2)|Options]) :-
  420    memberchk((:- autoload(_,_)), Old),
  421    !.
  422update_style(Old, Options, [directive(autoload/1)|Options]) :-
  423    memberchk((:- autoload(_)), Old),
  424    !.
  425update_style(Old, Options, [directive(use_module/2)|Options]) :-
  426    memberchk((:- use_module(_,_)), Old),
  427    !.
  428update_style(Old, Options, [directive(use_module/1)|Options]) :-
  429    memberchk((:- use_module(_)), Old),
  430    !.
  431update_style(_, Options, Options).
  432
  433
  434%!  directive(+Options, +FileAndHeads, -Directive)
  435%
  436%   Create a directive to import Heads from File.
  437
  438directive(Options, File-Heads, Directive) :-
  439    file_name_extension(File, pl, LibFile),
  440    file_name_on_path(LibFile, Lib0),
  441    segments(Lib0, Lib),
  442    maplist(pi_head, PIs, Heads),
  443    make_directive(Lib, PIs, Directive, Options).
  444
  445segments(Term0, Term) :-
  446    Term0 =.. [Alias,Atom],
  447    path_segments_atom(Segments, Atom),
  448    format(atom(Atom), '~q', [Segments]),
  449    !,
  450    Term =.. [Alias,Segments].
  451segments(FilePL, File) :-
  452    atom(FilePL),
  453    file_name_extension(File, pl, FilePL),
  454    !.
  455segments(Term, Term).
  456
  457:- multifile
  458    prolog:no_autoload_module/1.  459
  460make_directive(Lib, Import, (:- use_module(Lib, Import)), Options) :-
  461    option(directive(use_module/2), Options, use_autoload/2),
  462    !.
  463make_directive(Lib, _Import, (:- use_module(Lib)), Options) :-
  464    option(directive(use_module/1), Options, use_autoload/2),
  465    !.
  466make_directive(Lib, _Import, (:- use_module(Lib)), Options) :-
  467    option(directive(use_autoload/1), Options, use_autoload/2),
  468    prolog:no_autoload_module(Lib),
  469    !.
  470make_directive(Lib, Import, (:- use_module(Lib, Import)), _) :-
  471    prolog:no_autoload_module(Lib),
  472    !.
  473make_directive(Lib, _Import, (:- autoload(Lib)), Options) :-
  474    option(directive(use_autoload/1), Options, use_autoload/2),
  475    !.
  476make_directive(Lib, Import, (:- autoload(Lib, Import)), _).
  477
  478
  479		 /*******************************
  480		 *          NO AUTOLOAD		*
  481		 *******************************/
  482
  483:- dynamic
  484    library_index/3,                % Head x Module x Path
  485    autoload_directories/1,         % List
  486    index_checked_at/1.             % Time
  487:- volatile
  488    library_index/3,
  489    autoload_directories/1,
  490    index_checked_at/1.  491
  492%!  noautoload(+Head, -File) is semidet.
  493%
  494%   True when Head can be loaded from   File.  Where the autoload system
  495%   only considers the autoload directories,   this version searches all
  496%   indexed directories.
  497
  498noautoload(Head, File) :-
  499    functor(Head, Name, Arity),
  500    functor(GenHead, Name, Arity),
  501    context_module(Here),
  502    '$autoload':load_library_index(Here:Name, Arity, Here:noautoload('INDEX')),
  503    library_index(GenHead, _, File),
  504    !.
  505
  506
  507		 /*******************************
  508		 *           REPLACE		*
  509		 *******************************/
  510
  511%!  file_auto_import(+File, +Options)
  512%
  513%   Update the autoload/2 directives for File. This predicate __modifies
  514%   the file in place__. Defined options are:
  515%
  516%     - backup(+Extension)
  517%       Create a backup of File using Extension.
  518
  519file_auto_import(File, Options) :-
  520    absolute_file_name(File, Path,
  521                       [ file_type(prolog),
  522                         access(read)
  523                       ]),
  524    file_autoload_directives(Path, Directives, Options),
  525    (   option(backup(Ext), Options)
  526    ->  file_name_extension(Path, Ext, Old),
  527        copy_file_ext(Path, Old)
  528    ;   true
  529    ),
  530    Edit = _{import:Directives, done:_},
  531    (   has_import(Path)
  532    ->  edit_file(Old, Path, Edit.put(replace,true))
  533    ;   edit_file(Old, Path, Edit.put(new,true))
  534    ).
  535
  536:- if(current_predicate(copy_file/2)).  537copy_file_ext(From, To) :-
  538    copy_file(From, To).
  539:- else.  540copy_file_ext(_From, _To) :-
  541    existence_error(predicate, copy_file/2).
  542:- endif.  543
  544has_import(InFile) :-
  545    setup_call_cleanup(
  546        prolog_open_source(InFile, In),
  547        (   repeat,
  548            prolog_read_source_term(In, Term, _Expanded, []),
  549            (   Term == end_of_file
  550            ->  !
  551            ;    true
  552            )
  553        ),
  554        prolog_close_source(In)),
  555    nonvar(Term),
  556    import_directive(Term),
  557    !.
  558
  559import_directive((:- use_module(_))).
  560import_directive((:- use_module(_, _))).
  561
  562%!  rewrite_term(+In, -Keep, -OutList, +Options) is semidet.
  563
  564rewrite_term(Never,_,_,_) :-
  565    never_rewrite(Never),
  566    !,
  567    fail.
  568rewrite_term(Import,false,[],Options) :-
  569    Options.done == true,
  570    !,
  571    import_directive(Import).
  572rewrite_term(In,false,Directives,Options) :-
  573    import_directive(In),
  574    !,
  575    append(Options.import, [nl], Directives),
  576    Options.done = true.
  577rewrite_term(In,true,Directives,Options) :-
  578    In = (:- module(_,_)),
  579    Options.get(new) == true,
  580    !,
  581    append(Options.import, [nl], Directives),
  582    Options.done = true.
  583
  584never_rewrite((:- use_module(_, []))).
  585
  586edit_file(InFile, OutFile, Options) :-
  587    read_file_to_string(InFile, String, []),
  588    setup_call_cleanup(
  589        prolog_open_source(InFile, In),
  590        setup_call_cleanup(
  591            open(OutFile, write, Out),
  592            rewrite(In, Out, String, Options),
  593            close(Out)),
  594        prolog_close_source(In)).
  595
  596rewrite(In, Out, String, Options) :-
  597    prolog_read_source_term(
  598        In, Term, _Expanded,
  599        [ term_position(StartPos),
  600          subterm_positions(TermPos),
  601          comments(Comments)
  602        ]),
  603    stream_position_data(char_count, StartPos, StartChar),
  604    copy_comments(Comments, StartChar, String, Out),
  605    (   Term == end_of_file
  606    ->  true
  607    ;   (   nonvar(Term),
  608            rewrite_term(Term, Keep, List, Options)
  609        ->  (   Keep == true
  610            ->  copy_term_string(TermPos, String, Out)
  611            ;   true
  612            ),
  613            forall(member(T, List),
  614                   output_term(Out, T)),
  615            (   append(_, [nl], List)
  616            ->  skip_blanks(In)
  617            ;   true
  618            )
  619        ;   copy_term_string(TermPos, String, Out)
  620        ),
  621        rewrite(In, Out, String, Options)
  622    ).
  623
  624output_term(Out, nl) :-
  625    !,
  626    nl(Out).
  627output_term(Out, Term) :-
  628    print_term(Term, [output(Out)]),
  629    format(Out, '.~n', []).
  630
  631copy_comments([Pos-H|T], StartChar, String, Out) :-
  632    stream_position_data(char_count, Pos, Start),
  633    Start < StartChar,
  634    !,
  635    string_length(H, Len),
  636    sub_string(String, Start, Len, _, Comment),
  637    End is Start+Len+1,
  638    layout_after(End, String, Layout),
  639    format(Out, '~s~s', [Comment, Layout]),
  640    copy_comments(T, StartChar, String, Out).
  641copy_comments(_, _, _, _).
  642
  643copy_term_string(TermPos, String, Out) :-
  644    arg(1, TermPos, Start),
  645    arg(2, TermPos, End),
  646    Len is End - Start,
  647    sub_string(String, Start, Len, _, TermString),
  648    End1 is End + 1,
  649    full_stop_after(End1, String, Layout),
  650    format(Out, '~s~s', [TermString, Layout]).
  651
  652layout_after(Index, String, [H|T]) :-
  653    string_code(Index, String, H),
  654    code_type(H, space),
  655    !,
  656    Index2 is Index+1,
  657    layout_after(Index2, String, T).
  658layout_after(_, _, []).
  659
  660full_stop_after(Index, String, [H|T]) :-
  661    string_code(Index, String, H),
  662    Index2 is Index+1,
  663    (   code_type(H, space)
  664    ->  !, full_stop_after(Index2, String, T)
  665    ;   H == 0'.
  666    ->  !, layout_after(Index2, String, T)
  667    ).
  668full_stop_after(_, _, []).
  669
  670skip_blanks(In) :-
  671    peek_code(In, C),
  672    code_type(C, space),
  673    !,
  674    get_code(In, _),
  675    skip_blanks(In).
  676skip_blanks(_)