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)  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(:-)