add_user_printer :
  ({Tyop : string, Thy : string} * userprinter * string) -> unit

SYNOPSIS
Adds a user specified pretty-printer for a specified type.

LIBRARY
Parse

DESCRIBE
The function add_user_printer is used to add a special purpose term pretty-printer to the interactive system. The pretty-printer is called whenever the type of a term to be printed is as given by the first parameter of the triple. This first parameter specifies an operator that is not necessarily nullary, so that if the Tyop-Thy pair is list-list for example, then the printing function will be called on all values of type :x list, where x is any type.

The user-supplied function may choose not to print anything for the given term and hand back control to the standard printer by raising the exception term_pp_types.UserPP_Failed. All other exceptions will propagate to the top-level. If the system printer receives the UserPP_Failed exception, it prints out the term using its standard algorithm, but will again attempt to call the user function on any sub-terms of the given type.

The type userprinter is an abbreviation defined in term_pp_types to be

   type userprinter =
     sysprinter -> (grav * grav * grav) -> int -> Portable.ppstream ->
     term -> unit
where the type grav is
   datatype grav = Top | RealTop | Prec of (int * string)
and the type sysprinter is another abbreviation
   type sysprinter = (grav * grav * grav) -> int -> term -> unit
Thus, when the user's printing function is called, it is passed seven parameters, including three "gravity" values in a triple. The first parameter is the system's own printer, so that the user function can use the default printer on sub-terms that it is not interested in. The user function must not call the sysprinter on the term that it is handed initially as the sysprinter will immediately call the user printing function all over again. If the user printer wants to give the whole term back to the system printer, then it must use the UserPP_Failed exception described above.

The grav type is used to let pretty-printers know a little about the context in which a term is to be printed out. The triple of gravities is given in the order ``parent'', ``left'' and ``right''. The left and right gravities specify the precedence of any operator that might be attempting to ``grab'' arguments from the left and right. For example, the term

   (p /\ (if q then r else s)) ==> t
should be pretty-printed as
   p /\ (if q then r else s) ==> t
The system figures this out when it comes to print the conditional expression because it knows both that the operator to the left has the appropriate precedence for conjunction but also that there is an operator with implication's precedence to the right. The issue arises because conjunction is tighter than implication in precedence, leading the printer to decide that parentheses aren't necessary around the conjunction. Similarly, considered on its own, the conjunction doesn't require parentheses around the conditional expression because there is no competition between them for arguments.

The grav constructors Top and RealTop indicate a context analogous to the top of the term, where there is no binding competition. The constructor RealTop is reserved for situations where the term really is the top of the tree; Top is used for analogous situations such when the term is enclosed in parentheses. (In the conditional expression above, the printing of q will have Top gravities to the left and right.)

The Prec constructor for gravity values takes both a number indicating precedence level and a string corresponding to the token that has this precedence level. This string parameter is of most importance in the parent gravity (the first component of the triple) where it can be useful in deciding whether or not to print parentheses and whether or not to begin fresh pretty-printing blocks. For example, tuples in the logic look better if they have parentheses around the topmost instance of the comma-operator, regardless of whether or not this is required according to precedence considerations. By examining the parent gravity, a printer can determine more about the term's context. (Note that the parent gravity will also be one or other of the left and right gravities; but it is not possible to tell which.)

The integer parameter to both the system printing function and the user printing function is the depth of the term. The system printer will stop printing a term if the depth ever reaches exactly zero. Each time it calls itself recursively, the depth parameter is reduced by one. It starts out at the value stored in Globals.max_print_depth. Setting the latter to ~1 will ensure that all of a term is always printed.

Finally, the string parameter to the add_user_printer function is a string corresponding to the ML function. Best practice is probably to define the printing function in an independent structure and to then have the string be of the form "module.fnname". This parameter is not present in the accompanying temp_add_user_printer, as this latter function does not affect the grammar exported to disk with export_theory.

FAILURE
Will not fail directly, but if the function parameter fails to print all terms of the registered type in any other way than raising the UserPP_Failed exception, then the pretty-printer will also fail. If the string parameter does not correspond to valid ML code, then the theory file generated by export_theory will not compile.

EXAMPLE
This example uses the system printer to print sub-terms, and concerns itself only with printing conjunctions:
  - fun myprint sys gravs d pps t = let
      open Portable term_pp_types
      val (l,r) = dest_conj t
    in
      add_string pps "CONJ:";
      add_break pps (1,0);
      sys (Top, Top, Top) (d - 1) l;
      add_string " and then ";
      sys (Top, Top, Top) (d - 1) r;
      add_string "ENDCONJ"
    end handle HOL_ERR _ => raise term_pp_types.UserPP_Failed;
  > val ('a, 'b) myprint = fn :
    (grav * grav * grav -> int -> term -> 'a) -> 'b -> int ->
    ppstream -> term -> unit

  - temp_add_user_printer ({Tyop = "bool", Thy = "min"}, myprint);
  > val it = () : unit

  - ``p ==> q /\ r``;
  > val it = ``p ==> CONJ: q and then r ENDCONJ`` : term
The variables p, q and r as well as the implication are all of boolean type, but are handled by the system printer. The user printer handles just the special form of the conjunction. Note that this example actually falls within the scope of the add_rule functionality.

Another approach to printing conjunctions is not possible with add_rule:

  - fun myprint2 sys (pg,lg,rg) d pps t = let
      open Portable term_pp_types
      val (l,r) = dest_conj t
      fun delim act = case pg of
                        Prec(_, "CONJ") => ()
                      | _ => act()
    in
      delim (fn () => (begin_block pps CONSISTENT 0;
                       add_string pps "CONJ";
                       add_break pps (1,2);
                       begin_block pps INCONSISTENT 0));
      sys (Prec(0, "CONJ"), Top, Top) (d - 1) l;
      add_string pps ",";
      add_break pps (1,0);
      sys (Prec(0, "CONJ"), Top, Top) (d - 1) r;
      delim (fn () => (end_block pps;
                       add_break pps (1,0);
                       add_string pps "ENDCONJ";
                       end_block pps))
    end handle HOL_ERR _ => raise term_pp_types.UserPP_Failed;
  > val ('a, 'b, 'c) myprint2 = fn :
    (grav * grav * grav -> int -> term -> 'a) -> grav * 'b * 'c ->
    int -> ppstream -> term -> unit

  - ``p /\ q /\ r /\ s /\ t /\ u /\ p /\ p /\ p /\ p /\ p /\ p /\
      p /\ p /\ p /\ p/\ p /\ p /\ q /\ r /\ s /\ t /\ u /\ v /\
      (w /\ x) /\ (p \/ q) /\ r``;
  > val it =
      ``CONJ
          p, q, r, s, t, u, p, p, p, p, p, p, p, p, p, p, p, p, q,
          r, s, t, u, v, w, x, p \/ q, r
        ENDCONJ`` : term
This examples demonstrates using pretty-printer blocks in order to get a pleasing effect, and also using parent gravities to print out a big term. Note also how the flow of control doubles backwards and forwards between the system printer and the user's. A better approach (and certainly a more direct one) would probably be to call strip_conj and print all of the conjuncts in one fell swoop. Finally, this example demonstrates how easy it is to conceal genuine syntactic structure with a pretty-printer.

USES
For extending the pretty-printer in ways not possible to encompass with the built-in grammar rules for concrete syntax.

SEEALSO  remove_user_printer

HOL  Kananaskis 0