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.
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.
After the colon, it is possible to specify where to insert the defined levels:
- The identifier "FIRST" (resp. "LAST") indicates that the level must be inserted before (resp. after) all possibly existing levels of the entry. They become their first (resp. last) levels.
- The identifier "BEFORE" (resp. "AFTER") followed by a level label (a string) indicates that the levels must be inserted before (resp. after) that level, if it exists. If it does not exist, the extend statement fails at run time.
- The identifier "LEVEL" followed by a label indicates that the first level defined in the extend statement must be inserted at the given level, extending and modifying it. The other levels defined in the statement are inserted after this level, and before the possible levels following this level. If there is no level with this label, the extend statement fails at run time.
- By default, if the entry has no level, the levels defined in the statement are inserted in the entry. Otherwise the first defined level is inserted at the first level of the entry, extending or modifying it. The other levels are inserted afterwards (before the possible second level which may previously exist in the entry).
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.
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".
The rules are sorted by their patterns, according to the rules of the extensible functions.
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 variable "curr" which is a function which can be called to recursively invoke the printer at the current level,
- The variable "next" which is a function which can be called to invoke the printer at the next level,
- The variable "pc" which contains the printing context of type "Pprintf.pr_context" (see chapter Pprintf).
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".
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:
- pr_r.cmo: pretty print in revised syntax
- pr_o.cmo: pretty print in normal syntax
- pr_rp.cmo: also pretty print the parsers in revised syntax
- pr_op.cmo: also pretty print the parsers in normal syntax
See the chapter entitled "Printing programs".↑