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]).  288src_file(@(Ref), File) =>
  289    get(?(@(Ref), file), absolute_path, File).
  290:- endif.  291src_file(File0, File) =>
  292    File = File0.
  293
  294%!  directives(+File, +FileAndHeads, -Directives, +Options) is det.
  295%
  296%   Assemble the final set of directives. Uses the option update(Old).
  297
  298directives(File, FileAndHeads, Directives, Options) :-
  299    option(update(Old), Options, []),
  300    phrase(update_directives(Old, FileAndHeads, RestDeps, File),
  301           Directives, Rest),
  302    update_style(Old, Options, Options1),
  303    maplist(directive(Options1), RestDeps, Rest0),
  304    sort(Rest0, Rest).
  305
  306update_directives([], Deps, Deps, _) -->
  307    [].
  308update_directives([:-(H)|T], Deps0, Deps, File) -->
  309    { update_directive(File, H, Deps0, Deps1, Directive) },
  310    !,
  311    [ :-(Directive) ],
  312    update_directives(T, Deps1, Deps, File).
  313update_directives([H|T], Deps0, Deps, File) -->
  314    [ H ],
  315    update_directives(T, Deps0, Deps, File).
  316
  317update_directive(Src, Dir0, Deps0, Deps, Dir) :-
  318    src_file(Src, SrcFile),
  319    directive_file(Dir0, FileSpec),
  320    absolute_file_name(FileSpec, File,
  321                       [ file_type(prolog),
  322                         file_errors(fail),
  323                         relative_to(SrcFile),
  324                         access(read)
  325                       ]),
  326    qlf_pl_file(File, PlFile),
  327    select(DepFile-Heads, Deps0, Deps),
  328    same_dep_file(DepFile, PlFile),
  329    !,
  330    (   Dir0 =.. [Pred,File0,Imports]
  331    ->  xref_public_list(PlFile, SrcFile, [exports(Exports)]),
  332        maplist(head_pi(Exports), Heads, PIs),
  333        subtract_pis(PIs, Imports, New),
  334        append(Imports, New, NewImports),
  335        Dir =.. [Pred,File0,NewImports]
  336    ;   Dir = Dir0
  337    ).
  338
  339directive_file(use_module(File),   File).
  340directive_file(use_module(File,_), File).
  341directive_file(autoload(File),     File).
  342directive_file(autoload(File,_),   File).
  343
  344qlf_pl_file(File, PlFile) :-
  345    file_name_extension(_Base, Ext, File),
  346    user:prolog_file_type(Ext, qlf),
  347    !,
  348    '$qlf_module'(File, Info),
  349    PlFile = Info.get(file).
  350qlf_pl_file(File, File).
  351
  352same_dep_file(File, File) :-
  353    !.
  354same_dep_file(Dep, _File) :-
  355    exists_file(Dep),
  356    !,
  357    fail.
  358same_dep_file(Dep, File) :-
  359    user:prolog_file_type(Ext, prolog),
  360    file_name_extension(Dep, Ext, DepFile),
  361    same_file(DepFile, File),
  362    !.
  363
  364is_pi(Name/Arity), atom(Name), integer(Arity) => true.
  365is_pi(Name//Arity), atom(Name), integer(Arity) => true.
  366is_pi(_) => fail.
  367
  368%!  head_pi(+Exports, +Head, -PI) is det.
  369
  370head_pi(PIs, Head, PI) :-
  371    head_pi(Head, PI),
  372    memberchk(PI, PIs),
  373    !.
  374head_pi(_PIs, Head, PI) :-
  375    pi_head(PI, Head).
  376
  377head_pi(Head, PI) :-
  378    pi_head(PI0, Head),
  379    (   PI = PI0
  380    ;   dcg_pi(PI0, PI)
  381    ).
  382
  383dcg_pi(Module:Name/Arity, PI), integer(Arity), Arity >= 2 =>
  384    DCGArity is Arity - 2,
  385    PI = Module:Name//DCGArity.
  386dcg_pi(Name/Arity, PI), integer(Arity), Arity >= 2 =>
  387    DCGArity is Arity - 2,
  388    PI = Name//DCGArity.
  389dcg_pi(_/Arity, _), integer(Arity) =>
  390    fail.
  391
  392%!  subtract_pis(+Set, +Delete, -Result) is det.
  393
  394subtract_pis([], _, R) =>
  395    R = [].
  396subtract_pis([H|T], D, R) =>
  397    (   member(E, D),
  398        same_pi(H, E)
  399    ->  subtract_pis(T, D, R)
  400    ;   R = [H|R1],
  401        subtract_pis(T, D, R1)
  402    ).
  403
  404same_pi(PI, PI) => true.
  405same_pi(Name/A1, Name//A2) => A1 =:= A2+2.
  406same_pi(Name//A1, Name/A2) => A1 =:= A2-2.
  407same_pi(_,_) => fail.
  408
  409
  410%!  update_style(+OldDirectives, +Options0, -Options)
  411%
  412%   Determine  the  directive  to  use    for   new  dependencies.  This
  413%   establishes a default based on existing dependencies.
  414
  415update_style(_Old, Options, Options) :-
  416    option(directive(_), Options),
  417    !.
  418update_style(Old, Options, [directive(autoload/2)|Options]) :-
  419    memberchk((:- autoload(_,_)), Old),
  420    !.
  421update_style(Old, Options, [directive(autoload/1)|Options]) :-
  422    memberchk((:- autoload(_)), Old),
  423    !.
  424update_style(Old, Options, [directive(use_module/2)|Options]) :-
  425    memberchk((:- use_module(_,_)), Old),
  426    !.
  427update_style(Old, Options, [directive(use_module/1)|Options]) :-
  428    memberchk((:- use_module(_)), Old),
  429    !.
  430update_style(_, Options, Options).
  431
  432
  433%!  directive(+Options, +FileAndHeads, -Directive)
  434%
  435%   Create a directive to import Heads from File.
  436
  437directive(Options, File-Heads, Directive) :-
  438    file_name_extension(File, pl, LibFile),
  439    file_name_on_path(LibFile, Lib0),
  440    segments(Lib0, Lib),
  441    maplist(pi_head, PIs, Heads),
  442    make_directive(Lib, PIs, Directive, Options).
  443
  444segments(Term0, Term) :-
  445    Term0 =.. [Alias,Atom],
  446    path_segments_atom(Segments, Atom),
  447    format(atom(Atom), '~q', [Segments]),
  448    !,
  449    Term =.. [Alias,Segments].
  450segments(FilePL, File) :-
  451    atom(FilePL),
  452    file_name_extension(File, pl, FilePL),
  453    !.
  454segments(Term, Term).
  455
  456:- multifile
  457    prolog:no_autoload_module/1.  458
  459make_directive(Lib, Import, (:- use_module(Lib, Import)), Options) :-
  460    option(directive(use_module/2), Options, use_autoload/2),
  461    !.
  462make_directive(Lib, _Import, (:- use_module(Lib)), Options) :-
  463    option(directive(use_module/1), Options, use_autoload/2),
  464    !.
  465make_directive(Lib, _Import, (:- use_module(Lib)), Options) :-
  466    option(directive(use_autoload/1), Options, use_autoload/2),
  467    prolog:no_autoload_module(Lib),
  468    !.
  469make_directive(Lib, Import, (:- use_module(Lib, Import)), _) :-
  470    prolog:no_autoload_module(Lib),
  471    !.
  472make_directive(Lib, _Import, (:- autoload(Lib)), Options) :-
  473    option(directive(use_autoload/1), Options, use_autoload/2),
  474    !.
  475make_directive(Lib, Import, (:- autoload(Lib, Import)), _).
  476
  477
  478		 /*******************************
  479		 *          NO AUTOLOAD		*
  480		 *******************************/
  481
  482:- dynamic
  483    library_index/3,                % Head x Module x Path
  484    autoload_directories/1,         % List
  485    index_checked_at/1.             % Time
  486:- volatile
  487    library_index/3,
  488    autoload_directories/1,
  489    index_checked_at/1.  490
  491%!  noautoload(+Head, -File) is semidet.
  492%
  493%   True when Head can be loaded from   File.  Where the autoload system
  494%   only considers the autoload directories,   this version searches all
  495%   indexed directories.
  496
  497noautoload(Head, File) :-
  498    functor(Head, Name, Arity),
  499    functor(GenHead, Name, Arity),
  500    context_module(Here),
  501    '$autoload':load_library_index(Here:Name, Arity, Here:noautoload('INDEX')),
  502    library_index(GenHead, _, File),
  503    !.
  504
  505
  506		 /*******************************
  507		 *           REPLACE		*
  508		 *******************************/
  509
  510%!  file_auto_import(+File, +Options)
  511%
  512%   Update the autoload/2 directives for File. This predicate __modifies
  513%   the file in place__. Defined options are:
  514%
  515%     - backup(+Extension)
  516%       Create a backup of File using Extension.
  517
  518file_auto_import(File, Options) :-
  519    absolute_file_name(File, Path,
  520                       [ file_type(prolog),
  521                         access(read)
  522                       ]),
  523    file_autoload_directives(Path, Directives, Options),
  524    (   option(backup(Ext), Options)
  525    ->  file_name_extension(Path, Ext, Old),
  526        copy_file_ext(Path, Old)
  527    ;   true
  528    ),
  529    Edit = _{import:Directives, done:_},
  530    (   has_import(Path)
  531    ->  edit_file(Old, Path, Edit.put(replace,true))
  532    ;   edit_file(Old, Path, Edit.put(new,true))
  533    ).
  534
  535:- if(current_predicate(copy_file/2)).  536copy_file_ext(From, To) :-
  537    copy_file(From, To).
  538:- else.  539copy_file_ext(_From, _To) :-
  540    existence_error(predicate, copy_file/2).
  541:- endif.  542
  543has_import(InFile) :-
  544    setup_call_cleanup(
  545        prolog_open_source(InFile, In),
  546        (   repeat,
  547            prolog_read_source_term(In, Term, _Expanded, []),
  548            (   Term == end_of_file
  549            ->  !
  550            ;    true
  551            )
  552        ),
  553        prolog_close_source(In)),
  554    nonvar(Term),
  555    import_directive(Term),
  556    !.
  557
  558import_directive((:- use_module(_))).
  559import_directive((:- use_module(_, _))).
  560
  561%!  rewrite_term(+In, -Keep, -OutList, +Options) is semidet.
  562
  563rewrite_term(Never,_,_,_) :-
  564    never_rewrite(Never),
  565    !,
  566    fail.
  567rewrite_term(Import,false,[],Options) :-
  568    Options.done == true,
  569    !,
  570    import_directive(Import).
  571rewrite_term(In,false,Directives,Options) :-
  572    import_directive(In),
  573    !,
  574    append(Options.import, [nl], Directives),
  575    Options.done = true.
  576rewrite_term(In,true,Directives,Options) :-
  577    In = (:- module(_,_)),
  578    Options.get(new) == true,
  579    !,
  580    append(Options.import, [nl], Directives),
  581    Options.done = true.
  582
  583never_rewrite((:- use_module(_, []))).
  584
  585edit_file(InFile, OutFile, Options) :-
  586    read_file_to_string(InFile, String, []),
  587    setup_call_cleanup(
  588        prolog_open_source(InFile, In),
  589        setup_call_cleanup(
  590            open(OutFile, write, Out),
  591            rewrite(In, Out, String, Options),
  592            close(Out)),
  593        prolog_close_source(In)).
  594
  595rewrite(In, Out, String, Options) :-
  596    prolog_read_source_term(
  597        In, Term, _Expanded,
  598        [ term_position(StartPos),
  599          subterm_positions(TermPos),
  600          comments(Comments)
  601        ]),
  602    stream_position_data(char_count, StartPos, StartChar),
  603    copy_comments(Comments, StartChar, String, Out),
  604    (   Term == end_of_file
  605    ->  true
  606    ;   (   nonvar(Term),
  607            rewrite_term(Term, Keep, List, Options)
  608        ->  (   Keep == true
  609            ->  copy_term_string(TermPos, String, Out)
  610            ;   true
  611            ),
  612            forall(member(T, List),
  613                   output_term(Out, T)),
  614            (   append(_, [nl], List)
  615            ->  skip_blanks(In)
  616            ;   true
  617            )
  618        ;   copy_term_string(TermPos, String, Out)
  619        ),
  620        rewrite(In, Out, String, Options)
  621    ).
  622
  623output_term(Out, nl) :-
  624    !,
  625    nl(Out).
  626output_term(Out, Term) :-
  627    print_term(Term, [output(Out)]),
  628    format(Out, '.~n', []).
  629
  630copy_comments([Pos-H|T], StartChar, String, Out) :-
  631    stream_position_data(char_count, Pos, Start),
  632    Start < StartChar,
  633    !,
  634    string_length(H, Len),
  635    sub_string(String, Start, Len, _, Comment),
  636    End is Start+Len+1,
  637    layout_after(End, String, Layout),
  638    format(Out, '~s~s', [Comment, Layout]),
  639    copy_comments(T, StartChar, String, Out).
  640copy_comments(_, _, _, _).
  641
  642copy_term_string(TermPos, String, Out) :-
  643    arg(1, TermPos, Start),
  644    arg(2, TermPos, End),
  645    Len is End - Start,
  646    sub_string(String, Start, Len, _, TermString),
  647    End1 is End + 1,
  648    full_stop_after(End1, String, Layout),
  649    format(Out, '~s~s', [TermString, Layout]).
  650
  651layout_after(Index, String, [H|T]) :-
  652    string_code(Index, String, H),
  653    code_type(H, space),
  654    !,
  655    Index2 is Index+1,
  656    layout_after(Index2, String, T).
  657layout_after(_, _, []).
  658
  659full_stop_after(Index, String, [H|T]) :-
  660    string_code(Index, String, H),
  661    Index2 is Index+1,
  662    (   code_type(H, space)
  663    ->  !, full_stop_after(Index2, String, T)
  664    ;   H == 0'.
  665    ->  !, layout_after(Index2, String, T)
  666    ).
  667full_stop_after(_, _, []).
  668
  669skip_blanks(In) :-
  670    peek_code(In, C),
  671    code_type(C, space),
  672    !,
  673    get_code(In, _),
  674    skip_blanks(In).
  675skip_blanks(_)