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)  2014-2020, 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(prolog_pretty_print,
38          [ print_term/2        % +Term, +Options
39          ]).
40:- autoload(library(option),
41            [merge_options/3, select_option/3, select_option/4,
42             option/2, option/3]).
43
44/** <module> Pretty Print Prolog terms
45
46This module is a first  start  of   what  should  become a full-featured
47pretty printer for Prolog  terms  with   many  options  and  parameters.
48Eventually,  it  should  replace  portray_clause/1   and  various  other
49special-purpose predicates.
50
51@tbd This is just a quicky. We  need proper handling of portray/1, avoid
52printing very long terms  multiple   times,  spacing (around operators),
53etc.
54
55@tbd Use a record for the option-processing.
56
57@tbd The current approach is far too simple, often resulting in illegal
58     terms.
59*/
60
61:- predicate_options(print_term/2, 2,
62                     [ output(stream),
63                       right_margin(integer),
64                       left_margin(integer),
65                       tab_width(integer),
66                       indent_arguments(integer),
67                       operators(boolean),
68                       write_options(list)
69                     ]).
70
71%!  print_term(+Term, +Options) is det.
72%
73%   Pretty print a Prolog term. The following options are processed:
74%
75%     * output(+Stream)
76%     Define the output stream.  Default is =user_output=
77%     * right_margin(+Integer)
78%     Width of a line.  Default is 72 characters.
79%     * left_margin(+Integer)
80%     Left margin for continuation lines.  Default is 0.
81%     * tab_width(+Integer)
82%     Distance between tab-stops.  Default is 8 characters.
83%     * indent_arguments(+Spec)
84%     Defines how arguments of compound terms are placed.  Defined
85%     values are:
86%       $ =false= :
87%       Simply place them left to right (no line-breaks)
88%       $ =true= :
89%       Place them vertically, aligned with the open bracket (not
90%       implemented)
91%       $ =auto= (default) :
92%       As horizontal if line-width is not exceeded, vertical
93%       otherwise.
94%       $ An integer :
95%       Place them vertically aligned, <N> spaces to the right of
96%       the beginning of the head.
97%     * operators(+Boolean)
98%     This is the inverse of the write_term/3 option =ignore_ops=.
99%     Default is to respect them.
100%     * write_options(+List)
101%     List of options passed to write_term/3 for terms that are
102%     not further processed.  Default:
103%       ==
104%           [ numbervars(true),
105%             quoted(true),
106%             portray(true)
107%           ]
108%       ==
109
110print_term(Term, Options) :-
111    \+ \+ print_term_2(Term, Options).
112
113print_term_2(Term, Options0) :-
114    prepare_term(Term, Template, Cycles, Constraints),
115    defaults(Defs0),
116    select_option(write_options(WrtDefs), Defs0, Defs),
117    select_option(write_options(WrtUser), Options0, Options1, []),
118    merge_options(WrtUser, WrtDefs, WrtOpts),
119    merge_options(Options1, Defs, Options2),
120    option(max_depth(MaxDepth), WrtOpts, infinite),
121    Options = [write_options(WrtOpts)|Options2],
122
123    dict_create(Context, #, [max_depth(MaxDepth)|Options]),
124    pp(Template, Context, Options),
125    print_extra(Cycles, Context, 'where', Options),
126    print_extra(Constraints, Context, 'with constraints', Options).
127
128print_extra([], _, _, _) :- !.
129print_extra(List, Context, Comment, Options) :-
130    option(output(Out), Options),
131    format(Out, ', % ~w', [Comment]),
132    modify_context(Context, [indent=4], Context1),
133    print_extra_2(List, Context1, Options).
134
135print_extra_2([H|T], Context, Options) :-
136    option(output(Out), Options),
137    context(Context, indent, Indent),
138    indent(Out, Indent, Options),
139    pp(H, Context, Options),
140    (   T == []
141    ->  true
142    ;   format(Out, ',', []),
143        print_extra_2(T, Context, Options)
144    ).
145
146
147%!  prepare_term(+Term, -Template, -Cycles, -Constraints)
148%
149%   Prepare a term, possibly  holding   cycles  and  constraints for
150%   printing.
151
152prepare_term(Term, Template, Cycles, Constraints) :-
153    term_attvars(Term, []),
154    !,
155    Constraints = [],
156    '$factorize_term'(Term, Template, Factors),
157    bind_non_cycles(Factors, 1, Cycles),
158    numbervars(Template+Cycles+Constraints, 0, _,
159               [singletons(true)]).
160prepare_term(Term, Template, Cycles, Constraints) :-
161    copy_term(Term, Copy, Constraints),
162    !,
163    '$factorize_term'(Copy, Template, Factors),
164    bind_non_cycles(Factors, 1, Cycles),
165    numbervars(Template+Cycles+Constraints, 0, _,
166               [singletons(true)]).
167
168
169bind_non_cycles([], _, []).
170bind_non_cycles([V=Term|T], I, L) :-
171    unify_with_occurs_check(V, Term),
172    !,
173    bind_non_cycles(T, I, L).
174bind_non_cycles([H|T0], I, [H|T]) :-
175    H = ('$VAR'(Name)=_),
176    atom_concat('_S', I, Name),
177    I2 is I + 1,
178    bind_non_cycles(T0, I2, T).
179
180
181defaults([ output(user_output),
182           left_margin(0),
183           right_margin(72),
184           depth(0),
185           indent(0),
186           indent_arguments(auto),
187           operators(true),
188           write_options([ quoted(true),
189                           numbervars(true),
190                           portray(true),
191                           attributes(portray)
192                         ]),
193           priority(1200)
194         ]).
195
196
197                 /*******************************
198                 *             CONTEXT          *
199                 *******************************/
200
201context(Ctx, Name, Value) :-
202    get_dict(Name, Ctx, Value).
203
204modify_context(Ctx0, Mapping, Ctx) :-
205    Ctx = Ctx0.put(Mapping).
206
207dec_depth(Ctx, Ctx) :-
208    context(Ctx, max_depth, infinite),
209    !.
210dec_depth(Ctx0, Ctx) :-
211    ND is Ctx0.max_depth - 1,
212    Ctx = Ctx0.put(max_depth, ND).
213
214
215                 /*******************************
216                 *              PP              *
217                 *******************************/
218
219pp(Primitive, Ctx, Options) :-
220    (   atomic(Primitive)
221    ;   var(Primitive)
222    ;   Primitive = '$VAR'(Var),
223        (   integer(Var)
224        ;   atom(Var)
225        )
226    ),
227    !,
228    pprint(Primitive, Ctx, Options).
229pp(Portray, _Ctx, Options) :-
230    option(write_options(WriteOptions), Options),
231    option(portray(true), WriteOptions),
232    option(output(Out), Options),
233    with_output_to(Out, user:portray(Portray)),
234    !.
235pp(List, Ctx, Options) :-
236    List = [_|_],
237    !,
238    context(Ctx, indent, Indent),
239    context(Ctx, depth, Depth),
240    option(output(Out), Options),
241    option(indent_arguments(IndentStyle), Options),
242    (   (   IndentStyle == false
243        ->  true
244        ;   IndentStyle == auto,
245            print_width(List, Width, Options),
246            option(right_margin(RM), Options),
247            Indent + Width < RM
248        )
249    ->  pprint(List, Ctx, Options)
250    ;   format(Out, '[ ', []),
251        Nindent is Indent + 2,
252        NDepth is Depth + 1,
253        modify_context(Ctx, [indent=Nindent, depth=NDepth], NCtx),
254        pp_list_elements(List, NCtx, Options),
255        indent(Out, Indent, Options),
256        format(Out, ']', [])
257    ).
258:- if(current_predicate(is_dict/1)).
259pp(Dict, Ctx, Options) :-
260    is_dict(Dict),
261    !,
262    dict_pairs(Dict, Tag, Pairs),
263    option(output(Out), Options),
264    option(indent_arguments(IndentStyle), Options),
265    context(Ctx, indent, Indent),
266    (   IndentStyle == false ; Pairs == []
267    ->  pprint(Dict, Ctx, Options)
268    ;   IndentStyle == auto,
269        print_width(Dict, Width, Options),
270        option(right_margin(RM), Options),
271        Indent + Width < RM         % fits on a line, simply write
272    ->  pprint(Dict, Ctx, Options)
273    ;   format(atom(Buf2), '~q{ ', [Tag]),
274        write(Out, Buf2),
275        atom_length(Buf2, FunctorIndent),
276        (   integer(IndentStyle)
277        ->  Nindent is Indent + IndentStyle,
278            (   FunctorIndent > IndentStyle
279            ->  indent(Out, Nindent, Options)
280            ;   true
281            )
282        ;   Nindent is Indent + FunctorIndent
283        ),
284        context(Ctx, depth, Depth),
285        NDepth is Depth + 1,
286        modify_context(Ctx, [indent=Nindent, depth=NDepth], NCtx0),
287        dec_depth(NCtx0, NCtx),
288        pp_dict_args(Pairs, NCtx, Options),
289        BraceIndent is Nindent - 2,         % '{ '
290        indent(Out, BraceIndent, Options),
291        write(Out, '}')
292    ).
293:- endif.
294pp(Term, Ctx, Options) :-               % handle operators
295    compound(Term),
296    compound_name_arity(Term, Name, Arity),
297    current_op(Prec, Type, Name),
298    match_op(Type, Arity, Kind, Prec, Left, Right),
299    option(operators(true), Options),
300    !,
301    quoted_op(Name, QName),
302    option(output(Out), Options),
303    context(Ctx, indent, Indent),
304    context(Ctx, depth, Depth),
305    context(Ctx, priority, CPrec),
306    NDepth is Depth + 1,
307    modify_context(Ctx, [depth=NDepth], Ctx1),
308    dec_depth(Ctx1, Ctx2),
309    LeftOptions  = Ctx2.put(priority, Left),
310    FuncOptions  = Ctx2.put(embrace, never),
311    RightOptions = Ctx2.put(priority, Right),
312    (   Kind == prefix
313    ->  arg(1, Term, Arg),
314        (   (   space_op(Name)
315            ;   need_space(Name, Arg, FuncOptions, RightOptions)
316            )
317        ->  Space = ' '
318        ;   Space = ''
319        ),
320        (   CPrec >= Prec
321        ->  format(atom(Buf), '~w~w', [QName, Space]),
322            atom_length(Buf, AL),
323            NIndent is Indent + AL,
324            write(Out, Buf),
325            modify_context(Ctx2, [indent=NIndent, priority=Right], Ctx3),
326            pp(Arg, Ctx3, Options)
327        ;   format(atom(Buf), '(~w', [QName,Space]),
328            atom_length(Buf, AL),
329            NIndent is Indent + AL,
330            write(Out, Buf),
331            modify_context(Ctx2, [indent=NIndent, priority=Right], Ctx3),
332            pp(Arg, Ctx3, Options),
333            format(Out, ')', [])
334        )
335    ;   Kind == postfix
336    ->  arg(1, Term, Arg),
337        (   (   space_op(Name)
338            ;   need_space(Name, Arg, FuncOptions, LeftOptions)
339            )
340        ->  Space = ' '
341        ;   Space = ''
342        ),
343        (   CPrec >= Prec
344        ->  modify_context(Ctx2, [priority=Left], Ctx3),
345            pp(Arg, Ctx3, Options),
346            format(Out, '~w~w', [Space,QName])
347        ;   format(Out, '(', []),
348            NIndent is Indent + 1,
349            modify_context(Ctx2, [indent=NIndent, priority=Left], Ctx3),
350            pp(Arg, Ctx3, Options),
351            format(Out, '~w~w)', [Space,QName])
352        )
353    ;   arg(1, Term, Arg1),
354        arg(2, Term, Arg2),
355        (   (   space_op(Name)
356            ;   need_space(Arg1, Name, LeftOptions, FuncOptions)
357            ;   need_space(Name, Arg2, FuncOptions, RightOptions)
358            )
359        ->  Space = ' '
360        ;   Space = ''
361        ),
362        (   CPrec >= Prec
363        ->  modify_context(Ctx2, [priority=Left], Ctx3),
364            pp(Arg1, Ctx3, Options),
365            format(Out, '~w~w~w', [Space,QName,Space]),
366            modify_context(Ctx2, [priority=Right], Ctx4),
367            pp(Arg2, Ctx4, Options)
368        ;   format(Out, '(', []),
369            NIndent is Indent + 1,
370            modify_context(Ctx2, [indent=NIndent, priority=Left], Ctx3),
371            pp(Arg1, Ctx3, Options),
372            format(Out, '~w~w~w', [Space,QName,Space]),
373            modify_context(Ctx2, [priority=Right], Ctx4),
374            pp(Arg2, Ctx4, Options),
375            format(Out, ')', [])
376        )
377    ).
378pp(Term, Ctx, Options) :-               % compound
379    option(output(Out), Options),
380    option(indent_arguments(IndentStyle), Options),
381    context(Ctx, indent, Indent),
382    (   IndentStyle == false
383    ->  pprint(Term, Ctx, Options)
384    ;   IndentStyle == auto,
385        print_width(Term, Width, Options),
386        option(right_margin(RM), Options),
387        Indent + Width < RM         % fits on a line, simply write
388    ->  pprint(Term, Ctx, Options)
389    ;   Term =.. [Name|Args],
390        format(atom(Buf2), '~q(', [Name]),
391        write(Out, Buf2),
392        atom_length(Buf2, FunctorIndent),
393        (   integer(IndentStyle)
394        ->  Nindent is Indent + IndentStyle,
395            (   FunctorIndent > IndentStyle
396            ->  indent(Out, Nindent, Options)
397            ;   true
398            )
399        ;   Nindent is Indent + FunctorIndent
400        ),
401        context(Ctx, depth, Depth),
402        NDepth is Depth + 1,
403        modify_context(Ctx, [indent=Nindent, depth=NDepth], NCtx0),
404        dec_depth(NCtx0, NCtx),
405        pp_compound_args(Args, NCtx, Options),
406        write(Out, ')')
407    ).
408
409
410quoted_op(Op, Atom) :-
411    is_solo(Op),
412    !,
413    Atom = Op.
414quoted_op(Op, Q) :-
415    format(atom(Q), '~q', [Op]).
416
417pp_list_elements(_, Ctx, Options) :-
418    context(Ctx, max_depth, 0),
419    !,
420    option(output(Out), Options),
421    write(Out, '...').
422pp_list_elements([H|T], Ctx0, Options) :-
423    dec_depth(Ctx0, Ctx),
424    pp(H, Ctx, Options),
425    (   T == []
426    ->  true
427    ;   nonvar(T),
428        T = [_|_]
429    ->  option(output(Out), Options),
430        write(Out, ','),
431        context(Ctx, indent, Indent),
432        indent(Out, Indent, Options),
433        pp_list_elements(T, Ctx, Options)
434    ;   option(output(Out), Options),
435        context(Ctx, indent, Indent),
436        indent(Out, Indent-2, Options),
437        write(Out, '| '),
438        pp(T, Ctx, Options)
439    ).
440
441
442pp_compound_args([H|T], Ctx, Options) :-
443    pp(H, Ctx, Options),
444    (   T == []
445    ->  true
446    ;   T = [_|_]
447    ->  option(output(Out), Options),
448        write(Out, ','),
449        context(Ctx, indent, Indent),
450        indent(Out, Indent, Options),
451        pp_compound_args(T, Ctx, Options)
452    ;   option(output(Out), Options),
453        context(Ctx, indent, Indent),
454        indent(Out, Indent-2, Options),
455        write(Out, '| '),
456        pp(T, Ctx, Options)
457    ).
458
459
460:- if(current_predicate(is_dict/1)).
461pp_dict_args([Name-Value|T], Ctx, Options) :-
462    option(output(Out), Options),
463    line_position(Out, Pos0),
464    pp(Name, Ctx, Options),
465    write(Out, ':'),
466    line_position(Out, Pos1),
467    context(Ctx, indent, Indent),
468    Indent2 is Indent + Pos1-Pos0,
469    modify_context(Ctx, [indent=Indent2], Ctx2),
470    pp(Value, Ctx2, Options),
471    (   T == []
472    ->  true
473    ;   option(output(Out), Options),
474        write(Out, ','),
475        indent(Out, Indent, Options),
476        pp_dict_args(T, Ctx, Options)
477    ).
478:- endif.
479
480%       match_op(+Type, +Arity, +Precedence, -LeftPrec, -RightPrec
481
482match_op(fx,    1, prefix,  P, _, R) :- R is P - 1.
483match_op(fy,    1, prefix,  P, _, P).
484match_op(xf,    1, postfix, P, _, L) :- L is P - 1.
485match_op(yf,    1, postfix, P, P, _).
486match_op(xfx,   2, infix,   P, A, A) :- A is P - 1.
487match_op(xfy,   2, infix,   P, L, P) :- L is P - 1.
488match_op(yfx,   2, infix,   P, P, R) :- R is P - 1.
489
490
491%!  indent(+Out, +Indent, +Options)
492%
493%   Newline and indent to the indicated  column. Respects the option
494%   =tab_width=.  Default  is  8.  If  the  tab-width  equals  zero,
495%   indentation is emitted using spaces.
496
497indent(Out, Indent, Options) :-
498    option(tab_width(TW), Options, 8),
499    nl(Out),
500    (   TW =:= 0
501    ->  tab(Out, Indent)
502    ;   Tabs is Indent // TW,
503        Spaces is Indent mod TW,
504        forall(between(1, Tabs, _), put(Out, 9)),
505        tab(Out, Spaces)
506    ).
507
508%!  print_width(+Term, -W, +Options) is det.
509%
510%   Width required when printing `normally' left-to-right.
511
512print_width(Term, W, Options) :-
513    option(right_margin(RM), Options),
514    (   write_length(Term, W, [max_length(RM)|Options])
515    ->  true
516    ;   W = RM
517    ).
518
519%!  pprint(+Term, +Context, +Options)
520%
521%   The bottom-line print-routine.
522
523pprint(Term, Ctx, Options) :-
524    option(output(Out), Options),
525    pprint(Out, Term, Ctx, Options).
526
527pprint(Out, Term, Ctx, Options) :-
528    option(write_options(WriteOptions), Options),
529    context(Ctx, max_depth, MaxDepth),
530    (   MaxDepth == infinite
531    ->  write_term(Out, Term, WriteOptions)
532    ;   MaxDepth =< 0
533    ->  format(Out, '...', [])
534    ;   write_term(Out, Term, [max_depth(MaxDepth)|WriteOptions])
535    ).
536
537
538		 /*******************************
539		 *    SHARED WITH term_html.pl	*
540		 *******************************/
541
542
543%!  is_op1(+Name, -Type, -Priority, -ArgPriority, +Options) is semidet.
544%
545%   True if Name is an operator taking one argument of Type.
546
547is_op1(Name, Type, Pri, ArgPri, Options) :-
548    operator_module(Module, Options),
549    current_op(Pri, OpType, Module:Name),
550    argpri(OpType, Type, Pri, ArgPri),
551    !.
552
553argpri(fx, prefix,  Pri0, Pri) :- Pri is Pri0 - 1.
554argpri(fy, prefix,  Pri,  Pri).
555argpri(xf, postfix, Pri0, Pri) :- Pri is Pri0 - 1.
556argpri(yf, postfix, Pri,  Pri).
557
558%!  is_op2(+Name, -LeftPri, -Pri, -RightPri, +Options) is semidet.
559%
560%   True if Name is an operator taking two arguments of Type.
561
562is_op2(Name, LeftPri, Pri, RightPri, Options) :-
563    operator_module(Module, Options),
564    current_op(Pri, Type, Module:Name),
565    infix_argpri(Type, LeftPri, Pri, RightPri),
566    !.
567
568infix_argpri(xfx, ArgPri, Pri, ArgPri) :- ArgPri is Pri - 1.
569infix_argpri(yfx, Pri, Pri, ArgPri) :- ArgPri is Pri - 1.
570infix_argpri(xfy, ArgPri, Pri, Pri) :- ArgPri is Pri - 1.
571
572
573%!  need_space(@Term1, @Term2, +LeftOptions, +RightOptions)
574%
575%   True if a space is  needed  between   Term1  and  Term2  if they are
576%   printed using the given option lists.
577
578need_space(T1, T2, _, _) :-
579    (   is_solo(T1)
580    ;   is_solo(T2)
581    ),
582    !,
583    fail.
584need_space(T1, T2, LeftOptions, RightOptions) :-
585    end_code_type(T1, TypeR, LeftOptions.put(side, right)),
586    end_code_type(T2, TypeL, RightOptions.put(side, left)),
587    \+ no_space(TypeR, TypeL).
588
589no_space(punct, _).
590no_space(_, punct).
591no_space(quote(R), quote(L)) :-
592    !,
593    R \== L.
594no_space(alnum, symbol).
595no_space(symbol, alnum).
596
597%!  end_code_type(+Term, -Code, Options)
598%
599%   True when code is the first/last character code that is emitted
600%   by printing Term using Options.
601
602end_code_type(_, Type, Options) :-
603    MaxDepth = Options.max_depth,
604    integer(MaxDepth),
605    Options.depth >= MaxDepth,
606    !,
607    Type = symbol.
608end_code_type(Term, Type, Options) :-
609    primitive(Term, _),
610    !,
611    quote_atomic(Term, S, Options),
612    end_type(S, Type, Options).
613end_code_type(Dict, Type, Options) :-
614    is_dict(Dict, Tag),
615    !,
616    (   Options.side == left
617    ->  end_code_type(Tag, Type, Options)
618    ;   Type = punct
619    ).
620end_code_type('$VAR'(Var), Type, Options) :-
621    Options.get(numbervars) == true,
622    !,
623    format(string(S), '~W', ['$VAR'(Var), [numbervars(true)]]),
624    end_type(S, Type, Options).
625end_code_type(List, Type, _) :-
626    (   List == []
627    ;   List = [_|_]
628    ),
629    !,
630    Type = punct.
631end_code_type(OpTerm, Type, Options) :-
632    compound_name_arity(OpTerm, Name, 1),
633    is_op1(Name, Type, Pri, ArgPri, Options),
634    \+ Options.get(ignore_ops) == true,
635    !,
636    (   Pri > Options.priority
637    ->  Type = punct
638    ;   (   Type == prefix
639        ->  end_code_type(Name, Type, Options)
640        ;   arg(1, OpTerm, Arg),
641            arg_options(Options, ArgOptions),
642            end_code_type(Arg, Type, ArgOptions.put(priority, ArgPri))
643        )
644    ).
645end_code_type(OpTerm, Type, Options) :-
646    compound_name_arity(OpTerm, Name, 2),
647    is_op2(Name, LeftPri, Pri, _RightPri, Options),
648    \+ Options.get(ignore_ops) == true,
649    !,
650    (   Pri > Options.priority
651    ->  Type = punct
652    ;   arg(1, OpTerm, Arg),
653        arg_options(Options, ArgOptions),
654        end_code_type(Arg, Type, ArgOptions.put(priority, LeftPri))
655    ).
656end_code_type(Compound, Type, Options) :-
657    compound_name_arity(Compound, Name, _),
658    end_code_type(Name, Type, Options).
659
660end_type(S, Type, Options) :-
661    number(S),
662    !,
663    (   (S < 0 ; S == -0.0),
664        Options.side == left
665    ->  Type = symbol
666    ;   Type = alnum
667    ).
668end_type(S, Type, Options) :-
669    Options.side == left,
670    !,
671    sub_string(S, 0, 1, _, Start),
672    syntax_type(Start, Type).
673end_type(S, Type, _) :-
674    sub_string(S, _, 1, 0, End),
675    syntax_type(End, Type).
676
677syntax_type("\"", quote(double)) :- !.
678syntax_type("\'", quote(single)) :- !.
679syntax_type("\`", quote(back))   :- !.
680syntax_type(S, Type) :-
681    string_code(1, S, C),
682    (   code_type(C, prolog_identifier_continue)
683    ->  Type = alnum
684    ;   code_type(C, prolog_symbol)
685    ->  Type = symbol
686    ;   code_type(C, space)
687    ->  Type = layout
688    ;   Type = punct
689    ).
690
691is_solo(Var) :-
692    var(Var), !, fail.
693is_solo(',').
694is_solo(';').
695is_solo('!').
696
697%!  primitive(+Term, -Class) is semidet.
698%
699%   True if Term is a primitive term, rendered using the CSS
700%   class Class.
701
702primitive(Term, Type) :- var(Term),      !, Type = 'pl-avar'.
703primitive(Term, Type) :- atom(Term),     !, Type = 'pl-atom'.
704primitive(Term, Type) :- string(Term),   !, Type = 'pl-string'.
705primitive(Term, Type) :- integer(Term),  !, Type = 'pl-int'.
706primitive(Term, Type) :- rational(Term), !, Type = 'pl-rational'.
707primitive(Term, Type) :- float(Term),    !, Type = 'pl-float'.
708
709%!  operator_module(-Module, +Options) is det.
710%
711%   Find the module for evaluating operators.
712
713operator_module(Module, Options) :-
714    Module = Options.get(module),
715    !.
716operator_module(TypeIn, _) :-
717    '$module'(TypeIn, TypeIn).
718
719%!  arg_options(+Options, -OptionsOut) is det.
720%
721%   Increment depth in Options.
722
723arg_options(Options, Options.put(depth, NewDepth)) :-
724    NewDepth is Options.depth+1.
725
726quote_atomic(Float, String, Options) :-
727    float(Float),
728    Format = Options.get(float_format),
729    !,
730    format(string(String), Format, [Float]).
731quote_atomic(Plain, Plain, _) :-
732    number(Plain),
733    !.
734quote_atomic(Plain, String, Options) :-
735    Options.get(quoted) == true,
736    !,
737    (   Options.get(embrace) == never
738    ->  format(string(String), '~q', [Plain])
739    ;   format(string(String), '~W', [Plain, Options])
740    ).
741quote_atomic(Var, String, Options) :-
742    var(Var),
743    !,
744    format(string(String), '~W', [Var, Options]).
745quote_atomic(Plain, Plain, _).
746
747space_op(:-).
748