src/Pure/General/pretty.ML
author wenzelm
Sat, 07 Jul 2007 18:39:18 +0200
changeset 23638 09120c2dd71f
parent 23628 41cdbfb9f77b
child 23645 d220d12bd45e
permissions -rw-r--r--
added markup_chunks;
     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 = (string -> unit) * (int -> unit) * (int -> unit) *
    24   (unit -> unit) * (unit -> unit);
    25 
    26 signature PRETTY =
    27 sig
    28   val default_indent: string -> int -> string
    29   val default_markup: Markup.T -> string * string
    30   val add_mode: string -> (string -> int -> string) -> (Markup.T -> string * string) -> unit
    31   type T
    32   val raw_str: string * int -> T
    33   val str: string -> T
    34   val brk: int -> T
    35   val fbrk: T
    36   val blk: int * T list -> T
    37   val unbreakable: T -> T
    38   val breaks: T list -> T list
    39   val fbreaks: T list -> T list
    40   val block: T list -> T
    41   val markup: Markup.T -> T list -> T
    42   val keyword: string -> T
    43   val command: string -> T
    44   val strs: string list -> T
    45   val markup_chunks: Markup.T -> T list -> T
    46   val chunks: T list -> T
    47   val chunks2: T list -> T
    48   val block_enclose: T * T -> T list -> T
    49   val quote: T -> T
    50   val backquote: T -> T
    51   val separate: string -> T list -> T list
    52   val commas: T list -> T list
    53   val enclose: string -> string -> T list -> T
    54   val enum: string -> string -> string -> T list -> T
    55   val list: string -> string -> T list -> T
    56   val str_list: string -> string -> string list -> T
    57   val big_list: string -> T list -> T
    58   val indent: int -> T -> T
    59   val string_of: T -> string
    60   val output_buffer: T -> Buffer.T
    61   val output: T -> string
    62   val writeln: T -> unit
    63   val str_of: T -> string
    64   val pprint: T -> pprint_args -> unit
    65   val setdepth: int -> unit
    66   val setmargin: int -> unit
    67   val setmp_margin: int -> ('a -> 'b) -> 'a -> 'b
    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 fun default_markup (_: Markup.T) = ("", "");
    89 
    90 local
    91   val default = {indent = default_indent, markup = default_markup};
    92   val modes = ref (Symtab.make [("", default)]);
    93 in
    94   fun add_mode name indent markup =
    95     change modes (Symtab.update_new (name, {indent = indent, markup = markup}));
    96   fun get_mode () =
    97     the_default default (Library.get_first (Symtab.lookup (! modes)) (! print_mode));
    98 end;
    99 
   100 fun mode_indent x y = #indent (get_mode ()) x y;
   101 fun mode_markup x = #markup (get_mode ()) x;
   102 
   103 
   104 
   105 (** printing items: compound phrases, strings, and breaks **)
   106 
   107 datatype T =
   108   Block of Markup.T * T list * int * int |  (*markup, body, indentation, length*)
   109   String of string * int |                  (*text, length*)
   110   Break of bool * int;                      (*mandatory flag, width if not taken*)
   111 
   112 
   113 
   114 (** output text **)
   115 
   116 val output_spaces = Output.output o Symbol.spaces;
   117 val add_indent = Buffer.add o output_spaces;
   118 fun set_indent wd = Buffer.empty |> add_indent wd;
   119 
   120 type text = {tx: Buffer.T, ind: Buffer.T, pos: int, nl: int};
   121 
   122 val empty: text =
   123  {tx = Buffer.empty,
   124   ind = Buffer.empty,
   125   pos = 0,
   126   nl = 0};
   127 
   128 fun newline {tx, ind, pos, nl} : text =
   129  {tx = Buffer.add (Output.output "\n") tx,
   130   ind = Buffer.empty,
   131   pos = 0,
   132   nl = nl + 1};
   133 
   134 fun control s {tx, ind, pos: int, nl} : text =
   135  {tx = Buffer.add s tx,
   136   ind = ind,
   137   pos = pos,
   138   nl = nl};
   139 
   140 fun string (s, len) {tx, ind, pos: int, nl} : text =
   141  {tx = Buffer.add s tx,
   142   ind = Buffer.add s ind,
   143   pos = pos + len,
   144   nl = nl};
   145 
   146 fun blanks wd = string (output_spaces wd, wd);
   147 
   148 fun indentation (buf, len) {tx, ind, pos, nl} : text =
   149   let val s = Buffer.content buf in
   150    {tx = Buffer.add (mode_indent s len) tx,
   151     ind = Buffer.add s ind,
   152     pos = pos + len,
   153     nl = nl}
   154   end;
   155 
   156 
   157 
   158 (** formatting **)
   159 
   160 (* margin *)
   161 
   162 fun make_margin_info m =
   163  {margin = m,                   (*right margin, or page width*)
   164   breakgain = m div 20,         (*minimum added space required of a break*)
   165   emergencypos = m div 2};      (*position too far to right*)
   166 
   167 val margin_info = ref (make_margin_info 76);
   168 fun setmargin m = margin_info := make_margin_info m;
   169 fun setmp_margin m f = setmp margin_info (make_margin_info m) f;
   170 
   171 
   172 (* format *)
   173 
   174 (*Add the lengths of the expressions until the next Break; if no Break then
   175   include "after", to account for text following this block.*)
   176 fun breakdist (Block (_, _, _, len) :: es, after) = len + breakdist (es, after)
   177   | breakdist (String (s, len) :: es, after) = len + breakdist (es, after)
   178   | breakdist (Break _ :: es, after) = 0
   179   | breakdist ([], after) = after;
   180 
   181 (*Search for the next break (at this or higher levels) and force it to occur.*)
   182 fun forcenext [] = []
   183   | forcenext (Break (_, wd) :: es) = Break (true, 0) :: es
   184   | forcenext (e :: es) = e :: forcenext es;
   185 
   186 (*es is list of expressions to print;
   187   blockin is the indentation of the current block;
   188   after is the width of the following context until next break.*)
   189 fun format ([], _, _) text = text
   190   | format (e :: es, block as (blockind, blockin), after) (text as {ind, pos, nl, ...}) =
   191       (case e of
   192         Block (markup, bes, indent, wd) =>
   193           let
   194             val {emergencypos, ...} = ! margin_info;
   195             val pos' = pos + indent;
   196             val pos'' = pos' mod emergencypos;
   197             val block' =
   198               if pos' < emergencypos then (ind |> add_indent indent, pos')
   199               else (set_indent pos'', pos'');
   200             val (bg, en) = if markup = Markup.none then ("", "") else mode_markup markup;
   201             val btext: text = text
   202               |> control bg
   203               |> format (bes, block', breakdist (es, after))
   204               |> control en;
   205             (*if this block was broken then force the next break*)
   206             val es' = if nl < #nl btext then forcenext es else es;
   207           in format (es', block, after) btext end
   208       | String str => format (es, block, after) (string str text)
   209       | Break (force, wd) =>
   210           let val {margin, breakgain, ...} = ! margin_info in
   211             (*no break if text to next break fits on this line
   212               or if breaking would add only breakgain to space*)
   213             format (es, block, after)
   214               (if not force andalso
   215                   pos + wd <= Int.max (margin - breakdist (es, after), blockin + breakgain)
   216                 then text |> blanks wd  (*just insert wd blanks*)
   217                 else text |> newline |> indentation block)
   218           end);
   219 
   220 
   221 (** Exported functions to create formatting expressions **)
   222 
   223 fun length (Block (_, _, _, len)) = len
   224   | length (String (_, len)) = len
   225   | length (Break(_, wd)) = wd;
   226 
   227 val raw_str = String;
   228 val str = String o Output.output_width;
   229 
   230 fun brk wd = Break (false, wd);
   231 val fbrk = Break (true, 2);
   232 
   233 fun markup_block m (indent, es) =
   234   let
   235     fun sum [] k = k
   236       | sum (e :: es) k = sum es (length e + k);
   237   in Block (m, es, indent, sum es 0) end;
   238 
   239 val blk = markup_block Markup.none;
   240 
   241 fun unbreakable (Break (_, wd)) = String (output_spaces wd, wd)
   242   | unbreakable (Block (m, es, indent, wd)) = Block (m, map unbreakable es, indent, wd)
   243   | unbreakable (e as String _) = e;
   244 
   245 
   246 (* utils *)
   247 
   248 fun breaks prts = Library.separate (brk 1) prts;
   249 fun fbreaks prts = Library.separate fbrk prts;
   250 
   251 fun block prts = blk (2, prts);
   252 fun markup m prts = markup_block m (0, prts);
   253 
   254 fun keyword name = markup (Markup.keyword name) [str name];
   255 fun command name = markup (Markup.command name) [str name];
   256 
   257 val strs = block o breaks o map str;
   258 
   259 fun markup_chunks m prts = markup m (fbreaks prts);
   260 val chunks = markup_chunks Markup.none;
   261 
   262 fun chunks2 prts = blk (0, flat (Library.separate [fbrk, fbrk] (map single prts)));
   263 fun block_enclose (p1, p2) ps = chunks [(block  o fbreaks) (p1 :: ps), p2];
   264 
   265 fun quote prt = blk (1, [str "\"", prt, str "\""]);
   266 fun backquote prt = blk (1, [str "`", prt, str "`"]);
   267 
   268 fun separate sep prts =
   269   flat (Library.separate [str sep, brk 1] (map single prts));
   270 
   271 val commas = separate ",";
   272 
   273 fun enclose lpar rpar prts =
   274   block (str lpar :: (prts @ [str rpar]));
   275 
   276 fun enum sep lpar rpar prts = enclose lpar rpar (separate sep prts);
   277 
   278 val list = enum ",";
   279 fun str_list lpar rpar strs = list lpar rpar (map str strs);
   280 
   281 fun big_list name prts = block (fbreaks (str name :: prts));
   282 
   283 fun indent 0 prt = prt
   284   | indent n prt = blk (0, [str (Symbol.spaces n), prt]);
   285 
   286 
   287 
   288 (** Pretty printing with depth limitation **)
   289 
   290 val depth       = ref 0;        (*maximum depth; 0 means no limit*)
   291 
   292 fun setdepth dp = (depth := dp);
   293 
   294 (*Recursively prune blocks, discarding all text exceeding depth dp*)
   295 fun pruning dp (Block (m, bes, indent, wd)) =
   296       if dp > 0
   297       then markup_block m (indent, map (pruning (dp - 1)) bes)
   298       else str "..."
   299   | pruning dp e = e;
   300 
   301 fun prune dp e = if dp > 0 then pruning dp e else e;
   302 
   303 fun output_buffer e = #tx (format ([prune (! depth) e], (Buffer.empty, 0), 0) empty);
   304 val output = Buffer.content o output_buffer;
   305 val string_of = Output.escape o output;
   306 val writeln = Output.writeln o string_of;
   307 
   308 
   309 (*Create a single flat string: no line breaking*)
   310 fun str_of prt =
   311   let
   312     fun fmt (Block (m, prts, _, _)) =
   313           let val (bg, en) = mode_markup m
   314           in Buffer.add bg #> fold fmt prts #> Buffer.add en end
   315       | fmt (String (s, _)) = Buffer.add s
   316       | fmt (Break (false, wd)) = Buffer.add (output_spaces wd)
   317       | fmt (Break (true, _)) = Buffer.add (output_spaces 1);
   318   in Output.escape (Buffer.content (fmt (prune (! depth) prt) Buffer.empty)) end;
   319 
   320 (*part of the interface to the ML toplevel pretty printers*)
   321 fun pprint prt (put_str, begin_blk, put_brk, put_fbrk, end_blk) =
   322   let
   323     fun pp (Block (_, prts, ind, _)) = (begin_blk ind; pp_lst prts; end_blk ())
   324       | pp (String (s, _)) = put_str s
   325       | pp (Break (false, wd)) = put_brk wd
   326       | pp (Break (true, _)) = put_fbrk ()
   327     and pp_lst [] = ()
   328       | pp_lst (prt :: prts) = (pp prt; pp_lst prts);
   329   in pp (prune (! depth) prt) end;
   330 
   331 
   332 
   333 (** abstract pretty printing context **)
   334 
   335 datatype pp =
   336   PP of (term -> T) * (typ -> T) * (sort -> T) * (class list -> T) * (arity -> T);
   337 
   338 val pp = PP;
   339 
   340 fun pp_fun f (PP x) = f x;
   341 
   342 val term     = pp_fun #1;
   343 val typ      = pp_fun #2;
   344 val sort     = pp_fun #3;
   345 val classrel = pp_fun #4;
   346 val arity    = pp_fun #5;
   347 
   348 val string_of_term     = string_of oo term;
   349 val string_of_typ      = string_of oo typ;
   350 val string_of_sort     = string_of oo sort;
   351 val string_of_classrel = string_of oo classrel;
   352 val string_of_arity    = string_of oo arity;
   353 
   354 end;