1val _ = use "../../tools-poly/prelude.ML";
2val _ = use "../../tools-poly/prelude2.ML";
3val _ = PolyML.print_depth 0;
4
5fun exnMessage (e:exn) =
6  let
7    fun p (e:exn) = PolyML.prettyRepresentation (e, 10000)
8  in
9    PP.pp_to_string 75 p e
10  end
11
12fun die s = (TextIO.output(TextIO.stdErr, s ^ "\n");
13             OS.Process.exit OS.Process.failure)
14fun lnumdie linenum extra exn =
15  die ("Exception raised on line " ^ Int.toString linenum ^ ": "^
16       extra ^ exnMessage exn)
17
18val outputPrompt = ref "> "
19
20val quote = QFRead.fromString
21val default_linewidth = 77
22
23fun quoteFile lnum fname =
24  QFRead.inputFile fname handle e => lnumdie lnum "" e
25
26datatype lbuf =
27         LB of {
28           currentOpt : string option ref,
29           strm : TextIO.instream ,
30           line : int ref
31         }
32
33fun current (LB {currentOpt, ...}) = !currentOpt
34fun linenum (LB {line, ...}) = !line
35fun advance (LB {strm, currentOpt, line}) =
36  (currentOpt := TextIO.inputLine strm ;
37   line := !line + 1)
38
39fun mklbuf strm =
40  let
41    val lb = LB {currentOpt = ref NONE, strm = strm, line = ref 0}
42    val _ = advance lb
43  in
44    lb
45  end
46
47fun mkLex s = let
48  val i = ref 0
49  val sz = size s
50  fun doit () =
51    if !i < sz then SOME (String.sub(s,!i)) before i := !i + 1
52    else NONE
53in
54  doit
55end
56
57fun compiler {push = obufPush, read = obufRD, reset = obufRST} handler infn =
58  let
59    fun pushback c infn =
60        let val used = ref false
61        in
62          fn () => if !used then infn() else (used := true; SOME c)
63        end
64    fun record_error {message,...} = PolyML.prettyPrint(obufPush,70) message
65    fun rpt infn acc =
66      (obufRST();
67       PolyML.compiler(infn,
68                       [PolyML.Compiler.CPErrorMessageProc record_error,
69                        PolyML.Compiler.CPOutStream obufPush]) ()
70       handle e => handler (obufRD()) e;
71       case infn() of
72           NONE => String.concat (List.rev (obufRD() :: acc))
73         | SOME c => rpt (pushback c infn) (obufRD() :: acc)
74      )
75  in
76    rpt infn []
77  end
78
79fun silentUse lnum s =
80  let
81    val filecontents = quoteFile lnum s
82    val buf = HM_SimpleBuffer.mkBuffer()
83  in
84    compiler buf (lnumdie 1) (mkLex filecontents)
85  end
86
87
88fun umunge umap s =
89  let
90    fun recurse acc s =
91      case UTF8.getChar s of
92          NONE => String.concat (List.rev acc)
93        | SOME ((c, _), rest) =>
94          case Binarymap.peek(umap, c) of
95              NONE => recurse (c :: acc) rest
96            | SOME s => recurse (s :: acc) rest
97  in
98    recurse [] s
99  end
100
101val elision_string1 =
102    ref "\\quad\\textit{\\small\\dots{}output elided\\dots{}}\n"
103
104fun deleteTrailingWhiteSpace s =
105  let
106    open Substring
107    val (pfx, sfx) = splitr Char.isSpace (full s)
108    val noNLsfx = dropl (not o equal #"\n") sfx
109    val term = if size noNLsfx = 0 then "" else "\n"
110  in
111    string pfx ^ term
112  end
113
114fun remove_multi_goalproved s =
115  let
116    val ss = Substring.full s
117    val (l,r) = Substring.position "\n\nGoal proved." ss
118    fun recurse ss =
119      let
120        val ss' = Substring.slice(ss, 1, NONE)
121        val (l,r) = Substring.position "\n\nGoal proved." ss'
122      in
123        if Substring.size r <> 0 then
124          case recurse r of NONE => SOME r | x => x
125        else NONE
126      end
127  in
128    if Substring.size r <> 0 then
129      case recurse r of
130          NONE => NONE
131        | SOME r' =>
132          SOME (Substring.string l ^ !elision_string1 ^
133                Substring.string (Substring.triml 1 r'))
134    else
135      NONE
136  end
137
138fun cruftSuffix sfxs s =
139  case List.find (fn (sfx,_) => String.isSuffix sfx s) sfxs of
140      NONE => NONE
141    | SOME (sfx,rep) => SOME (String.substring(s, 0, size s - size sfx) ^ rep)
142
143val cruftySuffixes = ref [
144      ("\n   : proofs\n", "\n"),
145      ("\n   : proof\n", "\n"),
146      (":\n   proof\n", "\n")
147    ]
148
149fun try _ [] s = s
150  | try restart (f::fs) s = case f s of
151                                SOME s' => restart s'
152                              | NONE => try restart fs s
153
154fun remove_nsubgoals s =
155  let
156    open Substring
157    val ss0 = full s
158    val (ss,_) = splitr Char.isSpace ss0
159  in
160    if isSuffix "subgoals" ss then
161      let
162        val (p,s) = splitr (fn c => c <> #"\n") ss
163        val (sn, rest) = splitl Char.isDigit s
164      in
165        if size sn <> 0 andalso string rest = " subgoals" then SOME (string p)
166        else NONE
167      end
168    else NONE
169  end
170
171fun delete_suffixNL sfx s =
172  if String.isSuffix (sfx ^ "\n") s then
173    String.extract(s, 0, SOME (size s - (size sfx + 1))) ^ "\n"
174        |> deleteTrailingWhiteSpace
175  else s
176
177fun remove_colonthm s =
178  s |> delete_suffixNL "thm" |> delete_suffixNL ":"
179
180fun shorten_longmetis s =
181  let
182    open Substring
183    val ss = full s
184    val (pfx, sfx) = position "\nmetis: " ss
185    fun ismetis_guff c =
186      case c of
187          #"r" => true | #"+" => true | #"[" => true | #"]" => true
188        | _ => Char.isDigit c
189    fun recurse csfx =
190      if size csfx <> 0 then
191        let
192          val (guff, rest) = splitl ismetis_guff (triml 8 csfx)
193        in
194          if size guff > 55 then
195            SOME (string (span (pfx, slice(guff, 0, SOME 50))) ^ " .... " ^
196                  string rest)
197          else
198            let
199              val (_, sfx') = position "\nmetis: " (triml 1 csfx)
200            in
201              recurse sfx'
202            end
203        end
204      else NONE
205  in
206    recurse sfx
207  end
208
209fun removeCruft s =
210  try removeCruft [cruftSuffix (!cruftySuffixes),
211                   remove_multi_goalproved,
212                   shorten_longmetis,
213                   remove_nsubgoals]
214      s
215
216fun addIndent ws = String.translate(fn #"\n" => "\n"^ws | c => str c)
217
218val linelen_limit = ref (NONE : int option)
219
220fun trunc lim s =
221    if UTF8.size s <= lim then s
222    else if lim > 3 then UTF8.substring(s,0, lim - 3) ^ "..."
223    else UTF8.substring(s,0,lim)
224
225fun impose_linelen_limit s =
226    case !linelen_limit of
227        NONE => s
228      | SOME lim =>
229        let
230          val lines = String.fields (fn c => c = #"\n") s
231        in
232          String.concatWith "\n" (map (trunc lim) lines)
233        end
234
235fun transformOutput umap ws s =
236  s |> umunge umap
237    |> removeCruft
238    |> addIndent ws
239    |> deleteTrailingWhiteSpace
240    |> (fn s => ws ^ s)
241    |> impose_linelen_limit
242
243fun spaceNotNL c = Char.isSpace c andalso c <> #"\n"
244
245val getIndent =
246  (Substring.string ## Substring.string)
247    o Substring.splitl spaceNotNL o Substring.full
248
249fun dropLWS s =
250  s |> Substring.full |> Substring.dropl Char.isSpace |> Substring.string
251
252fun strip_for_thm s =
253  let
254    val s0 = if String.isPrefix "val it =" s then
255               String.extract(s, 9, NONE) |> dropLWS
256             else s
257  in
258    remove_colonthm s0
259  end
260
261fun tailmap f [] = []
262  | tailmap f (h::t) = h :: map f t
263
264fun poss_space_extract n s =
265    let val s = String.extract(s,n,NONE)
266    in
267      if s <> "" andalso String.sub(s,0) = #" " then
268        (String.extract(s, 1, NONE), n + 1)
269      else (s, n)
270    end
271
272fun strcat s1 s2 = s1 ^ s2
273
274fun dropWhile0 P a [] = (List.rev a,[])
275  | dropWhile0 P a (l as h::t) = if P h then dropWhile0 P (h::a) t
276                                 else (List.rev a, l)
277fun dropWhile P l = dropWhile0 P [] l
278
279fun process_line debugp umap obuf origline lbuf = let
280  val {reset = obRST, ...} = obuf
281  val (ws,line) = getIndent origline
282  val indent = String.size ws
283  val oPsize = size (!outputPrompt)
284  val oPws = CharVector.tabulate(oPsize, fn _ => #" ")
285  fun getRest userPromptSize acc =
286    let
287      val _ = advance lbuf
288      (* know that we're being called on something with > indent many space
289         characters at the start of the line; that number is wssz *)
290      fun handlePromptSize wssz line =
291          (* strip indent many characters from line first *)
292          let
293            val line = String.extract(line, indent, NONE)
294            val remws = wssz - indent
295            val tostrip = Int.min(remws, userPromptSize)
296          in
297            String.extract(line, tostrip, NONE)
298          end
299      fun extract_trailing_blanklines l =
300          let
301            val (blanks, rest) = dropWhile (CharVector.all Char.isSpace) l
302          in
303            (List.rev rest, blanks |> List.rev |> String.concat)
304          end
305    in
306      case current lbuf of
307          NONE => extract_trailing_blanklines acc
308        | SOME s =>
309          let
310            val (ws',_) = getIndent s
311            val wssz = String.size ws'
312          in
313            if indent < wssz then
314              getRest userPromptSize (handlePromptSize wssz s::acc)
315            else if CharVector.all Char.isSpace s then
316              getRest userPromptSize ("\n" :: acc)
317            else extract_trailing_blanklines acc
318          end
319    end
320  val assertcmd = "##assert "
321  val assertcmdsz = size assertcmd
322  val stringCReader = #read o QFRead.stringToReader true
323  fun compile exnhandle input =
324      (if debugp then
325         TextIO.output(TextIO.stdErr, input)
326       else ();
327       compiler obuf exnhandle (stringCReader input))
328in
329  if String.isPrefix ">>>" line then
330    (advance lbuf; (ws ^ String.extract(line, 1, NONE), ""))
331  else if String.isPrefix "###" line then
332    (advance lbuf; (ws ^ String.extract(line, 1, NONE), ""))
333  else if String.isPrefix assertcmd line then
334    let
335      val e = String.substring(line, assertcmdsz, size line - assertcmdsz - 1)
336                              (* for \n at end *)
337      val _ = compile (lnumdie (linenum lbuf))
338                      ("val _ = if (" ^ e ^ ") then () " ^
339                       "else die \"Assertion failed: line " ^
340                       Int.toString (linenum lbuf) ^ "\";\n")
341      val _ = advance lbuf
342    in
343      ("", "")
344    end
345  else if String.isPrefix "##thm" line then
346    let
347      val suffix = String.extract(line, 5, NONE)
348      val ((guffp,guffs), thm_name) =
349          if Char.isDigit (String.sub(suffix,0)) then
350            let
351              val (w,nm) =
352                  case String.tokens Char.isSpace suffix of
353                      [ds,nm] => ((valOf (Int.fromString ds), nm)
354                                  handle Option =>
355                                         lnumdie (linenum lbuf)
356                                                 "Bad integer for linewidth"
357                                                 Option)
358                    | _ => lnumdie (linenum lbuf)
359                                   "Malformed ##thm line"
360                                   (Fail "")
361            in
362              (("val _ = linewidth := " ^ Int.toString w ^"; ",
363                "val _ = linewidth := " ^ Int.toString default_linewidth ^ ";"),
364               nm ^ "\n")
365            end
366          else (("",""), dropLWS suffix)
367      val raw_output =
368          compile (lnumdie (linenum lbuf))
369                  (guffp ^ thm_name ^ " :Thm.thm;" ^ guffs)
370      val output = transformOutput umap ws (strip_for_thm raw_output)
371                        |> deleteTrailingWhiteSpace
372                        |> (fn s => "  " ^ s)
373      val _ = advance lbuf
374    in
375      (ws ^ umunge umap thm_name, output)
376    end
377  else if String.isPrefix "##eval" line then
378    let
379      val line = String.extract(line, 6, NONE)
380      val e = Fail ""
381      val (inputpfx, firstline, indent) =
382          if String.isPrefix "[" line then
383            let
384              val ss = Substring.full line
385              val (p,s) = Substring.position "] " ss
386              val _ = Substring.size s <> 0 orelse
387                      lnumdie (linenum lbuf) "Mal-formed ##eval directive" e
388              val vname = String.extract(Substring.string p, 1, NONE)
389              val firstline = String.extract(Substring.string s, 2, NONE)
390            in
391              ("val "^vname^" = ", firstline, 9 + size vname)
392            end
393          else
394            ("", String.extract(line, 1, NONE), 7)
395              handle Subscript =>
396                     lnumdie (linenum lbuf) "Mal-formed ##eval directive" e
397      val (input, blankstr) = getRest indent [firstline]
398      val input = input |> map (fn s => ws ^ s) |> String.concat
399      val _ = compile (lnumdie (linenum lbuf)) (inputpfx ^ input)
400    in
401      (umunge umap input, blankstr)
402    end
403  else if String.isPrefix "##parse" line then
404    let
405      val line = String.extract(line, 7, NONE)
406      val pfx =
407          if String.isPrefix "tm " line then ""
408          else if String.isPrefix "ty " line then ":"
409          else lnumdie (linenum lbuf) "Mal-formed ##parse directive" (Fail "")
410      val (firstline, indent) = (String.extract(line, 3, NONE), 10)
411      val (input, blankstr) = getRest indent [firstline]
412      val input = input |> map (fn s => ws ^ s) |> String.concat
413      val _ = compile (lnumdie (linenum lbuf)) ("``" ^ pfx ^ input ^ "``")
414    in
415      (umunge umap input, blankstr)
416    end
417  else if String.isPrefix "##use " line then
418    let
419      val fname = String.substring(line, 6, size line - 7) (* for \n at end *)
420      val _ = silentUse (linenum lbuf) fname
421      val _ = advance lbuf
422    in
423      ("", "")
424    end
425  else if String.isPrefix "##linelen_limit " line then
426    case Int.fromString (String.extract(line, 16, NONE)) of
427        NONE => lnumdie (linenum lbuf) "Mal-formed ##linelen_limit directive"
428                        (Fail "")
429      | SOME i => if i >= 10 then
430                    (advance lbuf; linelen_limit := SOME i; ("", ""))
431                  else
432                    lnumdie (linenum lbuf)
433                            "Unreasonable (<10) ##linelen_limit directive"
434                            (Fail "")
435  else if String.isPrefix "##nolinelen_limit" line then
436    (advance lbuf; linelen_limit := NONE; ("", ""))
437  else if String.isPrefix ">>-" line then
438    let
439      val (firstline,d) = poss_space_extract 3 line
440      val (input, blankstr) = getRest d [firstline]
441      val raw_output = compile (lnumdie (linenum lbuf)) (String.concat input)
442    in
443      ("", transformOutput umap ws raw_output ^ blankstr)
444    end
445  else if String.isPrefix ">>__" line then
446    let
447      val (firstline,d) = poss_space_extract 4 line
448      val (input, blankstr) = getRest d [firstline]
449      val _ = compile (lnumdie (linenum lbuf)) (String.concat input)
450    in
451      ("", blankstr)
452    end
453  else if String.isPrefix ">>_" line then
454    let
455      val (firstline,d) = poss_space_extract 3 line
456      val (input, blankstr) = getRest d [firstline]
457      val inp_to_print = input |> tailmap (strcat (oPws ^ ws)) |> String.concat
458      val _ = compile (lnumdie (linenum lbuf)) (String.concat input)
459      fun removeNL s = String.substring(s, 0, size s - 1)
460    in
461      (ws ^ !outputPrompt ^ removeNL (umunge umap inp_to_print),
462       !elision_string1 ^ blankstr)
463    end
464  else if String.isPrefix ">>+" line then
465    let
466      val (firstline,d) = poss_space_extract 3 line
467      val (input,blankstr) = getRest d [firstline]
468      val inp_to_print = input |> tailmap (strcat (oPws ^ ws)) |> String.concat
469      fun handle_exn extra exn = raise Fail (extra ^ exnMessage exn)
470      val raw_output = compile handle_exn (String.concat input)
471                       handle Fail s => "Exception- " ^ s ^ " raised\n"
472    in
473      (ws ^ !outputPrompt ^ umunge umap inp_to_print,
474       transformOutput umap ws raw_output ^ blankstr)
475    end
476  else if String.isPrefix ">>" line then
477    let
478      val _ = obRST()
479      val (firstline, d) = poss_space_extract 2 line
480      val (input,blankstr) = getRest d [firstline]
481      val inp_to_print = input |> tailmap (strcat (oPws ^ ws)) |> String.concat
482      val raw_output = compile (lnumdie (linenum lbuf)) (String.concat input)
483    in
484      (ws ^ !outputPrompt ^ umunge umap inp_to_print,
485       transformOutput umap ws raw_output ^ blankstr)
486    end
487  else
488    (advance lbuf; (origline, ""))
489end
490
491fun read_umap fname =
492  let
493    val instrm = TextIO.openIn fname handle _ => die ("Couldn't open "^fname)
494    fun recurse (i, acc) =
495      case TextIO.inputLine instrm of
496          SOME line =>
497          (if size line <= 1 then recurse (i + 1, acc)
498           else
499             case UTF8.getChar line of
500                NONE => die ("read_umap: should never happen")
501              | SOME ((cstr, _), rest) =>
502                let
503                  val restss = Substring.full rest
504                  val range =
505                      restss |> Substring.dropl Char.isSpace
506                             |> Substring.dropr Char.isSpace
507                             |> Substring.string
508                in
509                  recurse(i + 1, Binarymap.insert(acc, cstr, range))
510                end)
511        | NONE => acc
512  in
513    recurse(1, Binarymap.mkDict String.compare)
514  end
515
516
517fun usage() =
518  "Usage:\n  "^ CommandLine.name() ^ " [-d] [umapfile]"
519
520fun main () =
521  let
522    val _ = PolyML.print_depth 100;
523    val _ = Parse.current_backend := PPBackEnd.raw_terminal
524    val (debugp,umap) =
525        case CommandLine.arguments() of
526            [] => (false, Binarymap.mkDict String.compare)
527          | ["-d"] => (true, Binarymap.mkDict String.compare)
528          | [name] => (false, read_umap name)
529          | ["-d", name] => (true, read_umap name)
530          | _ => die (usage())
531    val (obuf as {push = obPush, ...}) = HM_SimpleBuffer.mkBuffer()
532    val _ = ReadHMF.extend_path_with_includes {verbosity = 0, lpref = loadPath}
533    val _ = Feedback.ERR_outstream := obPush
534    val _ = Feedback.WARNING_outstream := obPush
535    val _ = Feedback.MESG_outstream := obPush
536    val cvn = PolyML.Compiler.compilerVersionNumber
537    val _ = if cvn = 551 orelse cvn = 552 then
538              ignore (TextIO.inputLine TextIO.stdIn)
539            else ()
540    val lb = mklbuf TextIO.stdIn
541    fun recurse lb : unit=
542      case current lb of
543          NONE => ()
544        | SOME line =>
545          let
546            val (i, output) = process_line debugp umap obuf line lb
547               handle e => die ("Untrapped exception: line "^
548                                Int.toString (linenum lb) ^ ": " ^
549                                exnMessage e)
550          in
551            print (i ^ output);
552            recurse lb
553          end
554  in
555    recurse lb
556  end
557