open PPrint
let smart_lparen = lparen ^^ ifflat empty space
let smart_rparen = ifflat empty hardline ^^ rparen
open Document

(* This file defines how to pretty-print a CST. *)

(* We use the module [Document], which extends [PPrint] with a notion of
   fragility. Fragility determines whether a space must (or need not) be
   inserted in the middle when two documents are concatenated. *)

(* We do not guarantee that the style in which expressions are rendered
   is the best possible style. It is just one example of pretty-printing
   concrete syntax trees.  *)


class print = object (self)

  (* The class [Parser.CST.reduce] is generated by Menhir. At a minimum,
     we must give concrete definitions of its virtual methods. We must
     also override some of its concrete methods in order to fine-tune
     certain aspects (e.g., to specify where boxes and breakable spaces
     are used). *)
  inherit [document] Parser.CST.reduce as super

  (* The methods [zero], [cat], [text] are specific to our choice
     of [document] as the result type. *)

  method zero = empty

  method cat d1 d2 =
    d1 ^^ d2

  method text = string (* or [utf8string] *)

  (* The following methods define how the tokens [INT] and [EOL] are
     printed. The methods associated with all other tokens are already
     defined by Menhir, based on the token aliases that we have provided
     in [parser.mly]. *)

  method visit_INT i =
    int i

  method visit_EOL =
    robust hardline

  (* The following methods customize the pretty-printed output. *)

  method! visit_expr e =
    (* Enclose every sub-expression in a group, so that, if possible,
       this sub-expression is printed in flat mode on a single line. *)
    group (super#visit_expr e)

  method! case_paren e =
    (* Customize the appearance of parenthesized expressions. In flat mode,
       we keep the default appearance provided by our super-class. If the
       expression [e] cannot be printed on a single line, then we want to
       produce something like this:

         ( 2 +
           3
         )

       That is, we want to indent the content of the parentheses by two; we
       want the content to begin on the same line as the opening parenthesis
       (after skipping a space); and we want the closing parenthesis to be
       alone on a line, aligned with the opening parenthesis. This can be
       achieved as follows. (Note: the fact that the two parentheses end up
       aligned is not obvious. It is a global property that holds because of
       the manner in which we insert breaks when printing unary and binary
       operators.) *)
    nest 2 (robust smart_lparen ^^ self#visit_expr e) ^^ robust smart_rparen

  (* Customize the appearance of binary operators. We want a normal space on
     the left-hand side and a breakable space on the right-hand side, so
     that, if line break must inserted, it is inserted after the
     operator. *)

  method! visit_PLUS =
    space ^^ robust plus ^^ break 1

  method! visit_MINUS =
    space ^^ robust minus ^^ break 1

  method! visit_TIMES =
    space ^^ robust star ^^ break 1

  method! visit_DIV =
    space ^^ robust slash ^^ break 1

  (* Customize the appearance of the production [expr -> MINUS expr]. We
     do not want the method [visit_MINUS] to be used in this case, as it
     would insert a leading space. *)

  method! case_neg e =
    robust minus ^^ break 0 ^^ self#visit_expr e

end

(* The entry point. *)

let main (m : Parser.CST.main) : document =
  (new print)#visit_main m
