# HG changeset patch # User wenzelm # Date 1513247066 -3600 # Node ID 694f29a5433b19128594f9469435dcbe9983792a # Parent 2f213405cc0eefee5e2b29f7cb223aa39a3690ce# Parent b335e255ebcc94fd5058f44eb1ed4d0a53059116 merged diff -r 2f213405cc0e -r 694f29a5433b NEWS --- a/NEWS Wed Dec 13 19:28:19 2017 +0100 +++ b/NEWS Thu Dec 14 11:24:26 2017 +0100 @@ -86,6 +86,10 @@ * Document preparation with skip_proofs option now preserves the content more accurately: only terminal proof steps ('by' etc.) are skipped. +* Command-line tool "isabelle document" has been re-implemented in +Isabelle/Scala, with simplified arguments and explicit errors from the +latex process. Minor INCOMPATIBILITY. + *** HOL *** diff -r 2f213405cc0e -r 694f29a5433b etc/options diff -r 2f213405cc0e -r 694f29a5433b etc/settings --- a/etc/settings Wed Dec 13 19:28:19 2017 +0100 +++ b/etc/settings Thu Dec 14 11:24:26 2017 +0100 @@ -46,12 +46,17 @@ ### Document preparation (cf. isabelle latex/document) ### -ISABELLE_LATEX="latex" -ISABELLE_PDFLATEX="pdflatex" +ISABELLE_LATEX="latex -file-line-error" +ISABELLE_PDFLATEX="pdflatex -file-line-error" ISABELLE_BIBTEX="bibtex" ISABELLE_MAKEINDEX="makeindex" ISABELLE_EPSTOPDF="epstopdf" +if [ "$ISABELLE_PLATFORM_FAMILY" = "windows" ]; then + ISABELLE_LATEX="latex -c-style-errors" + ISABELLE_PDFLATEX="pdflatex -c-style-errors" +fi + ### ### Misc path settings diff -r 2f213405cc0e -r 694f29a5433b lib/Tools/document --- a/lib/Tools/document Wed Dec 13 19:28:19 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ -#!/usr/bin/env bash -# -# Author: Markus Wenzel, TU Muenchen -# -# DESCRIPTION: prepare theory session document - - -PRG="$(basename "$0")" - -function usage() -{ - echo - echo "Usage: isabelle $PRG [OPTIONS] [DIR]" - echo - echo " Options are:" - echo " -c cleanup -- be aggressive in removing old stuff" - echo " -n NAME specify document name (default 'document')" - echo " -o FORMAT specify output format: pdf (default), dvi" - echo " -t TAGS specify tagged region markup" - echo - echo " Prepare the theory session document in DIR (default 'document')" - echo " producing the specified output format." - echo - exit 1 -} - -function fail() -{ - echo "$1" >&2 - exit 2 -} - - -## process command line - -# options - -CLEAN="" -NAME="document" -OUTFORMAT=pdf -declare -a TAGS=() - -while getopts "cn:o:t:" OPT -do - case "$OPT" in - c) - CLEAN=true - ;; - n) - NAME="$OPTARG" - ;; - o) - OUTFORMAT="$OPTARG" - ;; - t) - splitarray "," "$OPTARG"; TAGS=("${SPLITARRAY[@]}") - ;; - \?) - usage - ;; - esac -done - -shift $(($OPTIND - 1)) - - -# args - -DIR="document" -[ "$#" -ge 1 ] && { DIR="$1"; shift; } - -[ "$#" -ne 0 ] && usage - - -## main - -# check format - -case "$OUTFORMAT" in - pdf | dvi) - ;; - *) - fail "Bad output format '$OUTFORMAT'" - ;; -esac - - -# document variants - -ROOT_NAME="root_$NAME" -[ ! -f "$DIR/$ROOT_NAME.tex" ] && ROOT_NAME="root" - -function prep_tags () -{ - ( - for TAG in "${TAGS[@]}" - do - case "$TAG" in - /*) - echo "\\isafoldtag{${TAG:1}}" - ;; - -*) - echo "\\isadroptag{${TAG:1}}" - ;; - +*) - echo "\\isakeeptag{${TAG:1}}" - ;; - *) - echo "\\isakeeptag{${TAG}}" - ;; - esac - done - echo - ) > isabelletags.sty -} - - -# prepare document - -( - cd "$DIR" || fail "Bad directory '$DIR'" - - [ -n "$CLEAN" ] && rm -f "../$NAME.$OUTFORMAT" *.aux *.out *.ind *.idx *.ilg *.bbl *.blg *.log - - prep_tags - - if [ -f build ]; then - ./build "$OUTFORMAT" "$NAME" - RC="$?" - else - isabelle latex -o sty "$ROOT_NAME.tex" && \ - isabelle latex -o "$OUTFORMAT" "$ROOT_NAME.tex" && \ - { [ ! -f "$ROOT_NAME.bib" ] || isabelle latex -o bbl "$ROOT_NAME.tex"; } && \ - { [ ! -f "$ROOT_NAME.idx" ] || isabelle latex -o idx "$ROOT_NAME.tex"; } && \ - isabelle latex -o "$OUTFORMAT" "$ROOT_NAME.tex" && \ - isabelle latex -o "$OUTFORMAT" "$ROOT_NAME.tex" - RC="$?" - fi - - if [ "$RC" -eq 0 -a -f "$ROOT_NAME.$OUTFORMAT" ]; then - cp -f "$ROOT_NAME.$OUTFORMAT" "../$NAME.$OUTFORMAT" - fi - - exit "$RC" -) -RC="$?" - - -# install - -[ "$RC" -ne 0 ] && fail "Document preparation failure in directory '$DIR'" - -#beware! -[ -n "$CLEAN" ] && rm -rf "$DIR" - -exit "$RC" diff -r 2f213405cc0e -r 694f29a5433b src/Doc/System/Presentation.thy --- a/src/Doc/System/Presentation.thy Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Doc/System/Presentation.thy Thu Dec 14 11:24:26 2017 +0100 @@ -148,16 +148,17 @@ The @{tool_def document} tool prepares logic session documents, processing the sources as provided by the user and generated by Isabelle. Its usage is: @{verbatim [display] -\Usage: isabelle document [OPTIONS] [DIR] +\Usage: isabelle document [OPTIONS] Options are: - -c cleanup -- be aggressive in removing old stuff - -n NAME specify document name (default 'document') - -o FORMAT specify output format: pdf (default), dvi - -t TAGS specify tagged region markup + -d DIR document output directory (default "output/document") + -n NAME document name (default "document") + -o FORMAT document format: pdf (default), dvi + -t TAGS markup for tagged regions - Prepare the theory session document in DIR (default 'document') - producing the specified output format.\} + Prepare the theory session document in document directory, producing the + specified output format. +\} This tool is usually run automatically as part of the Isabelle build process, provided document preparation has been enabled via suitable @@ -166,15 +167,13 @@ run. \<^medskip> - The \<^verbatim>\-c\ option tells @{tool document} to dispose the document sources - after successful operation! This is the right thing to do for sources - generated by an Isabelle process, but take care of your files in manual - document preparation! + Option \<^verbatim>\-d\ specifies an laternative document output directory. The default + is \<^verbatim>\output/document\ (derived from the document name). Note that the result + will appear in the parent of this directory. \<^medskip> The \<^verbatim>\-n\ and \<^verbatim>\-o\ option specify the final output file name and format, - the default is ``\<^verbatim>\document.dvi\''. Note that the result will appear in the - parent of the target \<^verbatim>\DIR\. + the default is ``\<^verbatim>\document.pdf\''. \<^medskip> The \<^verbatim>\-t\ option tells {\LaTeX} how to interpret tagged Isabelle command @@ -234,10 +233,10 @@ we recommend to include \<^file>\~~/lib/texinputs/pdfsetup.sty\ as well. \<^medskip> - As a final step of Isabelle document preparation, @{tool document}~\<^verbatim>\-c\ is - run on the resulting copy of the \<^verbatim>\document\ directory. Thus the actual - output document is built and installed in its proper place. The generated - sources are deleted after successful run of {\LaTeX} and friends. + As a final step of Isabelle document preparation, @{tool document} is run on + the generated \<^verbatim>\output/document\ directory. Thus the actual output document + is built and installed in its proper place. The generated sources are + deleted after successful run of {\LaTeX} and friends. Some care is needed if the document output location is configured differently, say within a directory whose content is still required diff -r 2f213405cc0e -r 694f29a5433b src/Pure/Admin/isabelle_cronjob.scala --- a/src/Pure/Admin/isabelle_cronjob.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/Admin/isabelle_cronjob.scala Thu Dec 14 11:24:26 2017 +0100 @@ -346,7 +346,7 @@ res match { case Exn.Res(_) => None case Exn.Exn(exn) => - System.err.println("Exception trace for " + quote(task.name) + ":") + Output.writeln("Exception trace for " + quote(task.name) + ":") exn.printStackTrace() val first_line = split_lines(Exn.message(exn)).headOption getOrElse "exception" Some(first_line) diff -r 2f213405cc0e -r 694f29a5433b src/Pure/General/antiquote.ML --- a/src/Pure/General/antiquote.ML Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/General/antiquote.ML Thu Dec 14 11:24:26 2017 +0100 @@ -76,11 +76,15 @@ val err_prefix = "Antiquotation lexical error: "; +val scan_nl = Scan.one (fn (s, _) => s = "\n") >> single; + val scan_txt = + scan_nl || Scan.repeats1 (Scan.many1 (fn (s, _) => - not (Symbol.is_control s) andalso s <> Symbol.open_ andalso s <> "@" andalso Symbol.not_eof s) || - $$$ "@" --| Scan.ahead (~$$ "{")); + not (Symbol.is_control s) andalso s <> Symbol.open_ andalso s <> "@" andalso + s <> "\n" andalso Symbol.not_eof s) || + $$$ "@" --| Scan.ahead (~$$ "{")) @@@ Scan.optional scan_nl []; val scan_antiq_body = Scan.trace (Symbol_Pos.scan_string_qq err_prefix || Symbol_Pos.scan_string_bq err_prefix) >> #2 || diff -r 2f213405cc0e -r 694f29a5433b src/Pure/General/output.scala --- a/src/Pure/General/output.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/General/output.scala Thu Dec 14 11:24:26 2017 +0100 @@ -24,24 +24,24 @@ def writeln(msg: String, stdout: Boolean = false) { if (msg != "") { - if (stdout) Console.println(writeln_text(msg)) - else Console.err.println(writeln_text(msg)) + if (stdout) Console.print(writeln_text(msg) + "\n") + else Console.err.print(writeln_text(msg) + "\n") } } def warning(msg: String, stdout: Boolean = false) { if (msg != "") { - if (stdout) Console.println(warning_text(msg)) - else Console.err.println(warning_text(msg)) + if (stdout) Console.print(warning_text(msg) + "\n") + else Console.err.print(warning_text(msg) + "\n") } } def error_message(msg: String, stdout: Boolean = false) { if (msg != "") { - if (stdout) Console.println(error_message_text(msg)) - else Console.err.println(error_message_text(msg)) + if (stdout) Console.print(error_message_text(msg) + "\n") + else Console.err.print(error_message_text(msg) + "\n") } } } diff -r 2f213405cc0e -r 694f29a5433b src/Pure/General/path.scala --- a/src/Pure/General/path.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/General/path.scala Thu Dec 14 11:24:26 2017 +0100 @@ -215,4 +215,7 @@ def absolute_file: JFile = File.absolute(file) def canonical_file: JFile = File.canonical(file) + + def absolute: Path = File.path(absolute_file) + def canonical: Path = File.path(canonical_file) } diff -r 2f213405cc0e -r 694f29a5433b src/Pure/PIDE/command.ML diff -r 2f213405cc0e -r 694f29a5433b src/Pure/PIDE/markup.ML diff -r 2f213405cc0e -r 694f29a5433b src/Pure/System/getopts.scala --- a/src/Pure/System/getopts.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/System/getopts.scala Thu Dec 14 11:24:26 2017 +0100 @@ -29,7 +29,7 @@ { def usage(): Nothing = { - Console.println(usage_text) + Output.writeln(usage_text, stdout = true) sys.exit(1) } diff -r 2f213405cc0e -r 694f29a5433b src/Pure/System/isabelle_process.scala --- a/src/Pure/System/isabelle_process.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/System/isabelle_process.scala Thu Dec 14 11:24:26 2017 +0100 @@ -41,7 +41,7 @@ modes: List[String] = Nil, cwd: JFile = null, env: Map[String, String] = Isabelle_System.settings(), - receiver: Prover.Receiver = Console.println(_), + receiver: Prover.Receiver = (msg: Prover.Message) => Output.writeln(msg.toString, stdout = true), xml_cache: XML.Cache = new XML.Cache(), sessions: Option[Sessions.Structure] = None, store: Sessions.Store = Sessions.store()): Prover = diff -r 2f213405cc0e -r 694f29a5433b src/Pure/System/isabelle_tool.scala --- a/src/Pure/System/isabelle_tool.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/System/isabelle_tool.scala Thu Dec 14 11:24:26 2017 +0100 @@ -114,6 +114,7 @@ ML_Process.isabelle_tool, NEWS.isabelle_tool, Options.isabelle_tool, + Present.isabelle_tool, Profiling_Report.isabelle_tool, Remote_DMG.isabelle_tool, Server.isabelle_tool, diff -r 2f213405cc0e -r 694f29a5433b src/Pure/System/options.scala --- a/src/Pure/System/options.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/System/options.scala Thu Dec 14 11:24:26 2017 +0100 @@ -185,13 +185,13 @@ } if (get_option != "") - Console.println(options.check_name(get_option).value) + Output.writeln(options.check_name(get_option).value, stdout = true) if (export_file != "") File.write(Path.explode(export_file), YXML.string_of_body(options.encode)) if (get_option == "" && export_file == "") - Console.println(options.print) + Output.writeln(options.print, stdout = true) }) } diff -r 2f213405cc0e -r 694f29a5433b src/Pure/System/progress.scala --- a/src/Pure/System/progress.scala Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/System/progress.scala Thu Dec 14 11:24:26 2017 +0100 @@ -45,7 +45,7 @@ { override def echo(msg: String) { - if (stderr) Console.err.println(msg) else Console.println(msg) + Output.writeln(msg, stdout = !stderr) } override def theory(session: String, theory: String): Unit = diff -r 2f213405cc0e -r 694f29a5433b src/Pure/Thy/document_antiquotations.ML --- a/src/Pure/Thy/document_antiquotations.ML Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/Thy/document_antiquotations.ML Thu Dec 14 11:24:26 2017 +0100 @@ -44,7 +44,8 @@ fun control_antiquotation name s1 s2 = Thy_Output.antiquotation name (Scan.lift Args.cartouche_input) - (fn {state, ...} => enclose s1 s2 o Thy_Output.output_text state {markdown = false}); + (fn {state, ...} => + enclose s1 s2 o Latex.output_text o Thy_Output.output_text state {markdown = false}); in diff -r 2f213405cc0e -r 694f29a5433b src/Pure/Thy/latex.ML --- a/src/Pure/Thy/latex.ML Wed Dec 13 19:28:19 2017 +0100 +++ b/src/Pure/Thy/latex.ML Thu Dec 14 11:24:26 2017 +0100 @@ -6,6 +6,13 @@ signature LATEX = sig + type text + val string: string -> text + val text: string * Position.T -> text + val block: text list -> text + val enclose_body: string -> string -> text list -> text list + val output_text: text list -> string + val output_positions: Position.T -> text list -> string val output_name: string -> string val output_ascii: string -> string val latex_control: Symbol.symbol @@ -20,9 +27,9 @@ val end_delim: string -> string val begin_tag: string -> string val end_tag: string -> string + val environment_block: string -> text list -> text val environment: string -> string -> string - val tex_trailer: string - val isabelle_theory: string -> string -> string + val isabelle_body: string -> text list -> text list val symbol_source: (string -> bool) * (string -> bool) -> string -> Symbol.symbol list -> string val theory_entry: string -> string @@ -32,6 +39,50 @@ structure Latex: LATEX = struct +(* text with positions *) + +abstype text = Text of string * Position.T | Block of text list +with + +fun string s = Text (s, Position.none); +val text = Text; +val block = Block; + +fun output_text texts = + let + fun output (Text (s, _)) = Buffer.add s + | output (Block body) = fold output body; + in Buffer.empty |> fold output texts |> Buffer.content end; + +fun output_positions file_pos texts = + let + fun position (a, b) = enclose "%:%" "%:%" (a ^ "=" ^ b); + fun add_position p positions = + let val s = position (apply2 Value.print_int p) + in positions |> s <> hd positions ? cons s end; + + fun output (Text (s, pos)) (positions, line) = + let + val positions' = + (case Position.line_of pos of + NONE => positions + | SOME l => add_position (line, l) positions); + val line' = fold_string (fn c => fn n => if c = "\n" then n + 1 else n) s line; + in (positions', line') end + | output (Block body) res = fold output body res; + in + (case Position.file_of file_pos of + NONE => "" + | SOME file => + ([position (Markup.fileN, file), "\\endinput"], 1) + |> fold output texts |> #1 |> rev |> cat_lines) + end; + +end; + +fun enclose_body bg en body = string bg :: body @ [string en]; + + (* output name for LaTeX macros *) val output_name = @@ -173,25 +224,28 @@ (* output token *) fun output_token tok = - let val s = Token.content_of tok in - if Token.is_kind Token.Comment tok then "" - else if Token.is_command tok then - "\\isacommand{" ^ output_syms s ^ "}" - else if Token.is_kind Token.Keyword tok andalso Symbol.is_ascii_identifier s then - "\\isakeyword{" ^ output_syms s ^ "}" - else if Token.is_kind Token.String tok then - enclose "{\\isachardoublequoteopen}" "{\\isachardoublequoteclose}" (output_syms s) - else if Token.is_kind Token.Alt_String tok then - enclose "{\\isacharbackquoteopen}" "{\\isacharbackquoteclose}" (output_syms s) - else if Token.is_kind Token.Verbatim tok then - let - val ants = Antiquote.read (Token.input_of tok); - val out = implode (map output_syms_antiq ants); - in enclose "{\\isacharverbatimopen}" "{\\isacharverbatimclose}" out end - else if Token.is_kind Token.Cartouche tok then - enclose "{\\isacartoucheopen}" "{\\isacartoucheclose}" (output_syms s) - else output_syms s - end handle ERROR msg => error (msg ^ Position.here (Token.pos_of tok)); + let + val s = Token.content_of tok; + val output = + if Token.is_kind Token.Comment tok then "" + else if Token.is_command tok then + "\\isacommand{" ^ output_syms s ^ "}" + else if Token.is_kind Token.Keyword tok andalso Symbol.is_ascii_identifier s then + "\\isakeyword{" ^ output_syms s ^ "}" + else if Token.is_kind Token.String tok then + enclose "{\\isachardoublequoteopen}" "{\\isachardoublequoteclose}" (output_syms s) + else if Token.is_kind Token.Alt_String tok then + enclose "{\\isacharbackquoteopen}" "{\\isacharbackquoteclose}" (output_syms s) + else if Token.is_kind Token.Verbatim tok then + let + val ants = Antiquote.read (Token.input_of tok); + val out = implode (map output_syms_antiq ants); + in enclose "{\\isacharverbatimopen}" "{\\isacharverbatimclose}" out end + else if Token.is_kind Token.Cartouche tok then + enclose "{\\isacartoucheopen}" "{\\isacartoucheclose}" (output_syms s) + else output_syms s; + in output end + handle ERROR msg => error (msg ^ Position.here (Token.pos_of tok)); (* tags *) @@ -204,22 +258,21 @@ (* theory presentation *) -fun environment name = - enclose ("%\n\\begin{" ^ output_name name ^ "}%\n") ("%\n\\end{" ^ output_name name ^ "}"); +fun environment_delim name = + ("%\n\\begin{" ^ output_name name ^ "}%\n", + "%\n\\end{" ^ output_name name ^ "}"); -val tex_trailer = - "%%% Local Variables:\n\ - \%%% mode: latex\n\ - \%%% TeX-master: \"root\"\n\ - \%%% End:\n"; +fun environment_block name = environment_delim name |-> enclose_body #> block; +fun environment name = environment_delim name |-> enclose; -fun isabelle_theory name txt = - "%\n\\begin{isabellebody}%\n\ - \\\setisabellecontext{" ^ output_syms name ^ "}%\n" ^ txt ^ - "%\n\\end{isabellebody}%\n" ^ tex_trailer; +fun isabelle_body_delim name = + ("%\n\\begin{isabellebody}%\n\\setisabellecontext{" ^ output_syms name ^ "}%\n", + "%\n\\end{isabellebody}%\n"); + +fun isabelle_body name = isabelle_body_delim name |-> enclose_body; fun symbol_source known name syms = - isabelle_theory name + uncurry enclose (isabelle_body_delim name) ("\\isamarkupfile{" ^ output_known_symbols known (Symbol.explode name) ^ "}%\n" ^ output_known_symbols known syms); diff -r 2f213405cc0e -r 694f29a5433b src/Pure/Thy/latex.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Pure/Thy/latex.scala Thu Dec 14 11:24:26 2017 +0100 @@ -0,0 +1,158 @@ +/* Title: Pure/Thy/latex.scala + Author: Makarius + +Support for LaTeX. +*/ + +package isabelle + + +import java.io.{File => JFile} + +import scala.annotation.tailrec + + +object Latex +{ + /** latex errors **/ + + def latex_errors(dir: Path, root_name: String): List[String] = + { + val root_log_path = dir + Path.explode(root_name).ext("log") + if (root_log_path.is_file) { + for { (msg, pos) <- filter_errors(dir, File.read(root_log_path)) } + yield "Latex error" + Position.here(pos) + ":\n" + cat_lines(split_lines(msg).map(" " + _)) + } + else Nil + } + + + /* generated .tex file */ + + private val File_Pattern = """^%:%file=(.+)%:%$""".r + private val Line_Pattern = """^*%:%(\d+)=(\d+)%:%$""".r + + def read_tex_file(tex_file: Path): Tex_File = + { + val positions = + Line.logical_lines(File.read(tex_file)).reverse. + takeWhile(_ != "\\endinput").reverse + + val source_file = + positions match { + case File_Pattern(file) :: _ => Some(file) + case _ => None + } + + val source_lines = + if (source_file.isEmpty) Nil + else + positions.flatMap(line => + line match { + case Line_Pattern(Value.Int(line), Value.Int(source_line)) => Some(line -> source_line) + case _ => None + }) + + new Tex_File(tex_file, source_file, source_lines) + } + + final class Tex_File private[Latex]( + tex_file: Path, source_file: Option[String], source_lines: List[(Int, Int)]) + { + override def toString: String = tex_file.toString + + def source_position(l: Int): Option[Position.T] = + source_file match { + case None => None + case Some(file) => + val source_line = + (0 /: source_lines.iterator.takeWhile({ case (m, _) => m <= l }))( + { case (_, (_, n)) => n }) + if (source_line == 0) None else Some(Position.Line_File(source_line, file)) + } + + def position(line: Int): Position.T = + source_position(line) getOrElse Position.Line_File(line, tex_file.implode) + } + + + /* latex log */ + + def filter_errors(dir: Path, root_log: String): List[(String, Position.T)] = + { + val seen_files = Synchronized(Map.empty[JFile, Tex_File]) + + def check_tex_file(path: Path): Option[Tex_File] = + seen_files.change_result(seen => + seen.get(path.file) match { + case None => + if (path.is_file) { + val tex_file = read_tex_file(path) + (Some(tex_file), seen + (path.file -> tex_file)) + } + else (None, seen) + case some => (some, seen) + }) + + def tex_file_position(path: Path, line: Int): Position.T = + check_tex_file(path) match { + case Some(tex_file) => tex_file.position(line) + case None => Position.Line_File(line, path.implode) + } + + object File_Line_Error + { + val Pattern = """^(.*?\.\w\w\w):(\d+): (.*)$""".r + def unapply(line: String): Option[(Path, Int, String)] = + line match { + case Pattern(file, Value.Int(line), message) => + val path = File.standard_path(file) + if (Path.is_wellformed(path)) { + val file = (dir + Path.explode(path)).canonical + if (file.is_file) Some((file, line, message)) else None + } + else None + case _ => None + } + } + + val More_Error = + List( + """l\.\d+""", + "", + "