Extensible printers

This chapter describes the syntax and semantics of the extensible printers of Camlp5.

Similar to the extensible grammars, the extensible printers allow to define and extend printers of data or programs. A specific statement "EXTEND_PRINTER" allow to define these extensions.

  1. Getting started
  2. Syntax of the EXTEND_PRINTER statement
  3. Semantics of EXTEND_PRINTER
  4. The Eprinter module
  5. Examples

Getting started

A printer is a value of type "Eprinter.t a" where "a" is the type of the item to be printed. When applied, a printer returns a string, representing the printed item.

To create a printer, one must use the function "Eprinter.make" with, as parameter, the name of the printer, (used in error messages). A printer is created empty, i.e. it fails if it is applied.

As with grammar entries, printers may have several levels. When the function "Eprinter.apply" is applied to a printer, the first level is called. The function "Eprinter.apply_level" allows to call a printer at some specific level possibly different from the first one. When a level does not match any value of the printed item, the next level is tested. If there is no more levels, the printer fails.

In semantic actions of printers, functions are provided to recursively call the current level and the next level. Moreover, a printing context variable is also given, giving the current indentation, what should be printed before in the same line and what should be printed after in the same line (it is not mandatory to use them).

The extension of printers can be done with the "EXTEND_PRINTER" statement added by the parsing kit "pa_extprint.cmo".

Syntax of the EXTEND_PRINTER statement

        expression ::= extend-statement
  extend-statement ::= "EXTEND_PRINTER" extend-body "END"
       extend-body ::= extend-printers
   extend-printers ::= extend-printer extend-printers
                     | <nothing>
    extend-printer ::= printer-name ":" position-opt "[" levels "]"
      position-opt ::= "FIRST"
                     | "LAST"
                     | "BEFORE" label
                     | "AFTER" label
                     | "LEVEL" label
                     | <nothing>
            levels ::= level "|" levels
                     | level
             level ::= label-opt "[" rules "]"
         label-opt ::= label
                     | <nothing>
             rules ::= rule "|" rules
                     | rule
              rule ::= pattern "->" expression
                     | pattern "when" expression "->" expression
      printer-name ::= qualid
            qualid ::= qualid "." qualid
                     | uident
                     | lident
            uident ::= 'A'-'Z' ident
            lident ::= ('a'-'z' | '_' | misc-byte) ident
             ident ::= ident-char*
        ident-char ::= ('a'-'a' | 'A'-'Z' | '0'-'9' | '_' | ''' | misc-byte)
         misc-byte ::= '\128'-'\255'

Semantics of EXTEND_PRINTER

Printers definition list

All printers are extended according to their corresponding definitions which start with an optional "position" and follow with the "levels" definition.

Optional position

After the colon, it is possible to specify where to insert the defined levels:

Levels

After the optional "position", the level list follow. The levels are separated by vertical bars, the whole list being between brackets.

A level starts with an optional label, which corresponds to its name. This label is useful to specify this level in case of future extensions, using the position (see previous section) or for possible direct calls to this specific level.

Rules

A level is a list of rules separated by vertical bars, the whole list being between brackets.

A rule is an usual pattern association (in a function or in the "match" statement), i.e. a pattern, an arrow and an expression. The expression is the semantic action which must be of type "string".

Rules insertion

The rules are sorted by their patterns, according to the rules of the extensible functions.

Semantic action

The semantic action, i.e. the expression following the right arrow in rules, contains in its environment the variables bound by the pattern and three more variables:

The variables "curr" and "next" are of type:

  pr_context -> t -> string

where "t" is the type of the printer (i.e. the type of its patterns).

The variable "curr", "next" and "pc" have predefined names and can hide the possible identifiers having the same names in the pattern or in the environment of the "EXTEND_PRINTER" statement.

The Eprinter module

See its section in the chapter "Library".

Examples

Parser and Printer of expressions

This example illustrates the symmetry between parsers and printers. A simple type of expressions is defined. A parser converts a string to a value of this type, and a printer converts a value of this type to a string.

In the printer, there is no use of the "pc" parameter and no use of the "Pretty" module. The strings are printed on a single line.

Here is the source (file "foo.ml"):

  #load "pa_extend.cmo";
  #load "pa_extprint.cmo";

  open Printf;

  type expr =
    [ Op of string and expr and expr
    | Int of int
    | Var of string ]
  ;

  value g = Grammar.gcreate (Plexer.gmake ());
  value pa_e = Grammar.Entry.create g "expr";
  value pr_e = Eprinter.make "expr";

  EXTEND
    pa_e:
      [ [ x = SELF; "+"; y = SELF -> Op "+" x y
        | x = SELF; "-"; y = SELF -> Op "-" x y ]
      | [ x = SELF; "*"; y = SELF -> Op "*" x y
        | x = SELF; "/"; y = SELF -> Op "/" x y ]
      | [ x = INT -> Int (int_of_string x)
        | x = LIDENT -> Var x
        | "("; x = SELF; ")" -> x ] ]
    ;
  END;

  EXTEND_PRINTER
    pr_e:
      [ [ Op "+" x y -> sprintf "%s + %s" (curr pc x) (next pc y)
        | Op "-" x y -> sprintf "%s - %s" (curr pc x) (next pc y) ]
      | [ Op "*" x y -> sprintf "%s * %s" (curr pc x) (next pc y)
        | Op "/" x y -> sprintf "%s / %s" (curr pc x) (next pc y) ]
      | [ Int x -> string_of_int x
        | Var x -> x
        | x -> sprintf "(%s)" (Eprinter.apply pr_e pc x) ] ]
    ;
  END;

  value parse s = Grammar.Entry.parse pa_e (Stream.of_string s);
  value print e = Eprinter.apply pr_e Pprintf.empty_pc e;

  if Sys.interactive.val then ()
  else print_endline (print (parse Sys.argv.(1)));

Remark on the use of "curr" and "next" while printing operators: due to left associativity, the first operand uses "curr" and the second operand uses "next". For right associativity operators, they should be inverted. For no associativity, both should use "next".

The last line of the file allows use in either the OCaml toplevel or as standalone program, taking the string to be printed as parameter. It can be compiled this way:

  ocamlc -pp camlp5r -I +camlp5 gramlib.cma foo.ml

Examples of use (notice the redundant parentheses automatically removed by the printing algorithm):

  $ ./a.out "(3 * x) + (2 / y)"
  3 * x + 2 / y
  $ ./a.out "(x+y)*(x-y)"
  (x + y) * (x - y)
  $ ./a.out "x + y - z"
  x + y - z
  $ ./a.out "(x + y) - z"
  x + y - z
  $ ./a.out "x + (y - z)"
  x + (y - z)

Printing OCaml programs

Complete examples of usage of extensible printers are the printers in syntaxes and extended syntaxes provided by Camlp5 in the pretty printing kits:

See the chapter entitled "Printing programs".


Copyright 2007-2017 Daniel de Rauglaudre (INRIA)

Valid XHTML 1.1