src/Pure/General/pretty.ML
author wenzelm
Tue, 24 Jun 2008 21:44:52 +0200
changeset 27347 31f98eaa198d
parent 26703 c07b1a90600c
child 27350 c8adf88960ad
permissions -rw-r--r--
pprint: proper output of markup (important for token translation);
     1 (*  Title:      Pure/General/pretty.ML
     2     ID:         $Id$
     3     Author:     Lawrence C Paulson, Cambridge University Computer Laboratory
     4     Author:     Markus Wenzel, TU Munich
     5 
     6 Generic pretty printing module.
     7 
     8 Loosely based on
     9   D. C. Oppen, "Pretty Printing",
    10   ACM Transactions on Programming Languages and Systems (1980), 465-483.
    11 
    12 The object to be printed is given as a tree with indentation and line
    13 breaking information.  A "break" inserts a newline if the text until
    14 the next break is too long to fit on the current line.  After the newline,
    15 text is indented to the level of the enclosing block.  Normally, if a block
    16 is broken then all enclosing blocks will also be broken.  Only "inconsistent
    17 breaks" are provided.
    18 
    19 The stored length of a block is used in breakdist (to treat each inner block as
    20 a unit for breaking).
    21 *)
    22 
    23 type pprint_args = (output -> unit) * (int -> unit) * (int -> unit) *
    24   (unit -> unit) * (unit -> unit);
    25 
    26 signature PRETTY =
    27 sig
    28   val default_indent: string -> int -> output
    29   val add_mode: string -> (string -> int -> output) -> unit
    30   type T
    31   val str: string -> T
    32   val brk: int -> T
    33   val fbrk: T
    34   val breaks: T list -> T list
    35   val fbreaks: T list -> T list
    36   val blk: int * T list -> T
    37   val block: T list -> T
    38   val strs: string list -> T
    39   val markup: Markup.T -> T list -> T
    40   val mark: Markup.T -> T -> T
    41   val keyword: string -> T
    42   val command: string -> T
    43   val markup_chunks: Markup.T -> T list -> T
    44   val chunks: T list -> T
    45   val chunks2: T list -> T
    46   val block_enclose: T * T -> T list -> T
    47   val quote: T -> T
    48   val backquote: T -> T
    49   val separate: string -> T list -> T list
    50   val commas: T list -> T list
    51   val enclose: string -> string -> T list -> T
    52   val enum: string -> string -> string -> T list -> T
    53   val list: string -> string -> T list -> T
    54   val str_list: string -> string -> string list -> T
    55   val big_list: string -> T list -> T
    56   val indent: int -> T -> T
    57   val unbreakable: T -> T
    58   val setmargin: int -> unit
    59   val setmp_margin: int -> ('a -> 'b) -> 'a -> 'b
    60   val setdepth: int -> unit
    61   val pprint: T -> pprint_args -> unit
    62   val symbolicN: string
    63   val output_buffer: T -> Buffer.T
    64   val output: T -> output
    65   val string_of: T -> string
    66   val str_of: T -> string
    67   val writeln: T -> unit
    68   type pp
    69   val pp: (term -> T) * (typ -> T) * (sort -> T) * (class list -> T) * (arity -> T) -> pp
    70   val term: pp -> term -> T
    71   val typ: pp -> typ -> T
    72   val sort: pp -> sort -> T
    73   val classrel: pp -> class list -> T
    74   val arity: pp -> arity -> T
    75   val string_of_term: pp -> term -> string
    76   val string_of_typ: pp -> typ -> string
    77   val string_of_sort: pp -> sort -> string
    78   val string_of_classrel: pp -> class list -> string
    79   val string_of_arity: pp -> arity -> string
    80 end;
    81 
    82 structure Pretty: PRETTY =
    83 struct
    84 
    85 (** print mode operations **)
    86 
    87 fun default_indent (_: string) = Symbol.spaces;
    88 
    89 local
    90   val default = {indent = default_indent};
    91   val modes = ref (Symtab.make [("", default)]);
    92 in
    93   fun add_mode name indent = CRITICAL (fn () =>
    94     change modes (Symtab.update_new (name, {indent = indent})));
    95   fun get_mode () =
    96     the_default default (Library.get_first (Symtab.lookup (! modes)) (print_mode_value ()));
    97 end;
    98 
    99 fun mode_indent x y = #indent (get_mode ()) x y;
   100 
   101 val output_spaces = Output.output o Symbol.spaces;
   102 val add_indent = Buffer.add o output_spaces;
   103 
   104 
   105 
   106 (** printing items: compound phrases, strings, and breaks **)
   107 
   108 datatype T =
   109   Block of Markup.T * T list * int * int |  (*markup, body, indentation, length*)
   110   String of output * int |                  (*text, length*)
   111   Break of bool * int;                      (*mandatory flag, width if not taken*)
   112 
   113 fun length (Block (_, _, _, len)) = len
   114   | length (String (_, len)) = len
   115   | length (Break (_, wd)) = wd;
   116 
   117 
   118 
   119 (** derived operations to create formatting expressions **)
   120 
   121 val str = String o Output.output_width;
   122 
   123 fun brk wd = Break (false, wd);
   124 val fbrk = Break (true, 2);
   125 
   126 fun breaks prts = Library.separate (brk 1) prts;
   127 fun fbreaks prts = Library.separate fbrk prts;
   128 
   129 fun markup_block m (indent, es) =
   130   let
   131     fun sum [] k = k
   132       | sum (e :: es) k = sum es (length e + k);
   133   in Block (m, es, indent, sum es 0) end;
   134 
   135 val blk = markup_block Markup.none;
   136 fun block prts = blk (2, prts);
   137 val strs = block o breaks o map str;
   138 
   139 fun markup m prts = markup_block m (0, prts);
   140 fun mark m prt = markup m [prt];
   141 fun keyword name = mark (Markup.keyword name) (str name);
   142 fun command name = mark (Markup.command name) (str name);
   143 
   144 fun markup_chunks m prts = markup m (fbreaks prts);
   145 val chunks = markup_chunks Markup.none;
   146 fun chunks2 prts = blk (0, flat (Library.separate [fbrk, fbrk] (map single prts)));
   147 
   148 fun block_enclose (p1, p2) ps = chunks [(block  o fbreaks) (p1 :: ps), p2];
   149 
   150 fun quote prt = blk (1, [str "\"", prt, str "\""]);
   151 fun backquote prt = blk (1, [str "`", prt, str "`"]);
   152 
   153 fun separate sep prts =
   154   flat (Library.separate [str sep, brk 1] (map single prts));
   155 
   156 val commas = separate ",";
   157 
   158 fun enclose lpar rpar prts =
   159   block (str lpar :: (prts @ [str rpar]));
   160 
   161 fun enum sep lpar rpar prts = enclose lpar rpar (separate sep prts);
   162 
   163 val list = enum ",";
   164 fun str_list lpar rpar strs = list lpar rpar (map str strs);
   165 
   166 fun big_list name prts = block (fbreaks (str name :: prts));
   167 
   168 fun indent 0 prt = prt
   169   | indent n prt = blk (0, [str (Symbol.spaces n), prt]);
   170 
   171 fun unbreakable (Break (_, wd)) = String (output_spaces wd, wd)
   172   | unbreakable (Block (m, es, indent, wd)) = Block (m, map unbreakable es, indent, wd)
   173   | unbreakable (e as String _) = e;
   174 
   175 
   176 
   177 (** formatting **)
   178 
   179 (* margin *)
   180 
   181 fun make_margin_info m =
   182  {margin = m,                   (*right margin, or page width*)
   183   breakgain = m div 20,         (*minimum added space required of a break*)
   184   emergencypos = m div 2};      (*position too far to right*)
   185 
   186 val margin_info = ref (make_margin_info 76);
   187 fun setmargin m = margin_info := make_margin_info m;
   188 fun setmp_margin m f = setmp margin_info (make_margin_info m) f;
   189 
   190 
   191 (* depth limitation *)
   192 
   193 val depth = ref 0;   (*maximum depth; 0 means no limit*)
   194 fun setdepth dp = (depth := dp);
   195 
   196 local
   197   fun pruning dp (Block (m, bes, indent, wd)) =
   198         if dp > 0
   199         then markup_block m (indent, map (pruning (dp - 1)) bes)
   200         else str "..."
   201     | pruning dp e = e
   202 in
   203   fun prune e = if ! depth > 0 then pruning (! depth) e else e;
   204 end;
   205 
   206 
   207 (* formatted output *)
   208 
   209 local
   210 
   211 type text = {tx: Buffer.T, ind: Buffer.T, pos: int, nl: int};
   212 
   213 val empty: text =
   214  {tx = Buffer.empty,
   215   ind = Buffer.empty,
   216   pos = 0,
   217   nl = 0};
   218 
   219 fun newline {tx, ind, pos, nl} : text =
   220  {tx = Buffer.add (Output.output "\n") tx,
   221   ind = Buffer.empty,
   222   pos = 0,
   223   nl = nl + 1};
   224 
   225 fun control s {tx, ind, pos: int, nl} : text =
   226  {tx = Buffer.add s tx,
   227   ind = ind,
   228   pos = pos,
   229   nl = nl};
   230 
   231 fun string (s, len) {tx, ind, pos: int, nl} : text =
   232  {tx = Buffer.add s tx,
   233   ind = Buffer.add s ind,
   234   pos = pos + len,
   235   nl = nl};
   236 
   237 fun blanks wd = string (output_spaces wd, wd);
   238 
   239 fun indentation (buf, len) {tx, ind, pos, nl} : text =
   240   let val s = Buffer.content buf in
   241    {tx = Buffer.add (mode_indent s len) tx,
   242     ind = Buffer.add s ind,
   243     pos = pos + len,
   244     nl = nl}
   245   end;
   246 
   247 (*Add the lengths of the expressions until the next Break; if no Break then
   248   include "after", to account for text following this block.*)
   249 fun breakdist (Block (_, _, _, len) :: es, after) = len + breakdist (es, after)
   250   | breakdist (String (s, len) :: es, after) = len + breakdist (es, after)
   251   | breakdist (Break _ :: es, after) = 0
   252   | breakdist ([], after) = after;
   253 
   254 (*Search for the next break (at this or higher levels) and force it to occur.*)
   255 fun forcenext [] = []
   256   | forcenext (Break (_, wd) :: es) = Break (true, 0) :: es
   257   | forcenext (e :: es) = e :: forcenext es;
   258 
   259 (*es is list of expressions to print;
   260   blockin is the indentation of the current block;
   261   after is the width of the following context until next break.*)
   262 fun format ([], _, _) text = text
   263   | format (e :: es, block as (blockind, blockin), after) (text as {ind, pos, nl, ...}) =
   264       (case e of
   265         Block (markup, bes, indent, wd) =>
   266           let
   267             val {emergencypos, ...} = ! margin_info;
   268             val pos' = pos + indent;
   269             val pos'' = pos' mod emergencypos;
   270             val block' =
   271               if pos' < emergencypos then (ind |> add_indent indent, pos')
   272               else (add_indent pos'' Buffer.empty, pos'');
   273             val (bg, en) = Markup.output markup;
   274             val btext: text = text
   275               |> control bg
   276               |> format (bes, block', breakdist (es, after))
   277               |> control en;
   278             (*if this block was broken then force the next break*)
   279             val es' = if nl < #nl btext then forcenext es else es;
   280           in format (es', block, after) btext end
   281       | String str => format (es, block, after) (string str text)
   282       | Break (force, wd) =>
   283           let val {margin, breakgain, ...} = ! margin_info in
   284             (*no break if text to next break fits on this line
   285               or if breaking would add only breakgain to space*)
   286             format (es, block, after)
   287               (if not force andalso
   288                   pos + wd <= Int.max (margin - breakdist (es, after), blockin + breakgain)
   289                 then text |> blanks wd  (*just insert wd blanks*)
   290                 else text |> newline |> indentation block)
   291           end);
   292 
   293 in
   294 
   295 fun formatted e = #tx (format ([prune e], (Buffer.empty, 0), 0) empty);
   296 
   297 end;
   298 
   299 
   300 (* special output *)
   301 
   302 (*symbolic markup -- no formatting*)
   303 fun symbolic prt =
   304   let
   305     fun out (Block (m, [], _, _)) = Buffer.markup m I
   306       | out (Block (m, prts, indent, _)) =
   307           Buffer.markup m (Buffer.markup (Markup.block indent) (fold out prts))
   308       | out (String (s, _)) = Buffer.add s
   309       | out (Break (false, wd)) = Buffer.markup (Markup.break wd) (Buffer.add (output_spaces wd))
   310       | out (Break (true, _)) = Buffer.markup Markup.fbreak (Buffer.add (output_spaces 1));
   311   in out prt Buffer.empty end;
   312 
   313 (*unformatted output*)
   314 fun unformatted prt =
   315   let
   316     fun fmt (Block (m, prts, _, _)) = Buffer.markup m (fold fmt prts)
   317       | fmt (String (s, _)) = Buffer.add s
   318       | fmt (Break (false, wd)) = Buffer.add (output_spaces wd)
   319       | fmt (Break (true, _)) = Buffer.add (output_spaces 1);
   320   in fmt (prune prt) Buffer.empty end;
   321 
   322 (*ML toplevel pretty printing*)
   323 fun pprint prt (put_str, begin_blk, put_brk, put_fbrk, end_blk) =
   324   let
   325     fun pp (Block (m, prts, ind, _)) =
   326           let val (bg, en) = Markup.output m
   327           in put_str bg; begin_blk ind; pp_lst prts; end_blk (); put_str en end
   328       | pp (String (s, _)) = put_str s
   329       | pp (Break (false, wd)) = put_brk wd
   330       | pp (Break (true, _)) = put_fbrk ()
   331     and pp_lst [] = ()
   332       | pp_lst (prt :: prts) = (pp prt; pp_lst prts);
   333   in pp (prune prt) end;
   334 
   335 
   336 (* output interfaces *)
   337 
   338 val symbolicN = "pretty_symbolic";
   339 
   340 fun output_buffer prt =
   341   if print_mode_active symbolicN then symbolic prt
   342   else formatted prt;
   343 
   344 val output = Buffer.content o output_buffer;
   345 val string_of = Output.escape o output;
   346 val str_of = Output.escape o Buffer.content o unformatted;
   347 val writeln = Output.writeln o string_of;
   348 
   349 
   350 
   351 (** abstract pretty printing context **)
   352 
   353 datatype pp =
   354   PP of (term -> T) * (typ -> T) * (sort -> T) * (class list -> T) * (arity -> T);
   355 
   356 val pp = PP;
   357 
   358 fun pp_fun f (PP x) = f x;
   359 
   360 val term     = pp_fun #1;
   361 val typ      = pp_fun #2;
   362 val sort     = pp_fun #3;
   363 val classrel = pp_fun #4;
   364 val arity    = pp_fun #5;
   365 
   366 val string_of_term     = string_of oo term;
   367 val string_of_typ      = string_of oo typ;
   368 val string_of_sort     = string_of oo sort;
   369 val string_of_classrel = string_of oo classrel;
   370 val string_of_arity    = string_of oo arity;
   371 
   372 end;