36
37:- module('$autoload',
38 [ '$find_library'/5,
39 '$in_library'/3,
40 '$define_predicate'/1,
41 '$update_library_index'/0,
42 '$autoload'/1,
43
44 make_library_index/1,
45 make_library_index/2,
46 reload_library_index/0,
47 autoload_path/1,
48
49 autoload/1, 50 autoload/2, 51
52 require/1 53 ]). 54
55:- meta_predicate
56 '$autoload'(:),
57 autoload(:),
58 autoload(:, +),
59 require(:). 60
61:- dynamic
62 library_index/3, 63 autoload_directories/1, 64 index_checked_at/1. 65:- volatile
66 library_index/3,
67 autoload_directories/1,
68 index_checked_at/1. 69
70user:file_search_path(autoload, swi(library)).
71user:file_search_path(autoload, pce(prolog/lib)).
72user:file_search_path(autoload, app_config(lib)).
73
74
82
83'$find_library'(Module, Name, Arity, LoadModule, Library) :-
84 load_library_index(Name, Arity),
85 functor(Head, Name, Arity),
86 ( library_index(Head, Module, Library),
87 LoadModule = Module
88 ; library_index(Head, LoadModule, Library)
89 ),
90 !.
91
96
97'$in_library'(Name, Arity, Path) :-
98 atom(Name), integer(Arity),
99 !,
100 load_library_index(Name, Arity),
101 functor(Head, Name, Arity),
102 library_index(Head, _, Path).
103'$in_library'(Name, Arity, Path) :-
104 load_library_index(Name, Arity),
105 library_index(Head, _, Path),
106 functor(Head, Name, Arity).
107
112
113:- meta_predicate
114 '$define_predicate'(:). 115
116'$define_predicate'(Head) :-
117 '$defined_predicate'(Head),
118 !.
119'$define_predicate'(Term) :-
120 Term = Module:Head,
121 ( compound(Head)
122 -> compound_name_arity(Head, Name, Arity)
123 ; Name = Head, Arity = 0
124 ),
125 '$undefined_procedure'(Module, Name, Arity, retry).
126
127
128 131
132:- thread_local
133 silent/0. 134
141
142'$update_library_index' :-
143 setof(Dir, writable_indexed_directory(Dir), Dirs),
144 !,
145 setup_call_cleanup(
146 asserta(silent, Ref),
147 guarded_make_library_index(Dirs),
148 erase(Ref)),
149 ( flag('$modified_index', true, false)
150 -> reload_library_index
151 ; true
152 ).
153'$update_library_index'.
154
155guarded_make_library_index([]).
156guarded_make_library_index([Dir|Dirs]) :-
157 ( catch(make_library_index(Dir), E,
158 print_message(error, E))
159 -> true
160 ; print_message(warning, goal_failed(make_library_index(Dir)))
161 ),
162 guarded_make_library_index(Dirs).
163
168
169writable_indexed_directory(Dir) :-
170 index_file_name(IndexFile, autoload('INDEX'), [access([read,write])]),
171 file_directory_name(IndexFile, Dir).
172writable_indexed_directory(Dir) :-
173 absolute_file_name(library('MKINDEX'),
174 [ file_type(prolog),
175 access(read),
176 solutions(all),
177 file_errors(fail)
178 ], MkIndexFile),
179 file_directory_name(MkIndexFile, Dir),
180 plfile_in_dir(Dir, 'INDEX', _, IndexFile),
181 access_file(IndexFile, write).
182
183
184 187
191
192reload_library_index :-
193 context_module(M),
194 reload_library_index(M).
195
196reload_library_index(M) :-
197 with_mutex('$autoload', clear_library_index(M)).
198
199clear_library_index(M) :-
200 retractall(M:library_index(_, _, _)),
201 retractall(M:autoload_directories(_)),
202 retractall(M:index_checked_at(_)).
203
204
211
212:- meta_predicate load_library_index(?, ?, :). 213:- public load_library_index/3. 214
215load_library_index(Name, Arity) :-
216 load_library_index(Name, Arity, autoload('INDEX')).
217
218load_library_index(Name, Arity, M:_Spec) :-
219 atom(Name), integer(Arity),
220 functor(Head, Name, Arity),
221 M:library_index(Head, _, _),
222 !.
223load_library_index(_, _, Spec) :-
224 notrace(with_mutex('$autoload', load_library_index_p(Spec))).
225
226load_library_index_p(M:_) :-
227 M:index_checked_at(Time),
228 get_time(Now),
229 Now-Time < 60,
230 !.
231load_library_index_p(M:Spec) :-
232 findall(Index, index_file_name(Index, Spec, [access(read)]), List0),
233 '$list_to_set'(List0, List),
234 retractall(M:index_checked_at(_)),
235 get_time(Now),
236 assert(M:index_checked_at(Now)),
237 ( M:autoload_directories(List)
238 -> true
239 ; retractall(M:library_index(_, _, _)),
240 retractall(M:autoload_directories(_)),
241 read_index(List, M),
242 assert(M:autoload_directories(List))
243 ).
244
252
253index_file_name(IndexFile, FileSpec, Options) :-
254 absolute_file_name(FileSpec,
255 IndexFile,
256 [ file_type(prolog),
257 solutions(all),
258 file_errors(fail)
259 | Options
260 ]).
261
262read_index([], _) :- !.
263read_index([H|T], M) :-
264 !,
265 read_index(H, M),
266 read_index(T, M).
267read_index(Index, M) :-
268 print_message(silent, autoload(read_index(Dir))),
269 file_directory_name(Index, Dir),
270 setup_call_cleanup(
271 '$push_input_context'(autoload_index),
272 setup_call_cleanup(
273 open(Index, read, In),
274 read_index_from_stream(Dir, In, M),
275 close(In)),
276 '$pop_input_context').
277
278read_index_from_stream(Dir, In, M) :-
279 repeat,
280 read(In, Term),
281 assert_index(Term, Dir, M),
282 !.
283
284assert_index(end_of_file, _, _) :- !.
285assert_index(index(Name, Arity, Module, File), Dir, M) :-
286 !,
287 functor(Head, Name, Arity),
288 atomic_list_concat([Dir, '/', File], Path),
289 assertz(M:library_index(Head, Module, Path)),
290 fail.
291assert_index(Term, Dir, _) :-
292 print_message(error, illegal_autoload_index(Dir, Term)),
293 fail.
294
295
296 299
310
311make_library_index(Dir0) :-
312 forall(absolute_file_name(Dir0, Dir,
313 [ expand(true),
314 file_type(directory),
315 file_errors(fail),
316 solutions(all)
317 ]),
318 make_library_index2(Dir)).
319
320make_library_index2(Dir) :-
321 plfile_in_dir(Dir, 'MKINDEX', _MkIndex, AbsMkIndex),
322 access_file(AbsMkIndex, read),
323 !,
324 load_files(user:AbsMkIndex, [silent(true)]).
325make_library_index2(Dir) :-
326 findall(Pattern, source_file_pattern(Pattern), PatternList),
327 make_library_index2(Dir, PatternList).
328
341
342make_library_index(Dir0, Patterns) :-
343 forall(absolute_file_name(Dir0, Dir,
344 [ expand(true),
345 file_type(directory),
346 file_errors(fail),
347 solutions(all)
348 ]),
349 make_library_index2(Dir, Patterns)).
350
351make_library_index2(Dir, Patterns) :-
352 plfile_in_dir(Dir, 'INDEX', _Index, AbsIndex),
353 ensure_slash(Dir, DirS),
354 pattern_files(Patterns, DirS, Files),
355 ( library_index_out_of_date(Dir, AbsIndex, Files)
356 -> do_make_library_index(AbsIndex, DirS, Files),
357 flag('$modified_index', _, true)
358 ; true
359 ).
360
361ensure_slash(Dir, DirS) :-
362 ( sub_atom(Dir, _, _, 0, /)
363 -> DirS = Dir
364 ; atom_concat(Dir, /, DirS)
365 ).
366
367source_file_pattern(Pattern) :-
368 user:prolog_file_type(PlExt, prolog),
369 PlExt \== qlf,
370 atom_concat('*.', PlExt, Pattern).
371
372plfile_in_dir(Dir, Base, PlBase, File) :-
373 file_name_extension(Base, pl, PlBase),
374 atomic_list_concat([Dir, '/', PlBase], File).
375
376pattern_files([], _, []).
377pattern_files([H|T], DirS, Files) :-
378 atom_concat(DirS, H, P0),
379 expand_file_name(P0, Files0),
380 '$append'(Files0, Rest, Files),
381 pattern_files(T, DirS, Rest).
382
383library_index_out_of_date(_Dir, Index, _Files) :-
384 \+ exists_file(Index),
385 !.
386library_index_out_of_date(Dir, Index, Files) :-
387 time_file(Index, IndexTime),
388 ( time_file(Dir, DotTime),
389 DotTime > IndexTime
390 ; '$member'(File, Files),
391 time_file(File, FileTime),
392 FileTime > IndexTime
393 ),
394 !.
395
396
397do_make_library_index(Index, Dir, Files) :-
398 ensure_slash(Dir, DirS),
399 '$stage_file'(Index, StagedIndex),
400 setup_call_catcher_cleanup(
401 open(StagedIndex, write, Out),
402 ( print_message(informational, make(library_index(Dir))),
403 index_header(Out),
404 index_files(Files, DirS, Out)
405 ),
406 Catcher,
407 install_index(Out, Catcher, StagedIndex, Index)).
408
409install_index(Out, Catcher, StagedIndex, Index) :-
410 catch(close(Out), Error, true),
411 ( silent
412 -> OnError = silent
413 ; OnError = error
414 ),
415 ( var(Error)
416 -> TheCatcher = Catcher
417 ; TheCatcher = exception(Error)
418 ),
419 '$install_staged_file'(TheCatcher, StagedIndex, Index, OnError).
420
424
425index_files([], _, _).
426index_files([File|Files], DirS, Fd) :-
427 catch(setup_call_cleanup(
428 open(File, read, In),
429 read(In, Term),
430 close(In)),
431 E, print_message(warning, E)),
432 ( Term = (:- module(Module, Public)),
433 is_list(Public)
434 -> atom_concat(DirS, Local, File),
435 file_name_extension(Base, _, Local),
436 forall(public_predicate(Public, Name/Arity),
437 format(Fd, 'index((~k), ~k, ~k, ~k).~n',
438 [Name, Arity, Module, Base]))
439 ; true
440 ),
441 index_files(Files, DirS, Fd).
442
443public_predicate(Public, PI) :-
444 '$member'(PI0, Public),
445 canonical_pi(PI0, PI).
446
447canonical_pi(Var, _) :-
448 var(Var), !, fail.
449canonical_pi(Name/Arity, Name/Arity).
450canonical_pi(Name//A0, Name/Arity) :-
451 Arity is A0 + 2.
452
453
(Fd):-
455 format(Fd, '/* Creator: make/0~n~n', []),
456 format(Fd, ' Purpose: Provide index for autoload~n', []),
457 format(Fd, '*/~n~n', []).
458
459
460 463
478
479autoload_path(Alias) :-
480 ( user:file_search_path(autoload, Alias)
481 -> true
482 ; assertz(user:file_search_path(autoload, Alias)),
483 reload_library_index
484 ).
485
486system:term_expansion((:- autoload_path(Alias)),
487 [ user:file_search_path(autoload, Alias),
488 (:- reload_library_index)
489 ]).
490
491
492 495
503
504'$autoload'(PI) :-
505 source_location(File, _Line),
506 !,
507 setup_call_cleanup(
508 '$start_aux'(File, Context),
509 '$autoload2'(PI),
510 '$end_aux'(File, Context)).
511'$autoload'(PI) :-
512 '$autoload2'(PI).
513
514'$autoload2'(PI) :-
515 setup_call_cleanup(
516 leave_sandbox(Old),
517 '$autoload3'(PI),
518 restore_sandbox(Old)).
519
520leave_sandbox(Sandboxed) :-
521 current_prolog_flag(sandboxed_load, Sandboxed),
522 set_prolog_flag(sandboxed_load, false).
523restore_sandbox(Sandboxed) :-
524 set_prolog_flag(sandboxed_load, Sandboxed).
525
526'$autoload3'(PI) :-
527 autoload_from(PI, LoadModule, FullFile),
528 do_autoload(FullFile, PI, LoadModule).
529
534
535autoload_from(Module:PI, LoadModule, FullFile) :-
536 autoload_in(Module, explicit),
537 current_autoload(Module:File, Ctx, import(Imports)),
538 memberchk(PI, Imports),
539 library_info(File, Ctx, FullFile, LoadModule, Exports),
540 ( pi_in_exports(PI, Exports)
541 -> !
542 ; autoload_error(Ctx, not_exported(PI, File, FullFile, Exports)),
543 fail
544 ).
545autoload_from(Module:Name/Arity, LoadModule, FullFile) :-
546 autoload_in(Module, explicit),
547 PI = Name/Arity,
548 current_autoload(Module:File, Ctx, all),
549 library_info(File, Ctx, FullFile, LoadModule, Exports),
550 pi_in_exports(PI, Exports).
551autoload_from(Module:Name/Arity, LoadModule, Library) :-
552 autoload_in(Module, general),
553 '$find_library'(Module, Name, Arity, LoadModule, Library).
554
555:- public autoload_in/2. 556
557autoload_in(Module, How) :-
558 current_prolog_flag(autoload, AutoLoad),
559 autoload_in(AutoLoad, How, Module),
560 !.
561
563
564autoload_in(true, _, _).
565autoload_in(explicit, explicit, _).
566autoload_in(explicit_or_user, explicit, _).
567autoload_in(user, explicit, user).
568autoload_in(explicit_or_user, explicit, _).
569autoload_in(user, _, user).
570autoload_in(explicit_or_user, general, user).
571
572
585
586do_autoload(Library, Module:Name/Arity, LoadModule) :-
587 functor(Head, Name, Arity),
588 '$update_autoload_level'([autoload(true)], Old),
589 verbose_autoload(Module:Name/Arity, Library),
590 '$compilation_mode'(OldComp, database),
591 ( Module == LoadModule
592 -> ensure_loaded(Module:Library)
593 ; ( '$c_current_predicate'(_, LoadModule:Head),
594 '$get_predicate_attribute'(LoadModule:Head, defined, 1),
595 \+ '$loading'(Library)
596 -> Module:import(LoadModule:Name/Arity)
597 ; use_module(Module:Library, [Name/Arity])
598 )
599 ),
600 '$set_compilation_mode'(OldComp),
601 '$set_autoload_level'(Old),
602 '$c_current_predicate'(_, Module:Head).
603
604verbose_autoload(PI, Library) :-
605 current_prolog_flag(verbose_autoload, true),
606 !,
607 set_prolog_flag(verbose_autoload, false),
608 print_message(informational, autoload(PI, Library)),
609 set_prolog_flag(verbose_autoload, true).
610verbose_autoload(PI, Library) :-
611 print_message(silent, autoload(PI, Library)).
612
613
619
620:- public 621 autoloadable/2. 622
623autoloadable(M:Head, FullFile) :-
624 atom(M),
625 current_module(M),
626 autoload_in(M, explicit),
627 ( callable(Head)
628 -> goal_name_arity(Head, Name, Arity),
629 autoload_from(M:Name/Arity, _, FullFile)
630 ; findall((M:H)-F, autoloadable_2(M:H, F), Pairs),
631 ( '$member'(M:Head-FullFile, Pairs)
632 ; current_autoload(M:File, Ctx, all),
633 library_info(File, Ctx, FullFile, _, Exports),
634 '$member'(PI, Exports),
635 '$pi_head'(PI, Head),
636 \+ memberchk(M:Head-_, Pairs)
637 )
638 ).
639autoloadable(M:Head, FullFile) :-
640 ( var(M)
641 -> autoload_in(any, general)
642 ; autoload_in(M, general)
643 ),
644 ( callable(Head)
645 -> goal_name_arity(Head, Name, Arity),
646 ( '$find_library'(_, Name, Arity, _, FullFile)
647 -> true
648 )
649 ; '$in_library'(Name, Arity, autoload),
650 functor(Head, Name, Arity)
651 ).
652
653
654autoloadable_2(M:Head, FullFile) :-
655 current_autoload(M:File, Ctx, import(Imports)),
656 library_info(File, Ctx, FullFile, _LoadModule, _Exports),
657 '$member'(PI, Imports),
658 '$pi_head'(PI, Head).
659
660goal_name_arity(Head, Name, Arity) :-
661 compound(Head),
662 !,
663 compound_name_arity(Head, Name, Arity).
664goal_name_arity(Head, Head, 0).
665
669
670library_info(Spec, _, FullFile, Module, Exports) :-
671 '$resolved_source_path'(Spec, FullFile, []),
672 !,
673 ( \+ '$loading_file'(FullFile, _Queue, _LoadThread)
674 -> '$current_module'(Module, FullFile),
675 '$module_property'(Module, exports(Exports))
676 ; library_info_from_file(FullFile, Module, Exports)
677 ).
678library_info(Spec, Context, FullFile, Module, Exports) :-
679 ( Context = (Path:_Line)
680 -> Extra = [relative_to(Path)]
681 ; Extra = []
682 ),
683 ( absolute_file_name(Spec, FullFile,
684 [ file_type(prolog),
685 access(read),
686 file_errors(fail)
687 | Extra
688 ])
689 -> '$register_resolved_source_path'(Spec, FullFile),
690 library_info_from_file(FullFile, Module, Exports)
691 ; autoload_error(Context, no_file(Spec)),
692 fail
693 ).
694
695
696library_info_from_file(FullFile, Module, Exports) :-
697 setup_call_cleanup(
698 '$open_source'(FullFile, In, State, [], []),
699 '$term_in_file'(In, _Read, _RLayout, Term, _TLayout, _Stream,
700 [FullFile], []),
701 '$close_source'(State, true)),
702 ( Term = (:- module(Module, Exports))
703 -> !
704 ; nonvar(Term),
705 skip_header(Term)
706 -> fail
707 ; '$domain_error'(module_header, Term)
708 ).
709
(begin_of_file).
711
712
713:- dynamic printed/3. 714:- volatile printed/3. 715
716autoload_error(Context, Error) :-
717 suppress(Context, Error),
718 !.
719autoload_error(Context, Error) :-
720 get_time(Now),
721 assertz(printed(Context, Error, Now)),
722 print_message(warning, error(autoload(Error), autoload(Context))).
723
724suppress(Context, Error) :-
725 printed(Context, Error, Printed),
726 get_time(Now),
727 ( Now - Printed < 1
728 -> true
729 ; retractall(printed(Context, Error, _)),
730 fail
731 ).
732
733
734 737
738:- public
739 set_autoload/1. 740
747
748set_autoload(FlagValue) :-
749 current_prolog_flag(autoload, FlagValue),
750 !.
751set_autoload(FlagValue) :-
752 \+ autoload_in(FlagValue, explicit, any),
753 !,
754 setup_call_cleanup(
755 nb_setval('$autoload_disabling', true),
756 materialize_autoload(Count),
757 nb_delete('$autoload_disabling')),
758 print_message(informational, autoload(disabled(Count))).
759set_autoload(_).
760
761materialize_autoload(Count) :-
762 State = state(0),
763 forall(current_predicate(M:'$autoload'/3),
764 materialize_autoload(M, State)),
765 arg(1, State, Count).
766
767materialize_autoload(M, State) :-
768 ( current_autoload(M:File, Context, Import),
769 library_info(File, Context, FullFile, _LoadModule, _Exports),
770 arg(1, State, N0),
771 N is N0+1,
772 nb_setarg(1, State, N),
773 ( Import == all
774 -> verbose_autoload(M:all, FullFile),
775 use_module(M:FullFile)
776 ; Import = import(Preds)
777 -> verbose_autoload(M:Preds, FullFile),
778 use_module(M:FullFile, Preds)
779 ),
780 fail
781 ; true
782 ),
783 abolish(M:'$autoload'/3).
784
785
786 789
790autoload(M:File) :-
791 ( \+ autoload_in(M, explicit)
792 ; nb_current('$autoload_disabling', true)
793 ),
794 !,
795 use_module(M:File).
796autoload(M:File) :-
797 '$must_be'(filespec, File),
798 source_context(Context),
799 ( current_autoload(M:File, _, import(all))
800 -> true
801 ; assert_autoload(M:'$autoload'(File, Context, all))
802 ).
803
804autoload(M:File, Imports) :-
805 ( \+ autoload_in(M, explicit)
806 ; nb_current('$autoload_disabling', true)
807 ),
808 !,
809 use_module(M:File, Imports).
810autoload(M:File, Imports0) :-
811 '$must_be'(filespec, File),
812 valid_imports(Imports0, Imports),
813 source_context(Context),
814 register_autoloads(Imports, M, File, Context),
815 ( current_autoload(M:File, _, import(Imports))
816 -> true
817 ; assert_autoload(M:'$autoload'(File, Context, import(Imports)))
818 ).
819
820source_context(Path:Line) :-
821 source_location(Path, Line),
822 !.
823source_context(-).
824
825assert_autoload(Clause) :-
826 '$initialization_context'(Source, Ctx),
827 '$store_admin_clause2'(Clause, _Layout, Source, Ctx).
828
829valid_imports(Imports0, Imports) :-
830 '$must_be'(list, Imports0),
831 valid_import_list(Imports0, Imports).
832
833valid_import_list([], []).
834valid_import_list([H0|T0], [H|T]) :-
835 '$pi_head'(H0, Head),
836 '$pi_head'(H, Head),
837 valid_import_list(T0, T).
838
843
844register_autoloads([], _, _, _).
845register_autoloads([PI|T], Module, File, Context) :-
846 PI = Name/Arity,
847 functor(Head, Name, Arity),
848 ( '$get_predicate_attribute'(Module:Head, autoload, 1)
849 -> ( current_autoload(Module:_File0, _Ctx0, import(Imports)),
850 memberchk(PI, Imports)
851 -> '$permission_error'(redefine, imported_procedure, PI),
852 fail
853 ; Done = true
854 )
855 ; '$c_current_predicate'(_, Module:Head), 856 '$get_predicate_attribute'(Module:Head, imported, From)
857 -> ( ( '$resolved_source_path'(File, FullFile)
858 -> true
859 ; '$resolve_source_path'(File, FullFile, [])
860 ),
861 module_property(From, file(FullFile))
862 -> Done = true
863 ; print_message(warning,
864 autoload(already_defined(Module:PI, From))),
865 Done = true
866 )
867 ; true
868 ),
869 ( Done == true
870 -> true
871 ; '$set_predicate_attribute'(Module:Head, autoload, 1)
872 ),
873 register_autoloads(T, Module, File, Context).
874
875pi_in_exports(PI, Exports) :-
876 '$member'(E, Exports),
877 canonical_pi(E, PI),
878 !.
879
880current_autoload(M:File, Context, Term) :-
881 '$get_predicate_attribute'(M:'$autoload'(_,_,_), defined, 1),
882 M:'$autoload'(File, Context, Term).
883
884 887
892
893require(M:Spec) :-
894 ( is_list(Spec)
895 -> List = Spec
896 ; phrase(comma_list(Spec), List)
897 ), !,
898 require(List, M, FromLib),
899 keysort(FromLib, Sorted),
900 by_file(Sorted, Autoload),
901 forall('$member'(File-Import, Autoload),
902 autoload(M:File, Import)).
903require(_:Spec) :-
904 '$type_error'(list, Spec).
905
906require([],_, []).
907require([H|T], M, Needed) :-
908 '$pi_head'(H, Head),
909 ( '$get_predicate_attribute'(system:Head, defined, 1)
910 -> require(T, M, Needed)
911 ; '$pi_head'(Module:Name/Arity, M:Head),
912 ( '$find_library'(Module, Name, Arity, _LoadModule, Library)
913 -> Needed = [Library-H|More],
914 require(T, M, More)
915 ; print_message(error, error(existence_error(procedure, Name/Arity), _)),
916 require(T, M, Needed)
917 )
918 ).
919
920by_file([], []).
921by_file([File-PI|T0], [Spec-[PI|PIs]|T]) :-
922 on_path(File, Spec),
923 same_file(T0, File, PIs, T1),
924 by_file(T1, T).
925
926on_path(Library, library(Base)) :-
927 file_base_name(Library, Base),
928 findall(Path, plain_source(library(Base), Path), [Library]),
929 !.
930on_path(Library, Library).
931
932plain_source(Spec, Path) :-
933 absolute_file_name(Spec, PathExt,
934 [ file_type(prolog),
935 access(read),
936 file_errors(fail),
937 solutions(all)
938 ]),
939 file_name_extension(Path, _, PathExt).
940
941same_file([File-PI|T0], File, [PI|PIs], T) :-
942 !,
943 same_file(T0, File, PIs, T).
944same_file(List, _, [], List).
945
946comma_list(Var) -->
947 { var(Var),
948 !,
949 '$instantiation_error'(Var)
950 }.
951comma_list((A,B)) -->
952 !,
953 comma_list(A),
954 comma_list(B).
955comma_list(A) -->
956 [A]