", "\\",
"\\", "\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\",
"\\", "\\")
val blanks: Set[String] = recode_set(space, "\t", "\n", "\u000B", "\f", "\r", "\r\n")
val sym_chars =
Set("!", "#", "$", "%", "&", "*", "+", "-", "/", "<", "=", ">", "?", "@", "^", "_", "|", "~")
val symbolic: Set[String] = recode_set((for {(sym, _) <- symbols; if raw_symbolic(sym)} yield sym): _*)
/* misc symbols */
val newline_decoded = decode(newline)
val comment_decoded = decode(comment)
val cancel_decoded = decode(cancel)
val latex_decoded = decode(latex)
val marker_decoded = decode(marker)
val open_decoded = decode(open)
val close_decoded = decode(close)
/* control symbols */
val control_decoded: Set[Symbol] =
Set((for ((sym, _) <- symbols if sym.startsWith("\\<^")) yield decode(sym)): _*)
val sub_decoded = decode(sub)
val sup_decoded = decode(sup)
val bold_decoded = decode(bold)
val emph_decoded = decode(emph)
val bsub_decoded = decode(bsub)
val esub_decoded = decode(esub)
val bsup_decoded = decode(bsup)
val esup_decoded = decode(esup)
}
/* tables */
def properties: Map[Symbol, Properties.T] = symbols.properties
def names: Map[Symbol, (String, String)] = symbols.names
def groups: List[(String, List[Symbol])] = symbols.groups
def abbrevs: Multi_Map[Symbol, String] = symbols.abbrevs
def codes: List[(Symbol, Int)] = symbols.codes
def groups_code: List[(String, List[Symbol])] =
{
val has_code = codes.iterator.map(_._1).toSet
groups.flatMap({ case (group, symbols) =>
val symbols1 = symbols.filter(has_code)
if (symbols1.isEmpty) None else Some((group, symbols1))
})
}
lazy val is_code: Int => Boolean = codes.map(_._2).toSet
def decode(text: String): String = symbols.decode(text)
def encode(text: String): String = symbols.encode(text)
def decode_yxml(text: String, cache: XML.Cache = XML.Cache.none): XML.Body =
YXML.parse_body(decode(text), cache = cache)
def decode_yxml_failsafe(text: String, cache: XML.Cache = XML.Cache.none): XML.Body =
YXML.parse_body_failsafe(decode(text), cache = cache)
def encode_yxml(body: XML.Body): String = encode(YXML.string_of_body(body))
def decode_strict(text: String): String =
{
val decoded = decode(text)
if (encode(decoded) == text) decoded
else {
val bad = new mutable.ListBuffer[Symbol]
for (s <- iterator(text) if encode(decode(s)) != s && !bad.contains(s))
bad += s
error("Bad Unicode symbols in text: " + commas_quote(bad))
}
}
def output(unicode_symbols: Boolean, text: String): String =
if (unicode_symbols) Symbol.decode(text) else Symbol.encode(text)
def fonts: Map[Symbol, String] = symbols.fonts
def font_names: List[String] = symbols.font_names
def font_index: Map[String, Int] = symbols.font_index
def lookup_font(sym: Symbol): Option[Int] = symbols.fonts.get(sym).map(font_index(_))
/* classification */
def is_letter(sym: Symbol): Boolean = symbols.letters.contains(sym)
def is_digit(sym: Symbol): Boolean = sym.length == 1 && '0' <= sym(0) && sym(0) <= '9'
def is_quasi(sym: Symbol): Boolean = sym == "_" || sym == "'"
def is_letdig(sym: Symbol): Boolean = is_letter(sym) || is_digit(sym) || is_quasi(sym)
def is_blank(sym: Symbol): Boolean = symbols.blanks.contains(sym)
/* symbolic newline */
val newline: Symbol = "\\"
def newline_decoded: Symbol = symbols.newline_decoded
def print_newlines(str: String): String =
if (str.contains('\n'))
(for (s <- iterator(str)) yield { if (s == "\n") newline_decoded else s }).mkString
else str
/* formal comments */
val comment: Symbol = "\\"
val cancel: Symbol = "\\<^cancel>"
val latex: Symbol = "\\<^latex>"
val marker: Symbol = "\\<^marker>"
def comment_decoded: Symbol = symbols.comment_decoded
def cancel_decoded: Symbol = symbols.cancel_decoded
def latex_decoded: Symbol = symbols.latex_decoded
def marker_decoded: Symbol = symbols.marker_decoded
/* cartouches */
val open: Symbol = "\\"
val close: Symbol = "\\"
def open_decoded: Symbol = symbols.open_decoded
def close_decoded: Symbol = symbols.close_decoded
def is_open(sym: Symbol): Boolean = sym == open_decoded || sym == open
def is_close(sym: Symbol): Boolean = sym == close_decoded || sym == close
def cartouche(s: String): String = open + s + close
def cartouche_decoded(s: String): String = open_decoded + s + close_decoded
/* symbols for symbolic identifiers */
private def raw_symbolic(sym: Symbol): Boolean =
sym.startsWith("\\<") && sym.endsWith(">") && !sym.startsWith("\\<^")
def is_symbolic(sym: Symbol): Boolean =
!is_open(sym) && !is_close(sym) && (raw_symbolic(sym) || symbols.symbolic.contains(sym))
def is_symbolic_char(sym: Symbol): Boolean = symbols.sym_chars.contains(sym)
/* control symbols */
val control_prefix = "\\<^"
val control_suffix = ">"
def control_name(sym: Symbol): Option[String] =
if (is_control_encoded(sym))
Some(sym.substring(control_prefix.length, sym.length - control_suffix.length))
else None
def is_control_encoded(sym: Symbol): Boolean =
sym.startsWith(control_prefix) && sym.endsWith(control_suffix)
def is_control(sym: Symbol): Boolean =
is_control_encoded(sym) || symbols.control_decoded.contains(sym)
def is_controllable(sym: Symbol): Boolean =
!is_blank(sym) && !is_control(sym) && !is_open(sym) && !is_close(sym) &&
!is_malformed(sym) && sym != "\""
val sub: Symbol = "\\<^sub>"
val sup: Symbol = "\\<^sup>"
val bold: Symbol = "\\<^bold>"
val emph: Symbol = "\\<^emph>"
val bsub: Symbol = "\\<^bsub>"
val esub: Symbol = "\\<^esub>"
val bsup: Symbol = "\\<^bsup>"
val esup: Symbol = "\\<^esup>"
def sub_decoded: Symbol = symbols.sub_decoded
def sup_decoded: Symbol = symbols.sup_decoded
def bold_decoded: Symbol = symbols.bold_decoded
def emph_decoded: Symbol = symbols.emph_decoded
def bsub_decoded: Symbol = symbols.bsub_decoded
def esub_decoded: Symbol = symbols.esub_decoded
def bsup_decoded: Symbol = symbols.bsup_decoded
def esup_decoded: Symbol = symbols.esup_decoded
/* metric */
def is_printable(sym: Symbol): Boolean =
if (is_ascii(sym)) is_ascii_printable(sym(0))
else !is_control(sym)
object Metric extends Pretty.Metric
{
val unit = 1.0
def apply(str: String): Double =
(for (s <- iterator(str)) yield {
val sym = encode(s)
if (sym.startsWith("\\ (t: Time): Boolean = ms > t.ms
def >= (t: Time): Boolean = ms >= t.ms
def min(t: Time): Time = if (this < t) this else t
def max(t: Time): Time = if (this > t) this else t
def is_zero: Boolean = ms == 0
def is_relevant: Boolean = ms >= 1
override def toString: String = Time.print_seconds(seconds)
def message: String = toString + "s"
def message_hms: String =
{
val s = ms / 1000
String.format(Locale.ROOT, "%d:%02d:%02d",
java.lang.Long.valueOf(s / 3600),
java.lang.Long.valueOf((s / 60) % 60),
java.lang.Long.valueOf(s % 60))
}
def instant: Instant = Instant.ofEpochMilli(ms)
- def sleep { Thread.sleep(ms) }
+ def sleep: Unit = Thread.sleep(ms)
}
diff --git a/src/Pure/General/untyped.scala b/src/Pure/General/untyped.scala
--- a/src/Pure/General/untyped.scala
+++ b/src/Pure/General/untyped.scala
@@ -1,60 +1,60 @@
/* Title: Pure/General/untyped.scala
Author: Makarius
Untyped, unscoped, unchecked access to JVM objects.
*/
package isabelle
import java.lang.reflect.{Constructor, Method, Field}
object Untyped
{
def constructor[C](c: Class[C], arg_types: Class[_]*): Constructor[C] =
{
val con = c.getDeclaredConstructor(arg_types: _*)
con.setAccessible(true)
con
}
def method(c: Class[_], name: String, arg_types: Class[_]*): Method =
{
val m = c.getDeclaredMethod(name, arg_types: _*)
m.setAccessible(true)
m
}
def classes(obj: AnyRef): Iterator[Class[_ <: AnyRef]] = new Iterator[Class[_ <: AnyRef]] {
private var next_elem: Class[_ <: AnyRef] = obj.getClass
- def hasNext(): Boolean = next_elem != null
+ def hasNext: Boolean = next_elem != null
def next(): Class[_ <: AnyRef] = {
val c = next_elem
next_elem = c.getSuperclass.asInstanceOf[Class[_ <: AnyRef]]
c
}
}
def field(obj: AnyRef, x: String): Field =
{
val iterator =
for {
c <- classes(obj)
field <- c.getDeclaredFields.iterator
if field.getName == x
} yield {
field.setAccessible(true)
field
}
- if (iterator.hasNext) iterator.next
+ if (iterator.hasNext) iterator.next()
else error("No field " + quote(x) + " for " + obj)
}
def get[A](obj: AnyRef, x: String): A =
if (obj == null) null.asInstanceOf[A]
else field(obj, x).get(obj).asInstanceOf[A]
def set[A](obj: AnyRef, x: String, y: A): Unit =
field(obj, x).set(obj, y)
}
diff --git a/src/Pure/General/utf8.scala b/src/Pure/General/utf8.scala
--- a/src/Pure/General/utf8.scala
+++ b/src/Pure/General/utf8.scala
@@ -1,90 +1,90 @@
/* Title: Pure/General/utf8.scala
Author: Makarius
Variations on UTF-8.
*/
package isabelle
import java.nio.charset.Charset
import scala.io.Codec
object UTF8
{
/* charset */
val charset_name: String = "UTF-8"
val charset: Charset = Charset.forName(charset_name)
def codec(): Codec = Codec(charset)
def bytes(s: String): Array[Byte] = s.getBytes(charset)
/* permissive UTF-8 decoding */
// see also https://en.wikipedia.org/wiki/UTF-8#Description
// overlong encodings enable byte-stuffing of low-ASCII
def decode_permissive(text: CharSequence): String =
{
val buf = new java.lang.StringBuilder(text.length)
var code = -1
var rest = 0
- def flush()
+ def flush(): Unit =
{
if (code != -1) {
if (rest == 0 && Character.isValidCodePoint(code))
buf.appendCodePoint(code)
else buf.append('\uFFFD')
code = -1
rest = 0
}
}
- def init(x: Int, n: Int)
+ def init(x: Int, n: Int): Unit =
{
flush()
code = x
rest = n
}
- def push(x: Int)
+ def push(x: Int): Unit =
{
if (rest <= 0) init(x, -1)
else {
code <<= 6
code += x
rest -= 1
}
}
for (i <- 0 until text.length) {
val c = text.charAt(i)
if (c < 128) { flush(); buf.append(c) }
else if ((c & 0xC0) == 0x80) push(c & 0x3F)
else if ((c & 0xE0) == 0xC0) init(c & 0x1F, 1)
else if ((c & 0xF0) == 0xE0) init(c & 0x0F, 2)
else if ((c & 0xF8) == 0xF0) init(c & 0x07, 3)
}
flush()
buf.toString
}
private class Decode_Chars(decode: String => String,
buffer: Array[Byte], start: Int, end: Int) extends CharSequence
{
def length: Int = end - start
def charAt(i: Int): Char = (buffer(start + i).asInstanceOf[Int] & 0xFF).asInstanceOf[Char]
def subSequence(i: Int, j: Int): CharSequence =
new Decode_Chars(decode, buffer, start + i, start + j)
// toString with adhoc decoding: abuse of CharSequence interface
override def toString: String = decode(decode_permissive(this))
}
def decode_chars(decode: String => String,
buffer: Array[Byte], start: Int, end: Int): CharSequence =
{
require(0 <= start && start <= end && end <= buffer.length, "bad decode range")
new Decode_Chars(decode, buffer, start, end)
}
}
diff --git a/src/Pure/General/word.scala b/src/Pure/General/word.scala
--- a/src/Pure/General/word.scala
+++ b/src/Pure/General/word.scala
@@ -1,87 +1,87 @@
/* Title: Pure/General/word.scala
Author: Makarius
Support for words within Unicode text.
*/
package isabelle
import java.text.Bidi
import java.util.Locale
object Word
{
/* directionality */
def bidi_detect(str: String): Boolean =
str.exists(c => c >= 0x590) && Bidi.requiresBidi(str.toArray, 0, str.length)
def bidi_override(str: String): String =
if (bidi_detect(str)) "\u200E\u202D" + str + "\u202C" else str
/* case */
def lowercase(str: String): String = str.toLowerCase(Locale.ROOT)
def uppercase(str: String): String = str.toUpperCase(Locale.ROOT)
def capitalize(str: String): String =
if (str.length == 0) str
else {
val n = Character.charCount(str.codePointAt(0))
uppercase(str.substring(0, n)) + lowercase(str.substring(n))
}
def perhaps_capitalize(str: String): String =
if (Codepoint.iterator(str).forall(c => Character.isLowerCase(c) || Character.isDigit(c)))
capitalize(str)
else str
sealed abstract class Case
case object Lowercase extends Case
case object Uppercase extends Case
case object Capitalized extends Case
object Case
{
def apply(c: Case, str: String): String =
c match {
case Lowercase => lowercase(str)
case Uppercase => uppercase(str)
case Capitalized => capitalize(str)
}
def unapply(str: String): Option[Case] =
if (str.nonEmpty) {
if (Codepoint.iterator(str).forall(Character.isLowerCase)) Some(Lowercase)
else if (Codepoint.iterator(str).forall(Character.isUpperCase)) Some(Uppercase)
else {
val it = Codepoint.iterator(str)
- if (Character.isUpperCase(it.next) && it.forall(Character.isLowerCase))
+ if (Character.isUpperCase(it.next()) && it.forall(Character.isLowerCase))
Some(Capitalized)
else None
}
}
else None
}
/* sequence of words */
def implode(words: Iterable[String]): String = words.iterator.mkString(" ")
def explode(sep: Char => Boolean, text: String): List[String] =
Library.separated_chunks(sep, text).map(_.toString).filter(_ != "").toList
def explode(sep: Char, text: String): List[String] =
explode(_ == sep, text)
def explode(text: String): List[String] =
explode(Character.isWhitespace _, text)
/* brackets */
val open_brackets = "([{«‹⟨⌈⌊⦇⟦⦃⟪"
val close_brackets = ")]}»›⟩⌉⌋⦈⟧⦄⟫"
}
diff --git a/src/Pure/Isar/document_structure.scala b/src/Pure/Isar/document_structure.scala
--- a/src/Pure/Isar/document_structure.scala
+++ b/src/Pure/Isar/document_structure.scala
@@ -1,224 +1,224 @@
/* Title: Pure/Isar/document_structure.scala
Author: Makarius
Overall document structure.
*/
package isabelle
import scala.collection.mutable
import scala.annotation.tailrec
object Document_Structure
{
/** general structure **/
sealed abstract class Document { def length: Int }
case class Block(name: String, text: String, body: List[Document]) extends Document
{ val length: Int = (0 /: body)(_ + _.length) }
case class Atom(length: Int) extends Document
def is_theory_command(keywords: Keyword.Keywords, command: Command): Boolean =
command.span.is_kind(keywords,
kind => Keyword.theory(kind) && !Keyword.theory_end(kind), false)
def is_document_command(keywords: Keyword.Keywords, command: Command): Boolean =
command.span.is_kind(keywords, Keyword.document, false)
def is_diag_command(keywords: Keyword.Keywords, command: Command): Boolean =
command.span.is_kind(keywords, Keyword.diag, false)
def is_heading_command(command: Command): Boolean =
proper_heading_level(command).isDefined
def proper_heading_level(command: Command): Option[Int] =
command.span.name match {
case Thy_Header.CHAPTER => Some(0)
case Thy_Header.SECTION => Some(1)
case Thy_Header.SUBSECTION => Some(2)
case Thy_Header.SUBSUBSECTION => Some(3)
case Thy_Header.PARAGRAPH => Some(4)
case Thy_Header.SUBPARAGRAPH => Some(5)
case _ => None
}
def heading_level(keywords: Keyword.Keywords, command: Command): Option[Int] =
proper_heading_level(command) orElse
(if (is_theory_command(keywords, command)) Some(6) else None)
/** context blocks **/
def parse_blocks(
syntax: Outer_Syntax,
node_name: Document.Node.Name,
text: CharSequence): List[Document] =
{
def is_plain_theory(command: Command): Boolean =
is_theory_command(syntax.keywords, command) &&
!command.span.is_begin && !command.span.is_end
/* stack operations */
def buffer(): mutable.ListBuffer[Document] = new mutable.ListBuffer[Document]
var stack: List[(Command, mutable.ListBuffer[Document])] =
List((Command.empty, buffer()))
- def open(command: Command) { stack = (command, buffer()) :: stack }
+ def open(command: Command): Unit = { stack = (command, buffer()) :: stack }
def close(): Boolean =
stack match {
case (command, body) :: (_, body2) :: _ =>
body2 += Block(command.span.name, command.source, body.toList)
stack = stack.tail
true
case _ =>
false
}
- def flush() { if (is_plain_theory(stack.head._1)) close() }
+ def flush(): Unit = if (is_plain_theory(stack.head._1)) close()
def result(): List[Document] =
{
while (close()) { }
stack.head._2.toList
}
- def add(command: Command)
+ def add(command: Command): Unit =
{
if (command.span.is_begin || is_plain_theory(command)) { flush(); open(command) }
else if (command.span.is_end) { flush(); close() }
stack.head._2 += Atom(command.source.length)
}
/* result structure */
val spans = syntax.parse_spans(text)
spans.foreach(span => add(Command(Document_ID.none, node_name, Command.Blobs_Info.none, span)))
result()
}
/** section headings **/
trait Item
{
def name: String = ""
def source: String = ""
def heading_level: Option[Int] = None
}
object No_Item extends Item
class Sections(keywords: Keyword.Keywords)
{
private def buffer(): mutable.ListBuffer[Document] = new mutable.ListBuffer[Document]
private var stack: List[(Int, Item, mutable.ListBuffer[Document])] =
List((0, No_Item, buffer()))
- @tailrec private def close(level: Int => Boolean)
+ @tailrec private def close(level: Int => Boolean): Unit =
{
stack match {
case (lev, item, body) :: (_, _, body2) :: _ if level(lev) =>
body2 += Block(item.name, item.source, body.toList)
stack = stack.tail
close(level)
case _ =>
}
}
def result(): List[Document] =
{
close(_ => true)
stack.head._3.toList
}
- def add(item: Item)
+ def add(item: Item): Unit =
{
item.heading_level match {
case Some(i) =>
close(_ > i)
stack = (i + 1, item, buffer()) :: stack
case None =>
}
stack.head._3 += Atom(item.source.length)
}
}
/* outer syntax sections */
private class Command_Item(keywords: Keyword.Keywords, command: Command) extends Item
{
override def name: String = command.span.name
override def source: String = command.source
override def heading_level: Option[Int] = Document_Structure.heading_level(keywords, command)
}
def parse_sections(
syntax: Outer_Syntax,
node_name: Document.Node.Name,
text: CharSequence): List[Document] =
{
val sections = new Sections(syntax.keywords)
for { span <- syntax.parse_spans(text) } {
sections.add(
new Command_Item(syntax.keywords,
Command(Document_ID.none, node_name, Command.Blobs_Info.none, span)))
}
sections.result()
}
/* ML sections */
private class ML_Item(token: ML_Lex.Token, level: Option[Int]) extends Item
{
override def source: String = token.source
override def heading_level: Option[Int] = level
}
def parse_ml_sections(SML: Boolean, text: CharSequence): List[Document] =
{
val sections = new Sections(Keyword.Keywords.empty)
val nl = new ML_Item(ML_Lex.Token(ML_Lex.Kind.SPACE, "\n"), None)
var context: Scan.Line_Context = Scan.Finished
for (line <- Library.separated_chunks(_ == '\n', text)) {
val (toks, next_context) = ML_Lex.tokenize_line(SML, line, context)
val level =
toks.filterNot(_.is_space) match {
case List(tok) if tok.is_comment =>
val s = tok.source
if (Codepoint.iterator(s).exists(c => Character.isLetter(c) || Character.isDigit(c)))
{
if (s.startsWith("(**** ") && s.endsWith(" ****)")) Some(0)
else if (s.startsWith("(*** ") && s.endsWith(" ***)")) Some(1)
else if (s.startsWith("(** ") && s.endsWith(" **)")) Some(2)
else if (s.startsWith("(* ") && s.endsWith(" *)")) Some(3)
else None
}
else None
case _ => None
}
if (level.isDefined && context == Scan.Finished && next_context == Scan.Finished)
toks.foreach(tok => sections.add(new ML_Item(tok, if (tok.is_comment) level else None)))
else
toks.foreach(tok => sections.add(new ML_Item(tok, None)))
sections.add(nl)
context = next_context
}
sections.result()
}
}
diff --git a/src/Pure/Isar/line_structure.scala b/src/Pure/Isar/line_structure.scala
--- a/src/Pure/Isar/line_structure.scala
+++ b/src/Pure/Isar/line_structure.scala
@@ -1,70 +1,70 @@
/* Title: Pure/Isar/line_structure.scala
Author: Makarius
Line-oriented document structure, e.g. for fold handling.
*/
package isabelle
object Line_Structure
{
val init: Line_Structure = Line_Structure()
}
sealed case class Line_Structure(
improper: Boolean = true,
blank: Boolean = true,
command: Boolean = false,
depth: Int = 0,
span_depth: Int = 0,
after_span_depth: Int = 0,
element_depth: Int = 0)
{
def update(keywords: Keyword.Keywords, tokens: List[Token]): Line_Structure =
{
val improper1 = tokens.forall(tok => !tok.is_proper)
val blank1 = tokens.forall(_.is_space)
val command1 = tokens.exists(_.is_begin_or_command)
val command_depth =
- tokens.iterator.filter(_.is_proper).toStream.headOption match {
+ tokens.iterator.filter(_.is_proper).nextOption() match {
case Some(tok) =>
if (keywords.is_command(tok, Keyword.close_structure))
Some(after_span_depth - 1)
else None
case None => None
}
val depth1 =
if (tokens.exists(tok =>
keywords.is_before_command(tok) ||
!tok.is_end && keywords.is_command(tok, Keyword.theory))) element_depth
else if (command_depth.isDefined) command_depth.get
else if (command1) after_span_depth
else span_depth
val (span_depth1, after_span_depth1, element_depth1) =
((span_depth, after_span_depth, element_depth) /: tokens) {
case (depths @ (x, y, z), tok) =>
if (tok.is_begin) (z + 2, z + 1, z + 1)
else if (tok.is_end) (z + 1, z - 1, z - 1)
else if (tok.is_command) {
val depth0 = element_depth
if (keywords.is_command(tok, Keyword.theory_goal)) (depth0 + 2, depth0 + 1, z)
else if (keywords.is_command(tok, Keyword.theory)) (depth0 + 1, depth0, z)
else if (keywords.is_command(tok, Keyword.proof_open)) (y + 2, y + 1, z)
else if (keywords.is_command(tok, Set(Keyword.PRF_BLOCK))) (y + 2, y + 1, z)
else if (keywords.is_command(tok, Set(Keyword.QED_BLOCK))) (y - 1, y - 2, z)
else if (keywords.is_command(tok, Set(Keyword.PRF_CLOSE))) (y, y - 1, z)
else if (keywords.is_command(tok, Keyword.proof_close)) (y + 1, y - 1, z)
else if (keywords.is_command(tok, Keyword.qed_global)) (depth0 + 1, depth0, z)
else depths
}
else depths
}
Line_Structure(
improper1, blank1, command1, depth1, span_depth1, after_span_depth1, element_depth1)
}
}
diff --git a/src/Pure/Isar/outer_syntax.scala b/src/Pure/Isar/outer_syntax.scala
--- a/src/Pure/Isar/outer_syntax.scala
+++ b/src/Pure/Isar/outer_syntax.scala
@@ -1,210 +1,210 @@
/* Title: Pure/Isar/outer_syntax.scala
Author: Makarius
Isabelle/Isar outer syntax.
*/
package isabelle
import scala.collection.mutable
object Outer_Syntax
{
/* syntax */
val empty: Outer_Syntax = new Outer_Syntax()
def merge(syns: List[Outer_Syntax]): Outer_Syntax = (empty /: syns)(_ ++ _)
/* string literals */
def quote_string(str: String): String =
{
val result = new StringBuilder(str.length + 10)
result += '"'
for (s <- Symbol.iterator(str)) {
if (s.length == 1) {
val c = s(0)
if (c < 32 && c != YXML.X && c != YXML.Y || c == '\\' || c == '"') {
result += '\\'
if (c < 10) result += '0'
if (c < 100) result += '0'
result ++= c.asInstanceOf[Int].toString
}
else result += c
}
else result ++= s
}
result += '"'
result.toString
}
}
final class Outer_Syntax private(
val keywords: Keyword.Keywords = Keyword.Keywords.empty,
val rev_abbrevs: Thy_Header.Abbrevs = Nil,
val language_context: Completion.Language_Context = Completion.Language_Context.outer,
val has_tokens: Boolean = true)
{
/** syntax content **/
override def toString: String = keywords.toString
/* keywords */
def + (name: String, kind: String = "", load_command: String = ""): Outer_Syntax =
new Outer_Syntax(
keywords + (name, kind, load_command), rev_abbrevs, language_context, has_tokens = true)
def add_keywords(keywords: Thy_Header.Keywords): Outer_Syntax =
(this /: keywords) {
case (syntax, (name, spec)) =>
syntax +
(Symbol.decode(name), spec.kind, spec.load_command) +
(Symbol.encode(name), spec.kind, spec.load_command)
}
/* abbrevs */
def abbrevs: Thy_Header.Abbrevs = rev_abbrevs.reverse
def add_abbrevs(new_abbrevs: Thy_Header.Abbrevs): Outer_Syntax =
if (new_abbrevs.isEmpty) this
else {
val rev_abbrevs1 = Library.distinct(new_abbrevs) reverse_::: rev_abbrevs
new Outer_Syntax(keywords, rev_abbrevs1, language_context, has_tokens)
}
/* completion */
private lazy val completion: Completion =
{
val completion_keywords = (keywords.minor.iterator ++ keywords.major.iterator).toList
val completion_abbrevs =
completion_keywords.flatMap(name =>
(if (Keyword.theory_block.contains(keywords.kinds(name)))
List((name, name + "\nbegin\n\u0007\nend"))
else Nil) :::
(if (Completion.Word_Parsers.is_word(name)) List((name, name)) else Nil)) :::
abbrevs.flatMap(
{ case (a, b) =>
val a1 = Symbol.decode(a)
val a2 = Symbol.encode(a)
val b1 = Symbol.decode(b)
List((a1, b1), (a2, b1))
})
Completion.make(completion_keywords, completion_abbrevs)
}
def complete(
history: Completion.History,
unicode: Boolean,
explicit: Boolean,
start: Text.Offset,
text: CharSequence,
caret: Int,
context: Completion.Language_Context): Option[Completion.Result] =
{
completion.complete(history, unicode, explicit, start, text, caret, context)
}
/* build */
def + (header: Document.Node.Header): Outer_Syntax =
add_keywords(header.keywords).add_abbrevs(header.abbrevs)
def ++ (other: Outer_Syntax): Outer_Syntax =
if (this eq other) this
else if (this eq Outer_Syntax.empty) other
else {
val keywords1 = keywords ++ other.keywords
val rev_abbrevs1 = Library.merge(rev_abbrevs, other.rev_abbrevs)
if ((keywords eq keywords1) && (rev_abbrevs eq rev_abbrevs1)) this
else new Outer_Syntax(keywords1, rev_abbrevs1, language_context, has_tokens)
}
/* load commands */
def load_command(name: String): Option[String] =
keywords.load_commands.get(name)
def has_load_commands(text: String): Boolean =
keywords.load_commands.exists({ case (cmd, _) => text.containsSlice(cmd) })
/* language context */
def set_language_context(context: Completion.Language_Context): Outer_Syntax =
new Outer_Syntax(keywords, rev_abbrevs, context, has_tokens)
def no_tokens: Outer_Syntax =
{
require(keywords.is_empty, "bad syntax keywords")
new Outer_Syntax(
rev_abbrevs = rev_abbrevs,
language_context = language_context,
has_tokens = false)
}
/** parsing **/
/* command spans */
def parse_spans(toks: List[Token]): List[Command_Span.Span] =
{
val result = new mutable.ListBuffer[Command_Span.Span]
val content = new mutable.ListBuffer[Token]
val ignored = new mutable.ListBuffer[Token]
- def ship(content: List[Token])
+ def ship(content: List[Token]): Unit =
{
val kind =
if (content.forall(_.is_ignored)) Command_Span.Ignored_Span
else if (content.exists(_.is_error)) Command_Span.Malformed_Span
else
content.find(_.is_command) match {
case None => Command_Span.Malformed_Span
case Some(cmd) =>
val name = cmd.source
val offset =
(0 /: content.takeWhile(_ != cmd)) {
case (i, tok) => i + Symbol.length(tok.source) }
val end_offset = offset + Symbol.length(name)
val range = Text.Range(offset, end_offset) + 1
Command_Span.Command_Span(name, Position.Range(range))
}
result += Command_Span.Span(kind, content)
}
- def flush()
+ def flush(): Unit =
{
if (content.nonEmpty) { ship(content.toList); content.clear }
if (ignored.nonEmpty) { ship(ignored.toList); ignored.clear }
}
for (tok <- toks) {
if (tok.is_ignored) ignored += tok
else if (keywords.is_before_command(tok) ||
tok.is_command &&
(!content.exists(keywords.is_before_command) || content.exists(_.is_command)))
{ flush(); content += tok }
else { content ++= ignored; ignored.clear; content += tok }
}
flush()
result.toList
}
def parse_spans(input: CharSequence): List[Command_Span.Span] =
parse_spans(Token.explode(keywords, input))
}
diff --git a/src/Pure/ML/ml_console.scala b/src/Pure/ML/ml_console.scala
--- a/src/Pure/ML/ml_console.scala
+++ b/src/Pure/ML/ml_console.scala
@@ -1,84 +1,84 @@
/* Title: Pure/ML/ml_console.scala
Author: Makarius
The raw ML process in interactive mode.
*/
package isabelle
object ML_Console
{
/* command line entry point */
- def main(args: Array[String])
+ def main(args: Array[String]): Unit =
{
Command_Line.tool {
var dirs: List[Path] = Nil
var include_sessions: List[String] = Nil
var logic = Isabelle_System.getenv("ISABELLE_LOGIC")
var modes: List[String] = Nil
var no_build = false
var options = Options.init()
var raw_ml_system = false
val getopts = Getopts("""
Usage: isabelle console [OPTIONS]
Options are:
-d DIR include session directory
-i NAME include session in name-space of theories
-l NAME logic session name (default ISABELLE_LOGIC=""" + quote(logic) + """)
-m MODE add print mode for output
-n no build of session image on startup
-o OPTION override Isabelle system OPTION (via NAME=VAL or NAME)
-r bootstrap from raw Poly/ML
Build a logic session image and run the raw Isabelle ML process
in interactive mode, with line editor ISABELLE_LINE_EDITOR=""" +
quote(Isabelle_System.getenv("ISABELLE_LINE_EDITOR")) + """.
""",
"d:" -> (arg => dirs = dirs ::: List(Path.explode(arg))),
"i:" -> (arg => include_sessions = include_sessions ::: List(arg)),
"l:" -> (arg => logic = arg),
"m:" -> (arg => modes = arg :: modes),
"n" -> (_ => no_build = true),
"o:" -> (arg => options = options + arg),
"r" -> (_ => raw_ml_system = true))
val more_args = getopts(args)
if (more_args.nonEmpty) getopts.usage()
val sessions_structure = Sessions.load_structure(options, dirs = dirs)
val store = Sessions.store(options)
// build logic
if (!no_build && !raw_ml_system) {
val progress = new Console_Progress()
val rc =
progress.interrupt_handler {
Build.build_logic(options, logic, build_heap = true, progress = progress, dirs = dirs)
}
if (rc != 0) sys.exit(rc)
}
// process loop
val process =
ML_Process(options, sessions_structure, store,
logic = logic, args = List("-i"), redirect = true,
modes = if (raw_ml_system) Nil else modes ::: List("ASCII"),
raw_ml_system = raw_ml_system,
session_base =
if (raw_ml_system) None
else Some(Sessions.base_info(
options, logic, dirs = dirs, include_sessions = include_sessions).check.base))
POSIX_Interrupt.handler { process.interrupt } {
new TTY_Loop(process.stdin, process.stdout).join
val rc = process.join
if (rc != 0) sys.exit(rc)
}
}
}
}
diff --git a/src/Pure/ML/ml_statistics.scala b/src/Pure/ML/ml_statistics.scala
--- a/src/Pure/ML/ml_statistics.scala
+++ b/src/Pure/ML/ml_statistics.scala
@@ -1,324 +1,324 @@
/* Title: Pure/ML/ml_statistics.scala
Author: Makarius
ML runtime statistics.
*/
package isabelle
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.immutable.{SortedSet, SortedMap}
import scala.swing.{Frame, Component}
import org.jfree.data.xy.{XYSeries, XYSeriesCollection}
import org.jfree.chart.{JFreeChart, ChartPanel, ChartFactory}
import org.jfree.chart.plot.PlotOrientation
object ML_Statistics
{
/* properties */
val Now = new Properties.Double("now")
def now(props: Properties.T): Double = Now.unapply(props).get
/* memory status */
val Heap_Size = new Properties.Long("size_heap")
val Heap_Free = new Properties.Long("size_heap_free_last_GC")
val GC_Percent = new Properties.Int("GC_percent")
sealed case class Memory_Status(heap_size: Long, heap_free: Long, gc_percent: Int)
{
def heap_used: Long = (heap_size - heap_free) max 0
def heap_used_fraction: Double =
if (heap_size == 0) 0.0 else heap_used.toDouble / heap_size
def gc_progress: Option[Double] =
if (1 <= gc_percent && gc_percent <= 100) Some((gc_percent - 1) * 0.01) else None
}
def memory_status(props: Properties.T): Memory_Status =
{
val heap_size = Heap_Size.get(props)
val heap_free = Heap_Free.get(props)
val gc_percent = GC_Percent.get(props)
Memory_Status(heap_size, heap_free, gc_percent)
}
/* monitor process */
def monitor(pid: Long,
stats_dir: String = "",
delay: Time = Time.seconds(0.5),
- consume: Properties.T => Unit = Console.println)
+ consume: Properties.T => Unit = Console.println): Unit =
{
- def progress_stdout(line: String)
+ def progress_stdout(line: String): Unit =
{
val props =
Library.space_explode(',', line).flatMap((entry: String) =>
Library.space_explode('=', entry) match {
case List(a, b) => Some((a, b))
case _ => None
})
if (props.nonEmpty) consume(props)
}
val env_prefix =
if (stats_dir.isEmpty) "" else "export POLYSTATSDIR=" + Bash.string(stats_dir) + "\n"
Bash.process(env_prefix + "exec \"$POLYML_EXE\" -q --use src/Pure/ML/ml_statistics.ML --eval " +
Bash.string("ML_Statistics.monitor " + ML_Syntax.print_long(pid) + " " +
ML_Syntax.print_double(delay.seconds)),
cwd = Path.explode("~~").file)
.result(progress_stdout = progress_stdout, strict = false).check
}
/* protocol handler */
class Handler extends Session.Protocol_Handler
{
private var session: Session = null
private var monitoring: Future[Unit] = Future.value(())
override def init(session: Session): Unit = synchronized
{
this.session = session
}
override def exit(): Unit = synchronized
{
session = null
monitoring.cancel
}
private def consume(props: Properties.T): Unit = synchronized
{
if (session != null) {
val props1 = (session.cache.props(props ::: Java_Statistics.jvm_statistics()))
session.runtime_statistics.post(Session.Runtime_Statistics(props1))
}
}
private def ml_statistics(msg: Prover.Protocol_Output): Boolean = synchronized
{
msg.properties match {
case Markup.ML_Statistics(pid, stats_dir) =>
monitoring =
Future.thread("ML_statistics") {
monitor(pid, stats_dir = stats_dir, consume = consume)
}
true
case _ => false
}
}
override val functions = List(Markup.ML_Statistics.name -> ml_statistics)
}
/* memory fields (mega bytes) */
def mem_print(x: Long): Option[String] =
if (x == 0L) None else Some(x.toString + " M")
def mem_scale(x: Long): Long = x / 1024 / 1024
def mem_field_scale(name: String, x: Double): Double =
if (heap_fields._2.contains(name) || program_fields._2.contains(name))
mem_scale(x.toLong).toDouble
else x
val CODE_SIZE = "size_code"
val STACK_SIZE = "size_stacks"
val HEAP_SIZE = "size_heap"
/* standard fields */
type Fields = (String, List[String])
val tasks_fields: Fields =
("Future tasks",
List("tasks_ready", "tasks_pending", "tasks_running", "tasks_passive",
"tasks_urgent", "tasks_total"))
val workers_fields: Fields =
("Worker threads", List("workers_total", "workers_active", "workers_waiting"))
val GC_fields: Fields =
("GCs", List("partial_GCs", "full_GCs", "share_passes"))
val heap_fields: Fields =
("Heap", List(HEAP_SIZE, "size_allocation", "size_allocation_free",
"size_heap_free_last_full_GC", "size_heap_free_last_GC"))
val program_fields: Fields =
("Program", List("size_code", "size_stacks"))
val threads_fields: Fields =
("Threads", List("threads_total", "threads_in_ML", "threads_wait_condvar",
"threads_wait_IO", "threads_wait_mutex", "threads_wait_signal"))
val time_fields: Fields =
("Time", List("time_elapsed", "time_elapsed_GC", "time_CPU", "time_GC"))
val speed_fields: Fields =
("Speed", List("speed_CPU", "speed_GC"))
private val time_speed = Map("time_CPU" -> "speed_CPU", "time_GC" -> "speed_GC")
val java_heap_fields: Fields =
("Java heap", List("java_heap_size", "java_heap_used"))
val java_thread_fields: Fields =
("Java threads", List("java_threads_total", "java_workers_total", "java_workers_active"))
val main_fields: List[Fields] =
List(heap_fields, tasks_fields, workers_fields)
val other_fields: List[Fields] =
List(threads_fields, GC_fields, program_fields, time_fields, speed_fields,
java_heap_fields, java_thread_fields)
val all_fields: List[Fields] = main_fields ::: other_fields
/* content interpretation */
final case class Entry(time: Double, data: Map[String, Double])
{
def get(field: String): Double = data.getOrElse(field, 0.0)
}
val empty: ML_Statistics = apply(Nil)
def apply(ml_statistics0: List[Properties.T], heading: String = "",
domain: String => Boolean = (key: String) => true): ML_Statistics =
{
require(ml_statistics0.forall(props => Now.unapply(props).isDefined), "missing \"now\" field")
val ml_statistics = ml_statistics0.sortBy(now)
val time_start = if (ml_statistics.isEmpty) 0.0 else now(ml_statistics.head)
val duration = if (ml_statistics.isEmpty) 0.0 else now(ml_statistics.last) - time_start
val fields =
SortedSet.empty[String] ++
(for {
props <- ml_statistics.iterator
(x, _) <- props.iterator
if x != Now.name && domain(x) } yield x)
val content =
{
var last_edge = Map.empty[String, (Double, Double, Double)]
val result = new mutable.ListBuffer[ML_Statistics.Entry]
for (props <- ml_statistics) {
val time = now(props) - time_start
// rising edges -- relative speed
val speeds =
(for {
(key, value) <- props.iterator
key1 <- time_speed.get(key)
if domain(key1)
} yield {
val (x0, y0, s0) = last_edge.getOrElse(key, (0.0, 0.0, 0.0))
val x1 = time
val y1 = java.lang.Double.parseDouble(value)
val s1 = if (x1 == x0) 0.0 else (y1 - y0) / (x1 - x0)
if (y1 > y0) {
last_edge += (key -> (x1, y1, s1))
(key1, s1.toString)
}
else (key1, s0.toString)
}).toList
val data =
SortedMap.empty[String, Double] ++
(for {
(x, y) <- props.iterator ++ speeds.iterator
if x != Now.name && domain(x)
z = java.lang.Double.parseDouble(y) if z != 0.0
} yield { (x.intern, mem_field_scale(x, z)) })
result += ML_Statistics.Entry(time, data)
}
result.toList
}
new ML_Statistics(heading, fields, content, time_start, duration)
}
}
final class ML_Statistics private(
val heading: String,
val fields: Set[String],
val content: List[ML_Statistics.Entry],
val time_start: Double,
val duration: Double)
{
/* content */
def maximum(field: String): Double =
(0.0 /: content)({ case (m, e) => m max e.get(field) })
def average(field: String): Double =
{
@tailrec def sum(t0: Double, list: List[ML_Statistics.Entry], acc: Double): Double =
list match {
case Nil => acc
case e :: es =>
val t = e.time
sum(t, es, (t - t0) * e.get(field) + acc)
}
content match {
case Nil => 0.0
case List(e) => e.get(field)
case e :: es => sum(e.time, es, 0.0) / duration
}
}
/* charts */
- def update_data(data: XYSeriesCollection, selected_fields: List[String])
+ def update_data(data: XYSeriesCollection, selected_fields: List[String]): Unit =
{
data.removeAllSeries
for (field <- selected_fields) {
val series = new XYSeries(field)
content.foreach(entry => series.add(entry.time, entry.get(field)))
data.addSeries(series)
}
}
def chart(title: String, selected_fields: List[String]): JFreeChart =
{
val data = new XYSeriesCollection
update_data(data, selected_fields)
ChartFactory.createXYLineChart(title, "time", "value", data,
PlotOrientation.VERTICAL, true, true, true)
}
def chart(fields: ML_Statistics.Fields): JFreeChart =
chart(fields._1, fields._2)
def show_frames(fields: List[ML_Statistics.Fields] = ML_Statistics.main_fields): Unit =
fields.map(chart).foreach(c =>
GUI_Thread.later {
new Frame {
iconImage = GUI.isabelle_image()
title = heading
contents = Component.wrap(new ChartPanel(c))
visible = true
}
})
}
diff --git a/src/Pure/PIDE/byte_message.scala b/src/Pure/PIDE/byte_message.scala
--- a/src/Pure/PIDE/byte_message.scala
+++ b/src/Pure/PIDE/byte_message.scala
@@ -1,116 +1,116 @@
/* Title: Pure/General/byte_message.scala
Author: Makarius
Byte-oriented messages.
*/
package isabelle
import java.io.{ByteArrayOutputStream, OutputStream, InputStream, IOException}
object Byte_Message
{
/* output operations */
def write(stream: OutputStream, bytes: List[Bytes]): Unit =
bytes.foreach(_.write_stream(stream))
def flush(stream: OutputStream): Unit =
try { stream.flush() }
catch { case _: IOException => }
def write_line(stream: OutputStream, bytes: Bytes): Unit =
{
write(stream, List(bytes, Bytes.newline))
flush(stream)
}
/* input operations */
def read(stream: InputStream, n: Int): Bytes =
Bytes.read_stream(stream, limit = n)
def read_block(stream: InputStream, n: Int): (Option[Bytes], Int) =
{
val msg = read(stream, n)
val len = msg.length
(if (len == n) Some(msg) else None, len)
}
def read_line(stream: InputStream): Option[Bytes] =
{
val line = new ByteArrayOutputStream(100)
var c = 0
while ({ c = stream.read; c != -1 && c != 10 }) line.write(c)
if (c == -1 && line.size == 0) None
else {
val a = line.toByteArray
val n = a.length
val len = if (n > 0 && a(n - 1) == 13) n - 1 else n
Some(Bytes(a, 0, len))
}
}
/* messages with multiple chunks (arbitrary content) */
private def make_header(ns: List[Int]): List[Bytes] =
List(Bytes(ns.mkString(",")), Bytes.newline)
- def write_message(stream: OutputStream, chunks: List[Bytes])
+ def write_message(stream: OutputStream, chunks: List[Bytes]): Unit =
{
write(stream, make_header(chunks.map(_.length)) ::: chunks)
flush(stream)
}
private def parse_header(line: String): List[Int] =
try { space_explode(',', line).map(Value.Nat.parse) }
catch { case ERROR(_) => error("Malformed message header: " + quote(line)) }
private def read_chunk(stream: InputStream, n: Int): Bytes =
read_block(stream, n) match {
case (Some(chunk), _) => chunk
case (None, len) =>
error("Malformed message chunk: unexpected EOF after " + len + " of " + n + " bytes")
}
def read_message(stream: InputStream): Option[List[Bytes]] =
read_line(stream).map(line => parse_header(line.text).map(read_chunk(stream, _)))
/* hybrid messages: line or length+block (restricted content) */
private def is_length(msg: Bytes): Boolean =
!msg.is_empty && msg.iterator.forall(b => Symbol.is_ascii_digit(b.toChar))
private def is_terminated(msg: Bytes): Boolean =
{
val len = msg.length
len > 0 && Symbol.is_ascii_line_terminator(msg.charAt(len - 1))
}
- def write_line_message(stream: OutputStream, msg: Bytes)
+ def write_line_message(stream: OutputStream, msg: Bytes): Unit =
{
if (is_length(msg) || is_terminated(msg))
error ("Bad content for line message:\n" ++ msg.text.take(100))
val n = msg.length
write(stream,
(if (n > 100 || msg.iterator.contains(10)) make_header(List(n + 1)) else Nil) :::
List(msg, Bytes.newline))
flush(stream)
}
def read_line_message(stream: InputStream): Option[Bytes] =
read_line(stream) match {
case None => None
case Some(line) =>
Value.Nat.unapply(line.text) match {
case None => Some(line)
case Some(n) => read_block(stream, n)._1.map(_.trim_line)
}
}
}
diff --git a/src/Pure/PIDE/command.ML b/src/Pure/PIDE/command.ML
--- a/src/Pure/PIDE/command.ML
+++ b/src/Pure/PIDE/command.ML
@@ -1,507 +1,509 @@
(* Title: Pure/PIDE/command.ML
Author: Makarius
Prover command execution: read -- eval -- print.
*)
signature COMMAND =
sig
type blob = {file_node: string, src_path: Path.T, content: (SHA1.digest * string list) option}
val read_file: Path.T -> Position.T -> bool -> Path.T -> Token.file
val read_thy: Toplevel.state -> theory
val read: Keyword.keywords -> theory -> Path.T-> (unit -> theory) ->
blob Exn.result list * int -> Token.T list -> Toplevel.transition
val read_span: Keyword.keywords -> Toplevel.state -> Path.T -> (unit -> theory) ->
Command_Span.span -> Toplevel.transition
type eval
val eval_command_id: eval -> Document_ID.command
val eval_exec_id: eval -> Document_ID.exec
val eval_eq: eval * eval -> bool
val eval_running: eval -> bool
val eval_finished: eval -> bool
val eval_result_command: eval -> Toplevel.transition
val eval_result_state: eval -> Toplevel.state
val eval: Keyword.keywords -> Path.T -> (unit -> theory) ->
blob Exn.result list * int -> Document_ID.command -> Token.T list -> eval -> eval
type print
type print_fn = Toplevel.transition -> Toplevel.state -> unit
val print0: {pri: int, print_fn: print_fn} -> eval -> print
val print: bool -> (string * string list) list -> Keyword.keywords -> string ->
eval -> print list -> print list option
val parallel_print: print -> bool
type print_function =
{keywords: Keyword.keywords, command_name: string, args: string list, exec_id: Document_ID.exec} ->
{delay: Time.time option, pri: int, persistent: bool, strict: bool, print_fn: print_fn} option
val print_function: string -> print_function -> unit
val no_print_function: string -> unit
type exec = eval * print list
val init_exec: theory option -> exec
val no_exec: exec
val exec_ids: exec option -> Document_ID.exec list
val exec: Document_ID.execution -> exec -> unit
val exec_parallel_prints: Document_ID.execution -> Future.task list -> exec -> exec option
end;
structure Command: COMMAND =
struct
(** main phases of execution **)
fun task_context group f =
f
|> Future.interruptible_task
|> Future.task_context "Command.run_process" group;
(* read *)
type blob = {file_node: string, src_path: Path.T, content: (SHA1.digest * string list) option};
fun read_file_node file_node master_dir pos delimited src_path =
let
val _ =
if Context_Position.pide_reports ()
then Position.report pos (Markup.language_path delimited) else ();
fun read_file () =
let
val path = File.check_file (File.full_path master_dir src_path);
val text = File.read path;
val file_pos = Path.position path;
in (text, file_pos) end;
fun read_url () =
let
- val text = Isabelle_System.download file_node;
+ val text =
+ Isabelle_System.with_tmp_file "file" "" (fn file =>
+ (Isabelle_System.download file_node file; File.read file));
val file_pos = Position.file file_node;
in (text, file_pos) end;
val (text, file_pos) =
(case try Url.explode file_node of
NONE => read_file ()
| SOME (Url.File _) => read_file ()
| _ => read_url ());
val lines = split_lines text;
val digest = SHA1.digest text;
in {src_path = src_path, lines = lines, digest = digest, pos = Position.copy_id pos file_pos} end
handle ERROR msg => error (msg ^ Position.here pos);
val read_file = read_file_node "";
local
fun blob_file src_path lines digest file_node =
let
val file_pos =
Position.file file_node |>
(case Position.get_id (Position.thread_data ()) of
NONE => I
| SOME exec_id => Position.put_id exec_id);
in {src_path = src_path, lines = lines, digest = digest, pos = file_pos} end
fun resolve_files master_dir (blobs, blobs_index) toks =
(case Outer_Syntax.parse_spans toks of
[Command_Span.Span (Command_Span.Command_Span _, _)] =>
(case try (nth toks) blobs_index of
SOME tok =>
let
val source = Token.input_of tok;
val pos = Input.pos_of source;
val delimited = Input.is_delimited source;
fun make_file (Exn.Res {file_node, src_path, content = NONE}) =
Exn.interruptible_capture (fn () =>
read_file_node file_node master_dir pos delimited src_path) ()
| make_file (Exn.Res {file_node, src_path, content = SOME (digest, lines)}) =
(Position.report pos (Markup.language_path delimited);
Exn.Res (blob_file src_path lines digest file_node))
| make_file (Exn.Exn e) = Exn.Exn e;
val files = map make_file blobs;
in
toks |> map_index (fn (i, tok) =>
if i = blobs_index then Token.put_files files tok else tok)
end
| NONE => toks)
| _ => toks);
fun reports_of_token keywords tok =
let
val malformed_symbols =
Input.source_explode (Token.input_of tok)
|> map_filter (fn (sym, pos) =>
if Symbol.is_malformed sym
then SOME ((pos, Markup.bad ()), "Malformed symbolic character") else NONE);
val is_malformed = Token.is_error tok orelse not (null malformed_symbols);
val reports = Token.reports keywords tok @ Token.completion_report tok @ malformed_symbols;
in (is_malformed, reports) end;
in
fun read_thy st = Toplevel.theory_of st
handle Toplevel.UNDEF => Pure_Syn.bootstrap_thy;
fun read keywords thy master_dir init blobs_info span =
let
val command_reports = Outer_Syntax.command_reports thy;
val token_reports = map (reports_of_token keywords) span;
val _ = Position.reports_text (maps #2 token_reports @ maps command_reports span);
val verbatim =
span |> map_filter (fn tok =>
if Token.kind_of tok = Token.Verbatim then SOME (Token.pos_of tok) else NONE);
val _ =
if null verbatim then ()
else legacy_feature ("Old-style {* verbatim *} token -- use \cartouche\ instead" ^
Position.here_list verbatim);
in
if exists #1 token_reports
then Toplevel.malformed (#1 (Token.core_range_of span)) "Malformed command syntax"
else Outer_Syntax.parse_span thy init (resolve_files master_dir blobs_info span)
end;
end;
fun read_span keywords st master_dir init =
Command_Span.content #> read keywords (read_thy st) master_dir init ([], ~1);
(* eval *)
type eval_state = {failed: bool, command: Toplevel.transition, state: Toplevel.state};
fun init_eval_state opt_thy =
{failed = false,
command = Toplevel.empty,
state =
(case opt_thy of
NONE => Toplevel.init_toplevel ()
| SOME thy => Toplevel.theory_toplevel thy)};
datatype eval =
Eval of
{command_id: Document_ID.command, exec_id: Document_ID.exec, eval_process: eval_state lazy};
fun eval_command_id (Eval {command_id, ...}) = command_id;
fun eval_exec_id (Eval {exec_id, ...}) = exec_id;
val eval_eq = op = o apply2 eval_exec_id;
val eval_running = Execution.is_running_exec o eval_exec_id;
fun eval_finished (Eval {eval_process, ...}) = Lazy.is_finished eval_process;
fun eval_result (Eval {eval_process, ...}) =
Exn.release (Lazy.finished_result eval_process);
val eval_result_command = #command o eval_result;
val eval_result_state = #state o eval_result;
local
fun reset_state keywords tr st0 = Toplevel.setmp_thread_position tr (fn () =>
let
val name = Toplevel.name_of tr;
val res =
if Keyword.is_theory_body keywords name then Toplevel.reset_theory st0
else if Keyword.is_proof keywords name then Toplevel.reset_proof st0
else if Keyword.is_theory_end keywords name then
(case Toplevel.reset_notepad st0 of
NONE => Toplevel.reset_theory st0
| some => some)
else NONE;
in
(case res of
NONE => st0
| SOME st => (Output.error_message (Toplevel.type_error tr ^ " -- using reset state"); st))
end) ();
fun run keywords int tr st =
if Future.proofs_enabled 1 andalso Keyword.is_diag keywords (Toplevel.name_of tr) then
let
val (tr1, tr2) = Toplevel.fork_presentation tr;
val _ =
Execution.fork {name = "Toplevel.diag", pos = Toplevel.pos_of tr, pri = ~1}
(fn () => Toplevel.command_exception int tr1 st);
in Toplevel.command_errors int tr2 st end
else Toplevel.command_errors int tr st;
fun check_token_comments ctxt tok =
(Thy_Output.check_comments ctxt (Input.source_explode (Token.input_of tok)); [])
handle exn =>
if Exn.is_interrupt exn then Exn.reraise exn
else Runtime.exn_messages exn;
fun check_span_comments ctxt span tr =
Toplevel.setmp_thread_position tr (fn () => maps (check_token_comments ctxt) span) ();
fun report_indent tr st =
(case try Toplevel.proof_of st of
SOME prf =>
let val keywords = Thy_Header.get_keywords (Proof.theory_of prf) in
if Keyword.command_kind keywords (Toplevel.name_of tr) = SOME Keyword.prf_script then
(case try (Thm.nprems_of o #goal o Proof.goal) prf of
NONE => ()
| SOME 0 => ()
| SOME n =>
let val report = Markup.markup_only (Markup.command_indent (n - 1));
in Toplevel.setmp_thread_position tr (fn () => Output.report [report]) () end)
else ()
end
| NONE => ());
fun status tr m =
Toplevel.setmp_thread_position tr (fn () => Output.status [Markup.markup_only m]) ();
fun eval_state keywords span tr ({state, ...}: eval_state) =
let
val _ = Thread_Attributes.expose_interrupt ();
val st = reset_state keywords tr state;
val _ = report_indent tr st;
val _ = status tr Markup.running;
val (errs1, result) = run keywords true tr st;
val errs2 =
(case result of
NONE => []
| SOME st' => check_span_comments (Toplevel.presentation_context st') span tr);
val errs = errs1 @ errs2;
val _ = List.app (Future.error_message (Toplevel.pos_of tr)) errs;
in
(case result of
NONE =>
let
val _ = status tr Markup.failed;
val _ = status tr Markup.finished;
val _ = if null errs then (status tr Markup.canceled; Exn.interrupt ()) else ();
in {failed = true, command = tr, state = st} end
| SOME st' =>
let
val _ =
if Keyword.is_theory_end keywords (Toplevel.name_of tr) andalso
can (Toplevel.end_theory Position.none) st'
then status tr Markup.finalized else ();
val _ = status tr Markup.finished;
in {failed = false, command = tr, state = st'} end)
end;
in
fun eval keywords master_dir init blobs_info command_id span eval0 =
let
val exec_id = Document_ID.make ();
fun process () =
let
val eval_state0 = eval_result eval0;
val thy = read_thy (#state eval_state0);
val tr =
Position.setmp_thread_data (Position.id_only (Document_ID.print exec_id))
(fn () =>
read keywords thy master_dir init blobs_info span |> Toplevel.exec_id exec_id) ();
in eval_state keywords span tr eval_state0 end;
in
Eval {command_id = command_id, exec_id = exec_id,
eval_process = Lazy.lazy_name "Command.eval" process}
end;
end;
(* print *)
datatype print = Print of
{name: string, args: string list, delay: Time.time option, pri: int, persistent: bool,
exec_id: Document_ID.exec, print_process: unit lazy};
fun print_exec_id (Print {exec_id, ...}) = exec_id;
val print_eq = op = o apply2 print_exec_id;
type print_fn = Toplevel.transition -> Toplevel.state -> unit;
type print_function =
{keywords: Keyword.keywords, command_name: string, args: string list, exec_id: Document_ID.exec} ->
{delay: Time.time option, pri: int, persistent: bool, strict: bool, print_fn: print_fn} option;
local
val print_functions =
Synchronized.var "Command.print_functions" ([]: (string * print_function) list);
fun print_error tr opt_context e =
(Toplevel.setmp_thread_position tr o Runtime.controlled_execution opt_context) e ()
handle exn =>
if Exn.is_interrupt exn then Exn.reraise exn
else List.app (Future.error_message (Toplevel.pos_of tr)) (Runtime.exn_messages exn);
fun print_finished (Print {print_process, ...}) = Lazy.is_finished print_process;
fun print_persistent (Print {persistent, ...}) = persistent;
val overlay_ord = prod_ord string_ord (list_ord string_ord);
fun make_print (name, args) {delay, pri, persistent, strict, print_fn} eval =
let
val exec_id = Document_ID.make ();
fun process () =
let
val {failed, command, state = st', ...} = eval_result eval;
val tr = Toplevel.exec_id exec_id command;
val opt_context = try Toplevel.generic_theory_of st';
in
if failed andalso strict then ()
else print_error tr opt_context (fn () => print_fn tr st')
end;
in
Print {
name = name, args = args, delay = delay, pri = pri, persistent = persistent,
exec_id = exec_id, print_process = Lazy.lazy_name "Command.print" process}
end;
fun bad_print name_args exn =
make_print name_args {delay = NONE, pri = 0, persistent = false,
strict = false, print_fn = fn _ => fn _ => Exn.reraise exn};
in
fun print0 {pri, print_fn} =
make_print ("", [serial_string ()])
{delay = NONE, pri = pri, persistent = true, strict = true, print_fn = print_fn};
fun print command_visible command_overlays keywords command_name eval old_prints =
let
val print_functions = Synchronized.value print_functions;
fun new_print (name, args) get_pr =
let
val params =
{keywords = keywords,
command_name = command_name,
args = args,
exec_id = eval_exec_id eval};
in
(case Exn.capture (Runtime.controlled_execution NONE get_pr) params of
Exn.Res NONE => NONE
| Exn.Res (SOME pr) => SOME (make_print (name, args) pr eval)
| Exn.Exn exn => SOME (bad_print (name, args) exn eval))
end;
fun get_print (name, args) =
(case find_first (fn Print print => (#name print, #args print) = (name, args)) old_prints of
NONE =>
(case AList.lookup (op =) print_functions name of
NONE =>
SOME (bad_print (name, args) (ERROR ("Missing print function " ^ quote name)) eval)
| SOME get_pr => new_print (name, args) get_pr)
| some => some);
val retained_prints =
filter (fn print => print_finished print andalso print_persistent print) old_prints;
val visible_prints =
if command_visible then
fold (fn (name, _) => cons (name, [])) print_functions command_overlays
|> sort_distinct overlay_ord
|> map_filter get_print
else [];
val new_prints = visible_prints @ retained_prints;
in
if eq_list print_eq (old_prints, new_prints) then NONE else SOME new_prints
end;
fun parallel_print (Print {pri, ...}) =
pri <= 0 orelse (Future.enabled () andalso Options.default_bool "parallel_print");
fun print_function name f =
Synchronized.change print_functions (fn funs =>
(if name = "" then error "Unnamed print function" else ();
if not (AList.defined (op =) funs name) then ()
else warning ("Redefining command print function: " ^ quote name);
AList.update (op =) (name, f) funs));
fun no_print_function name =
Synchronized.change print_functions (filter_out (equal name o #1));
end;
val _ =
print_function "Execution.print"
(fn {args, exec_id, ...} =>
if null args then
SOME {delay = NONE, pri = Task_Queue.urgent_pri + 2, persistent = false, strict = false,
print_fn = fn _ => fn _ => Execution.fork_prints exec_id}
else NONE);
val _ =
print_function "print_state"
(fn {keywords, command_name, ...} =>
if Options.default_bool "editor_output_state" andalso Keyword.is_printed keywords command_name
then
SOME {delay = NONE, pri = Task_Queue.urgent_pri + 1, persistent = false, strict = false,
print_fn = fn _ => fn st =>
if Toplevel.is_proof st then Output.state (Toplevel.string_of_state st)
else ()}
else NONE);
(* combined execution *)
type exec = eval * print list;
fun init_exec opt_thy : exec =
(Eval
{command_id = Document_ID.none, exec_id = Document_ID.none,
eval_process = Lazy.value (init_eval_state opt_thy)}, []);
val no_exec = init_exec NONE;
fun exec_ids NONE = []
| exec_ids (SOME (eval, prints)) = eval_exec_id eval :: map print_exec_id prints;
local
fun run_process execution_id exec_id process =
let val group = Future.worker_subgroup () in
if Execution.running execution_id exec_id [group] then
ignore (task_context group (fn () => Lazy.force_result {strict = true} process) ())
else ()
end;
fun ignore_process process =
Lazy.is_running process orelse Lazy.is_finished process;
fun run_eval execution_id (Eval {exec_id, eval_process, ...}) =
if Lazy.is_finished eval_process then ()
else run_process execution_id exec_id eval_process;
fun fork_print execution_id deps (Print {name, delay, pri, exec_id, print_process, ...}) =
let
val group = Future.worker_subgroup ();
fun fork () =
ignore ((singleton o Future.forks)
{name = name, group = SOME group, deps = deps, pri = pri, interrupts = true}
(fn () =>
if ignore_process print_process then ()
else run_process execution_id exec_id print_process));
in
(case delay of
NONE => fork ()
| SOME d => ignore (Event_Timer.request {physical = true} (Time.now () + d) fork))
end;
fun run_print execution_id (print as Print {exec_id, print_process, ...}) =
if ignore_process print_process then ()
else if parallel_print print then fork_print execution_id [] print
else run_process execution_id exec_id print_process;
in
fun exec execution_id (eval, prints) =
(run_eval execution_id eval; List.app (run_print execution_id) prints);
fun exec_parallel_prints execution_id deps (exec as (Eval {eval_process, ...}, prints)) =
if Lazy.is_finished eval_process
then (List.app (fork_print execution_id deps) prints; NONE)
else SOME exec;
end;
end;
diff --git a/src/Pure/PIDE/document.scala b/src/Pure/PIDE/document.scala
--- a/src/Pure/PIDE/document.scala
+++ b/src/Pure/PIDE/document.scala
@@ -1,1264 +1,1264 @@
/* Title: Pure/PIDE/document.scala
Author: Makarius
Document as collection of named nodes, each consisting of an editable
list of commands, associated with asynchronous execution process.
*/
package isabelle
import scala.collection.mutable
object Document
{
/** document structure **/
/* overlays -- print functions with arguments */
object Overlays
{
val empty = new Overlays(Map.empty)
}
final class Overlays private(rep: Map[Node.Name, Node.Overlays])
{
def apply(name: Node.Name): Node.Overlays =
rep.getOrElse(name, Node.Overlays.empty)
private def update(name: Node.Name, f: Node.Overlays => Node.Overlays): Overlays =
{
val node_overlays = f(apply(name))
new Overlays(if (node_overlays.is_empty) rep - name else rep + (name -> node_overlays))
}
def insert(command: Command, fn: String, args: List[String]): Overlays =
update(command.node_name, _.insert(command, fn, args))
def remove(command: Command, fn: String, args: List[String]): Overlays =
update(command.node_name, _.remove(command, fn, args))
override def toString: String = rep.mkString("Overlays(", ",", ")")
}
/* document blobs: auxiliary files */
sealed case class Blob(bytes: Bytes, source: String, chunk: Symbol.Text_Chunk, changed: Boolean)
{
def unchanged: Blob = copy(changed = false)
}
object Blobs
{
def apply(blobs: Map[Node.Name, Blob]): Blobs = new Blobs(blobs)
val empty: Blobs = apply(Map.empty)
}
final class Blobs private(blobs: Map[Node.Name, Blob])
{
def get(name: Node.Name): Option[Blob] = blobs.get(name)
def changed(name: Node.Name): Boolean =
get(name) match {
case Some(blob) => blob.changed
case None => false
}
override def toString: String = blobs.mkString("Blobs(", ",", ")")
}
/* document nodes: theories and auxiliary files */
type Edit[A, B] = (Node.Name, Node.Edit[A, B])
type Edit_Text = Edit[Text.Edit, Text.Perspective]
type Edit_Command = Edit[Command.Edit, Command.Perspective]
object Node
{
/* header and name */
sealed case class Header(
imports_pos: List[(Name, Position.T)] = Nil,
keywords: Thy_Header.Keywords = Nil,
abbrevs: Thy_Header.Abbrevs = Nil,
errors: List[String] = Nil)
{
def imports_offset: Map[Int, Name] =
(for { (name, Position.Offset(i)) <- imports_pos } yield i -> name).toMap
def imports: List[Name] = imports_pos.map(_._1)
def append_errors(msgs: List[String]): Header =
copy(errors = errors ::: msgs)
def cat_errors(msg2: String): Header =
copy(errors = errors.map(msg1 => Exn.cat_message(msg1, msg2)))
}
val no_header: Header = Header()
def bad_header(msg: String): Header = Header(errors = List(msg))
object Name
{
val empty: Name = Name("")
object Ordering extends scala.math.Ordering[Name]
{
def compare(name1: Name, name2: Name): Int = name1.node compare name2.node
}
type Graph[A] = isabelle.Graph[Node.Name, A]
def make_graph[A](entries: List[((Name, A), List[Name])]): Graph[A] =
Graph.make(entries, symmetric = true)(Ordering)
}
sealed case class Name(node: String, master_dir: String = "", theory: String = "")
{
override def hashCode: Int = node.hashCode
override def equals(that: Any): Boolean =
that match {
case other: Name => node == other.node
case _ => false
}
def path: Path = Path.explode(File.standard_path(node))
def master_dir_path: Path = Path.explode(File.standard_path(master_dir))
def expand: Name =
Name(path.expand.implode, master_dir_path.expand.implode, theory)
def symbolic: Name =
Name(path.implode_symbolic, master_dir_path.implode_symbolic, theory)
def is_theory: Boolean = theory.nonEmpty
def theory_base_name: String = Long_Name.base_name(theory)
override def toString: String = if (is_theory) theory else node
def map(f: String => String): Name = copy(f(node), f(master_dir), theory)
def map_theory(f: String => String): Name = copy(node, master_dir, f(theory))
def json: JSON.Object.T =
JSON.Object("node_name" -> node, "theory_name" -> theory)
}
sealed case class Entry(name: Node.Name, header: Node.Header)
{
def map(f: String => String): Entry = copy(name = name.map(f))
override def toString: String = name.toString
}
/* node overlays */
object Overlays
{
val empty = new Overlays(Multi_Map.empty)
}
final class Overlays private(rep: Multi_Map[Command, (String, List[String])])
{
def commands: Set[Command] = rep.keySet
def is_empty: Boolean = rep.isEmpty
def dest: List[(Command, (String, List[String]))] = rep.iterator.toList
def insert(cmd: Command, fn: String, args: List[String]): Overlays =
new Overlays(rep.insert(cmd, (fn, args)))
def remove(cmd: Command, fn: String, args: List[String]): Overlays =
new Overlays(rep.remove(cmd, (fn, args)))
override def toString: String = rep.mkString("Node.Overlays(", ",", ")")
}
/* edits */
sealed abstract class Edit[A, B]
{
- def foreach(f: A => Unit)
+ def foreach(f: A => Unit): Unit =
{
this match {
case Edits(es) => es.foreach(f)
case _ =>
}
}
def is_void: Boolean =
this match {
case Edits(Nil) => true
case _ => false
}
}
case class Blob[A, B](blob: Document.Blob) extends Edit[A, B]
case class Edits[A, B](edits: List[A]) extends Edit[A, B]
case class Deps[A, B](header: Header) extends Edit[A, B]
case class Perspective[A, B](required: Boolean, visible: B, overlays: Overlays) extends Edit[A, B]
/* perspective */
type Perspective_Text = Perspective[Text.Edit, Text.Perspective]
type Perspective_Command = Perspective[Command.Edit, Command.Perspective]
val no_perspective_text: Perspective_Text =
Perspective(false, Text.Perspective.empty, Overlays.empty)
val no_perspective_command: Perspective_Command =
Perspective(false, Command.Perspective.empty, Overlays.empty)
def is_no_perspective_text(perspective: Perspective_Text): Boolean =
!perspective.required &&
perspective.visible.is_empty &&
perspective.overlays.is_empty
def is_no_perspective_command(perspective: Perspective_Command): Boolean =
!perspective.required &&
perspective.visible.is_empty &&
perspective.overlays.is_empty
/* commands */
object Commands
{
def apply(commands: Linear_Set[Command]): Commands = new Commands(commands)
val empty: Commands = apply(Linear_Set.empty)
def starts(commands: Iterator[Command], offset: Text.Offset = 0)
: Iterator[(Command, Text.Offset)] =
{
var i = offset
for (command <- commands) yield {
val start = i
i += command.length
(command, start)
}
}
def starts_pos(commands: Iterator[Command], pos: Token.Pos = Token.Pos.start)
: Iterator[(Command, Token.Pos)] =
{
var p = pos
for (command <- commands) yield {
val start = p
p = (p /: command.span.content)(_.advance(_))
(command, start)
}
}
private val block_size = 256
}
final class Commands private(val commands: Linear_Set[Command])
{
lazy val load_commands: List[Command] =
commands.iterator.filter(cmd => cmd.blobs.nonEmpty).toList
private lazy val full_index: (Array[(Command, Text.Offset)], Text.Range) =
{
val blocks = new mutable.ListBuffer[(Command, Text.Offset)]
var next_block = 0
var last_stop = 0
for ((command, start) <- Commands.starts(commands.iterator)) {
last_stop = start + command.length
while (last_stop + 1 > next_block) {
blocks += (command -> start)
next_block += Commands.block_size
}
}
(blocks.toArray, Text.Range(0, last_stop))
}
private def full_range: Text.Range = full_index._2
def iterator(i: Text.Offset = 0): Iterator[(Command, Text.Offset)] =
{
if (commands.nonEmpty && full_range.contains(i)) {
val (cmd0, start0) = full_index._1(i / Commands.block_size)
Node.Commands.starts(commands.iterator(cmd0), start0) dropWhile {
case (cmd, start) => start + cmd.length <= i }
}
else Iterator.empty
}
}
val empty: Node = new Node()
}
final class Node private(
val get_blob: Option[Document.Blob] = None,
val header: Node.Header = Node.no_header,
val syntax: Option[Outer_Syntax] = None,
val text_perspective: Text.Perspective = Text.Perspective.empty,
val perspective: Node.Perspective_Command = Node.no_perspective_command,
_commands: Node.Commands = Node.Commands.empty)
{
def is_empty: Boolean =
get_blob.isEmpty &&
header == Node.no_header &&
text_perspective.is_empty &&
Node.is_no_perspective_command(perspective) &&
commands.isEmpty
def has_header: Boolean = header != Node.no_header
override def toString: String =
if (is_empty) "empty"
else if (get_blob.isDefined) "blob"
else "node"
def commands: Linear_Set[Command] = _commands.commands
def load_commands: List[Command] = _commands.load_commands
def load_commands_changed(doc_blobs: Blobs): Boolean =
load_commands.exists(_.blobs_changed(doc_blobs))
def init_blob(blob: Blob): Node = new Node(get_blob = Some(blob.unchanged))
def update_header(new_header: Node.Header): Node =
new Node(get_blob, new_header, syntax, text_perspective, perspective, _commands)
def update_syntax(new_syntax: Option[Outer_Syntax]): Node =
new Node(get_blob, header, new_syntax, text_perspective, perspective, _commands)
def update_perspective(
new_text_perspective: Text.Perspective,
new_perspective: Node.Perspective_Command): Node =
new Node(get_blob, header, syntax, new_text_perspective, new_perspective, _commands)
def edit_perspective: Node.Edit[Text.Edit, Text.Perspective] =
Node.Perspective(perspective.required, text_perspective, perspective.overlays)
def same_perspective(
other_text_perspective: Text.Perspective,
other_perspective: Node.Perspective_Command): Boolean =
text_perspective == other_text_perspective &&
perspective.required == other_perspective.required &&
perspective.visible.same(other_perspective.visible) &&
perspective.overlays == other_perspective.overlays
def update_commands(new_commands: Linear_Set[Command]): Node =
if (new_commands eq _commands.commands) this
else
new Node(get_blob, header, syntax, text_perspective, perspective,
Node.Commands(new_commands))
def command_iterator(i: Text.Offset = 0): Iterator[(Command, Text.Offset)] =
_commands.iterator(i)
def command_iterator(range: Text.Range): Iterator[(Command, Text.Offset)] =
command_iterator(range.start) takeWhile { case (_, start) => start < range.stop }
def command_start(cmd: Command): Option[Text.Offset] =
Node.Commands.starts(commands.iterator).find(_._1 == cmd).map(_._2)
def source: String =
get_blob match {
case Some(blob) => blob.source
case None => command_iterator(0).map({ case (cmd, _) => cmd.source }).mkString
}
}
/* development graph */
object Nodes
{
val empty: Nodes = new Nodes(Graph.empty(Node.Name.Ordering))
}
final class Nodes private(graph: Graph[Node.Name, Node])
{
def apply(name: Node.Name): Node =
graph.default_node(name, Node.empty).get_node(name)
def is_suppressed(name: Node.Name): Boolean =
{
val graph1 = graph.default_node(name, Node.empty)
graph1.is_maximal(name) && graph1.get_node(name).is_empty
}
def purge_suppressed: Option[Nodes] =
graph.keys_iterator.filter(is_suppressed).toList match {
case Nil => None
case del => Some(new Nodes((graph /: del)(_.del_node(_))))
}
def + (entry: (Node.Name, Node)): Nodes =
{
val (name, node) = entry
val imports = node.header.imports
val graph1 =
(graph.default_node(name, Node.empty) /: imports)((g, p) => g.default_node(p, Node.empty))
val graph2 = (graph1 /: graph1.imm_preds(name))((g, dep) => g.del_edge(dep, name))
val graph3 = (graph2 /: imports)((g, dep) => g.add_edge(dep, name))
new Nodes(graph3.map_node(name, _ => node))
}
def domain: Set[Node.Name] = graph.domain
def iterator: Iterator[(Node.Name, Node)] =
graph.iterator.map({ case (name, (node, _)) => (name, node) })
def theory_name(theory: String): Option[Node.Name] =
graph.keys_iterator.find(name => name.theory == theory)
def commands_loading(file_name: Node.Name): List[Command] =
(for {
(_, node) <- iterator
cmd <- node.load_commands.iterator
name <- cmd.blobs_names.iterator
if name == file_name
} yield cmd).toList
def descendants(names: List[Node.Name]): List[Node.Name] = graph.all_succs(names)
def requirements(names: List[Node.Name]): List[Node.Name] = graph.all_preds_rev(names)
def topological_order: List[Node.Name] = graph.topological_order
override def toString: String = topological_order.mkString("Nodes(", ",", ")")
}
/** versioning **/
/* particular document versions */
object Version
{
val init: Version = new Version()
def make(nodes: Nodes): Version = new Version(Document_ID.make(), nodes)
def purge_future(versions: Map[Document_ID.Version, Version], future: Future[Version])
: Future[Version] =
{
if (future.is_finished) {
val version = future.join
versions.get(version.id) match {
case Some(version1) if !(version eq version1) => Future.value(version1)
case _ => future
}
}
else future
}
def purge_suppressed(
versions: Map[Document_ID.Version, Version]): Map[Document_ID.Version, Version] =
{
(versions /:
(for ((id, v) <- versions.iterator; v1 <- v.purge_suppressed) yield (id, v1)))(_ + _)
}
}
final class Version private(
val id: Document_ID.Version = Document_ID.none,
val nodes: Nodes = Nodes.empty)
{
override def toString: String = "Version(" + id + ")"
def purge_suppressed: Option[Version] =
nodes.purge_suppressed.map(new Version(id, _))
}
/* changes of plain text, eventually resulting in document edits */
object Change
{
val init: Change = new Change()
def make(previous: Future[Version], edits: List[Edit_Text], version: Future[Version]): Change =
new Change(Some(previous), edits.reverse, version)
}
final class Change private(
val previous: Option[Future[Version]] = Some(Future.value(Version.init)),
val rev_edits: List[Edit_Text] = Nil,
val version: Future[Version] = Future.value(Version.init))
{
def is_finished: Boolean =
(previous match { case None => true case Some(future) => future.is_finished }) &&
version.is_finished
def truncate: Change = new Change(None, Nil, version)
def purge(versions: Map[Document_ID.Version, Version]): Option[Change] =
{
val previous1 = previous.map(Version.purge_future(versions, _))
val version1 = Version.purge_future(versions, version)
if ((previous eq previous1) && (version eq version1)) None
else Some(new Change(previous1, rev_edits, version1))
}
}
/* history navigation */
object History
{
val init: History = new History()
}
final class History private(
val undo_list: List[Change] = List(Change.init)) // non-empty list
{
override def toString: String = "History(" + undo_list.length + ")"
def tip: Change = undo_list.head
def + (change: Change): History = new History(change :: undo_list)
def prune(check: Change => Boolean, retain: Int): Option[(List[Change], History)] =
{
val n = undo_list.iterator.zipWithIndex.find(p => check(p._1)).get._2 + 1
val (retained, dropped) = undo_list.splitAt(n max retain)
retained.splitAt(retained.length - 1) match {
case (prefix, List(last)) => Some(dropped, new History(prefix ::: List(last.truncate)))
case _ => None
}
}
def purge(versions: Map[Document_ID.Version, Version]): History =
{
val undo_list1 = undo_list.map(_.purge(versions))
if (undo_list1.forall(_.isEmpty)) this
else new History(for ((a, b) <- undo_list1 zip undo_list) yield a.getOrElse(b))
}
}
/* snapshot: persistent user-view of document state */
object Snapshot
{
val init: Snapshot = State.init.snapshot()
}
class Snapshot private[Document](
val state: State,
val version: Version,
val node_name: Node.Name,
edits: List[Text.Edit],
val snippet_command: Option[Command])
{
override def toString: String =
"Snapshot(node = " + node_name.node + ", version = " + version.id +
(if (is_outdated) ", outdated" else "") + ")"
/* nodes */
def get_node(name: Node.Name): Node = version.nodes(name)
val node: Node = get_node(node_name)
def node_files: List[Node.Name] =
node_name :: node.load_commands.flatMap(_.blobs_names)
/* edits */
def is_outdated: Boolean = edits.nonEmpty
private lazy val reverse_edits = edits.reverse
def convert(offset: Text.Offset): Text.Offset =
(offset /: edits)((i, edit) => edit.convert(i))
def revert(offset: Text.Offset): Text.Offset =
(offset /: reverse_edits)((i, edit) => edit.revert(i))
def convert(range: Text.Range): Text.Range = range.map(convert)
def revert(range: Text.Range): Text.Range = range.map(revert)
/* theory load commands */
val commands_loading: List[Command] =
if (node_name.is_theory) Nil
else version.nodes.commands_loading(node_name)
def commands_loading_ranges(pred: Node.Name => Boolean): List[Text.Range] =
(for {
cmd <- node.load_commands.iterator
blob_name <- cmd.blobs_names.iterator
if pred(blob_name)
start <- node.command_start(cmd)
} yield convert(cmd.core_range + start)).toList
/* command as add-on snippet */
def snippet(command: Command): Snapshot =
{
val node_name = command.node_name
val nodes0 = version.nodes
val nodes1 = nodes0 + (node_name -> nodes0(node_name).update_commands(Linear_Set(command)))
val version1 = Document.Version.make(nodes1)
val edits: List[Edit_Text] =
List(node_name -> Node.Edits(List(Text.Edit.insert(0, command.source))))
val state0 = state.define_command(command)
val state1 =
state0.continue_history(Future.value(version), edits, Future.value(version1))
.define_version(version1, state0.the_assignment(version))
.assign(version1.id, Nil, List(command.id -> List(Document_ID.make())))._2
state1.snapshot(node_name = node_name, snippet_command = Some(command))
}
/* XML markup */
def xml_markup(
range: Text.Range = Text.Range.full,
elements: Markup.Elements = Markup.Elements.full): XML.Body =
state.xml_markup(version, node_name, range = range, elements = elements)
def xml_markup_blobs(elements: Markup.Elements = Markup.Elements.full)
: List[(Path, XML.Body)] =
{
snippet_command match {
case None => Nil
case Some(command) =>
for (Exn.Res(blob) <- command.blobs)
yield {
val bytes = blob.read_file
val text = bytes.text
val xml =
if (Bytes(text) == bytes) {
val markup = command.init_markups(Command.Markup_Index.blob(blob))
markup.to_XML(Text.Range(0, text.length), text, elements)
}
else Nil
blob.src_path -> xml
}
}
}
/* messages */
lazy val messages: List[(XML.Elem, Position.T)] =
(for {
(command, start) <-
Document.Node.Commands.starts_pos(
node.commands.iterator, Token.Pos.file(node_name.node))
pos = command.span.keyword_pos(start).position(command.span.name)
(_, elem) <- state.command_results(version, command).iterator
} yield (elem, pos)).toList
/* exports */
lazy val exports: List[Export.Entry] =
state.node_exports(version, node_name).iterator.map(_._2).toList
lazy val exports_map: Map[String, Export.Entry] =
(for (entry <- exports.iterator) yield (entry.name, entry)).toMap
/* find command */
def find_command(id: Document_ID.Generic): Option[(Node, Command)] =
state.lookup_id(id) match {
case None => None
case Some(st) =>
val command = st.command
val command_node = get_node(command.node_name)
if (command_node.commands.contains(command)) Some((command_node, command)) else None
}
def find_command_position(id: Document_ID.Generic, offset: Symbol.Offset)
: Option[Line.Node_Position] =
for ((node, command) <- find_command(id))
yield {
val name = command.node_name.node
val sources_iterator =
node.commands.iterator.takeWhile(_ != command).map(_.source) ++
(if (offset == 0) Iterator.empty
else Iterator.single(command.source(Text.Range(0, command.chunk.decode(offset)))))
val pos = (Line.Position.zero /: sources_iterator)(_.advance(_))
Line.Node_Position(name, pos)
}
def current_command(other_node_name: Node.Name, offset: Text.Offset): Option[Command] =
if (other_node_name.is_theory) {
val other_node = get_node(other_node_name)
val iterator = other_node.command_iterator(revert(offset) max 0)
if (iterator.hasNext) {
- val (command0, _) = iterator.next
+ val (command0, _) = iterator.next()
other_node.commands.reverse.iterator(command0).find(command => !command.is_ignored)
}
else other_node.commands.reverse.iterator.find(command => !command.is_ignored)
}
else version.nodes.commands_loading(other_node_name).headOption
/* command results */
def command_results(range: Text.Range): Command.Results =
Command.State.merge_results(
select[List[Command.State]](range, Markup.Elements.full, command_states =>
{ case _ => Some(command_states) }).flatMap(_.info))
def command_results(command: Command): Command.Results =
state.command_results(version, command)
/* command ids: static and dynamic */
def command_id_map: Map[Document_ID.Generic, Command] =
state.command_id_map(version, get_node(node_name).commands)
/* cumulate markup */
def cumulate[A](
range: Text.Range,
info: A,
elements: Markup.Elements,
result: List[Command.State] => (A, Text.Markup) => Option[A],
status: Boolean = false): List[Text.Info[A]] =
{
val former_range = revert(range).inflate_singularity
val (chunk_name, command_iterator) =
commands_loading.headOption match {
case None => (Symbol.Text_Chunk.Default, node.command_iterator(former_range))
case Some(command) => (Symbol.Text_Chunk.File(node_name.node), Iterator((command, 0)))
}
val markup_index = Command.Markup_Index(status, chunk_name)
(for {
(command, command_start) <- command_iterator
chunk <- command.chunks.get(chunk_name).iterator
states = state.command_states(version, command)
res = result(states)
markup_range <- (former_range - command_start).try_restrict(chunk.range).iterator
markup = Command.State.merge_markup(states, markup_index, markup_range, elements)
Text.Info(r0, a) <- markup.cumulate[A](markup_range, info, elements,
{
case (a, Text.Info(r0, b)) => res(a, Text.Info(convert(r0 + command_start), b))
}).iterator
r1 <- convert(r0 + command_start).try_restrict(range).iterator
} yield Text.Info(r1, a)).toList
}
def select[A](
range: Text.Range,
elements: Markup.Elements,
result: List[Command.State] => Text.Markup => Option[A],
status: Boolean = false): List[Text.Info[A]] =
{
def result1(states: List[Command.State]): (Option[A], Text.Markup) => Option[Option[A]] =
{
val res = result(states)
(_: Option[A], x: Text.Markup) =>
res(x) match {
case None => None
case some => Some(some)
}
}
for (Text.Info(r, Some(x)) <- cumulate(range, None, elements, result1, status))
yield Text.Info(r, x)
}
}
/* model */
trait Session
{
def resources: Resources
}
trait Model
{
def session: Session
def is_stable: Boolean
def snapshot(): Snapshot
def node_name: Node.Name
def is_theory: Boolean = node_name.is_theory
override def toString: String = node_name.toString
def get_text(range: Text.Range): Option[String]
def node_required: Boolean
def get_blob: Option[Blob]
def bibtex_entries: List[Text.Info[String]]
def node_edits(
node_header: Node.Header,
text_edits: List[Text.Edit],
perspective: Node.Perspective_Text): List[Edit_Text] =
{
val edits: List[Node.Edit[Text.Edit, Text.Perspective]] =
get_blob match {
case None =>
List(
Node.Deps(
if (session.resources.session_base.loaded_theory(node_name)) {
node_header.append_errors(
List("Cannot update finished theory " + quote(node_name.theory)))
}
else node_header),
Node.Edits(text_edits), perspective)
case Some(blob) => List(Node.Blob(blob), Node.Edits(text_edits))
}
edits.flatMap(edit => if (edit.is_void) None else Some(node_name -> edit))
}
}
/** global state -- document structure, execution process, editing history **/
type Assign_Update =
List[(Document_ID.Command, List[Document_ID.Exec])] // update of exec state assignment
object State
{
class Fail(state: State) extends Exception
object Assignment
{
val init: Assignment = new Assignment()
}
final class Assignment private(
val command_execs: Map[Document_ID.Command, List[Document_ID.Exec]] = Map.empty,
val is_finished: Boolean = false)
{
override def toString: String = "Assignment(" + command_execs.size + "," + is_finished + ")"
def check_finished: Assignment = { require(is_finished, "assignment not finished"); this }
def unfinished: Assignment = new Assignment(command_execs, false)
def assign(update: Assign_Update): Assignment =
{
require(!is_finished, "assignment already finished")
val command_execs1 =
(command_execs /: update) {
case (res, (command_id, exec_ids)) =>
if (exec_ids.isEmpty) res - command_id
else res + (command_id -> exec_ids)
}
new Assignment(command_execs1, true)
}
}
val init: State =
State().define_version(Version.init, Assignment.init).assign(Version.init.id, Nil, Nil)._2
}
final case class State private(
/*reachable versions*/
versions: Map[Document_ID.Version, Version] = Map.empty,
/*inlined auxiliary files*/
blobs: Set[SHA1.Digest] = Set.empty,
/*loaded theories in batch builds*/
theories: Map[Document_ID.Exec, Command.State] = Map.empty,
/*static markup from define_command*/
commands: Map[Document_ID.Command, Command.State] = Map.empty,
/*dynamic markup from execution*/
execs: Map[Document_ID.Exec, Command.State] = Map.empty,
/*command-exec assignment for each version*/
assignments: Map[Document_ID.Version, State.Assignment] = Map.empty,
/*commands with markup produced by other commands (imm_succs)*/
commands_redirection: Graph[Document_ID.Command, Unit] = Graph.long,
/*explicit (linear) history*/
history: History = History.init,
/*intermediate state between remove_versions/removed_versions*/
removing_versions: Boolean = false)
{
override def toString: String =
"State(versions = " + versions.size +
", blobs = " + blobs.size +
", commands = " + commands.size +
", execs = " + execs.size +
", assignments = " + assignments.size +
", commands_redirection = " + commands_redirection.size +
", history = " + history.undo_list.size +
", removing_versions = " + removing_versions + ")"
private def fail[A]: A = throw new State.Fail(this)
def define_version(version: Version, assignment: State.Assignment): State =
{
val id = version.id
copy(versions = versions + (id -> version),
assignments = assignments + (id -> assignment.unfinished))
}
def define_blob(digest: SHA1.Digest): State = copy(blobs = blobs + digest)
def defined_blob(digest: SHA1.Digest): Boolean = blobs.contains(digest)
def define_command(command: Command): State =
{
val id = command.id
if (commands.isDefinedAt(id)) fail
else copy(commands = commands + (id -> command.init_state))
}
def defined_command(id: Document_ID.Command): Boolean = commands.isDefinedAt(id)
def the_version(id: Document_ID.Version): Version = versions.getOrElse(id, fail)
def the_static_state(id: Document_ID.Command): Command.State = commands.getOrElse(id, fail)
def the_dynamic_state(id: Document_ID.Exec): Command.State = execs.getOrElse(id, fail)
def the_assignment(version: Version): State.Assignment = assignments.getOrElse(version.id, fail)
def lookup_id(id: Document_ID.Generic): Option[Command.State] =
theories.get(id) orElse commands.get(id) orElse execs.get(id)
private def self_id(st: Command.State)(id: Document_ID.Generic): Boolean =
id == st.command.id ||
(execs.get(id) match { case Some(st1) => st1.command.id == st.command.id case None => false })
private def other_id(node_name: Node.Name, id: Document_ID.Generic)
: Option[(Symbol.Text_Chunk.Id, Symbol.Text_Chunk)] =
{
for {
st <- lookup_id(id)
if st.command.node_name == node_name
} yield (Symbol.Text_Chunk.Id(st.command.id), st.command.chunk)
}
private def redirection(st: Command.State): Graph[Document_ID.Command, Unit] =
(commands_redirection /: st.markups.redirection_iterator)({ case (graph, id) =>
graph.default_node(id, ()).default_node(st.command.id, ()).add_edge(id, st.command.id) })
def accumulate(id: Document_ID.Generic, message: XML.Elem, cache: XML.Cache)
: (Command.State, State) =
{
def update(st: Command.State): (Command.State, State) =
{
val st1 = st.accumulate(self_id(st), other_id, message, cache)
(st1, copy(commands_redirection = redirection(st1)))
}
execs.get(id).map(update) match {
case Some((st1, state1)) => (st1, state1.copy(execs = execs + (id -> st1)))
case None =>
commands.get(id).map(update) match {
case Some((st1, state1)) => (st1, state1.copy(commands = commands + (id -> st1)))
case None =>
theories.get(id).map(update) match {
case Some((st1, state1)) => (st1, state1.copy(theories = theories + (id -> st1)))
case None => fail
}
}
}
}
def add_export(id: Document_ID.Generic, entry: Command.Exports.Entry): (Command.State, State) =
{
execs.get(id) match {
case Some(st) =>
st.add_export(entry) match {
case Some(new_st) => (new_st, copy(execs = execs + (id -> new_st)))
case None => fail
}
case None =>
commands.get(id) match {
case Some(st) =>
st.add_export(entry) match {
case Some(new_st) => (new_st, copy(commands = commands + (id -> new_st)))
case None => fail
}
case None => fail
}
}
}
def node_exports(version: Version, node_name: Node.Name): Command.Exports =
Command.Exports.merge(
for {
command <- version.nodes(node_name).commands.iterator
st <- command_states(version, command).iterator
} yield st.exports)
def begin_theory(
node_name: Node.Name,
id: Document_ID.Exec,
source: String,
blobs_info: Command.Blobs_Info): State =
{
if (theories.isDefinedAt(id)) fail
else {
val command =
Command.unparsed(source, theory = true, id = id, node_name = node_name,
blobs_info = blobs_info)
copy(theories = theories + (id -> command.empty_state))
}
}
def end_theory(id: Document_ID.Exec): (Snapshot, State) =
theories.get(id) match {
case None => fail
case Some(st) =>
val command = st.command
val node_name = command.node_name
val command1 =
Command.unparsed(command.source, theory = true, id = id, node_name = node_name,
blobs_info = command.blobs_info, results = st.results, markups = st.markups)
val state1 = copy(theories = theories - id)
(state1.snippet(command1), state1)
}
def assign(id: Document_ID.Version, edited: List[String], update: Assign_Update)
: ((List[Node.Name], List[Command]), State) =
{
val version = the_version(id)
val edited_set = edited.toSet
val edited_nodes =
(for { (name, _) <- version.nodes.iterator if edited_set(name.node) } yield name).toList
def upd(exec_id: Document_ID.Exec, st: Command.State)
: Option[(Document_ID.Exec, Command.State)] =
if (execs.isDefinedAt(exec_id)) None else Some(exec_id -> st)
val (changed_commands, new_execs) =
((Nil: List[Command], execs) /: update) {
case ((commands1, execs1), (command_id, exec)) =>
val st = the_static_state(command_id)
val command = st.command
val commands2 = command :: commands1
val execs2 =
exec match {
case Nil => execs1
case eval_id :: print_ids =>
execs1 ++ upd(eval_id, st) ++
(for (id <- print_ids; up <- upd(id, command.empty_state)) yield up)
}
(commands2, execs2)
}
val new_assignment = the_assignment(version).assign(update)
val new_state = copy(assignments = assignments + (id -> new_assignment), execs = new_execs)
((edited_nodes, changed_commands), new_state)
}
def is_assigned(version: Version): Boolean =
assignments.get(version.id) match {
case Some(assgn) => assgn.is_finished
case None => false
}
def is_stable(change: Change): Boolean =
change.is_finished && is_assigned(change.version.get_finished)
def recent_finished: Change = history.undo_list.find(_.is_finished) getOrElse fail
def recent_stable: Change = history.undo_list.find(is_stable) getOrElse fail
def stable_tip_version: Option[Version] =
if (is_stable(history.tip)) Some(history.tip.version.get_finished) else None
def continue_history(
previous: Future[Version],
edits: List[Edit_Text],
version: Future[Version]): State =
{
val change = Change.make(previous, edits, version)
copy(history = history + change)
}
def remove_versions(retain: Int = 0): (List[Version], State) =
{
history.prune(is_stable, retain) match {
case Some((dropped, history1)) =>
val old_versions = dropped.map(change => change.version.get_finished)
val removing = old_versions.nonEmpty
val state1 = copy(history = history1, removing_versions = removing)
(old_versions, state1)
case None => fail
}
}
def removed_versions(removed: List[Document_ID.Version]): State =
{
val versions1 = Version.purge_suppressed(versions -- removed)
val assignments1 = assignments -- removed
var blobs1_names = Set.empty[Node.Name]
var blobs1 = Set.empty[SHA1.Digest]
var commands1 = Map.empty[Document_ID.Command, Command.State]
var execs1 = Map.empty[Document_ID.Exec, Command.State]
for {
(version_id, version) <- versions1.iterator
command_execs = assignments1(version_id).command_execs
(_, node) <- version.nodes.iterator
command <- node.commands.iterator
} {
for ((name, digest) <- command.blobs_defined) {
blobs1_names += name
blobs1 += digest
}
if (!commands1.isDefinedAt(command.id))
commands.get(command.id).foreach(st => commands1 += (command.id -> st))
for (exec_id <- command_execs.getOrElse(command.id, Nil)) {
if (!execs1.isDefinedAt(exec_id))
execs.get(exec_id).foreach(st => execs1 += (exec_id -> st))
}
}
copy(
versions = versions1,
blobs = blobs1,
commands = commands1,
execs = execs1,
commands_redirection = commands_redirection.restrict(commands1.keySet),
assignments = assignments1,
history = history.purge(versions1),
removing_versions = false)
}
def command_id_map(version: Version, commands: Iterable[Command])
: Map[Document_ID.Generic, Command] =
{
require(is_assigned(version), "version not assigned (command_id_map)")
val assignment = the_assignment(version).check_finished
(for {
command <- commands.iterator
id <- (command.id :: assignment.command_execs.getOrElse(command.id, Nil)).iterator
} yield (id -> command)).toMap
}
def command_maybe_consolidated(version: Version, command: Command): Boolean =
{
require(is_assigned(version), "version not assigned (command_maybe_consolidated)")
try {
the_assignment(version).check_finished.command_execs.getOrElse(command.id, Nil) match {
case eval_id :: print_ids =>
the_dynamic_state(eval_id).maybe_consolidated &&
!print_ids.exists(print_id => the_dynamic_state(print_id).consolidating)
case Nil => false
}
}
catch { case _: State.Fail => false }
}
private def command_states_self(version: Version, command: Command)
: List[(Document_ID.Generic, Command.State)] =
{
require(is_assigned(version), "version not assigned (command_states_self)")
try {
the_assignment(version).check_finished.command_execs.getOrElse(command.id, Nil)
.map(id => id -> the_dynamic_state(id)) match {
case Nil => fail
case res => res
}
}
catch {
case _: State.Fail =>
try { List(command.id -> the_static_state(command.id)) }
catch { case _: State.Fail => List(command.id -> command.init_state) }
}
}
def command_states(version: Version, command: Command): List[Command.State] =
{
val self = command_states_self(version, command)
val others =
if (commands_redirection.defined(command.id)) {
(for {
command_id <- commands_redirection.imm_succs(command.id).iterator
(id, st) <- command_states_self(version, the_static_state(command_id).command)
if !self.exists(_._1 == id)
} yield (id, st)).toMap.valuesIterator.toList
}
else Nil
self.map(_._2) ::: others.flatMap(_.redirect(command))
}
def command_results(version: Version, command: Command): Command.Results =
Command.State.merge_results(command_states(version, command))
def command_markup(version: Version, command: Command, index: Command.Markup_Index,
range: Text.Range, elements: Markup.Elements): Markup_Tree =
Command.State.merge_markup(command_states(version, command), index, range, elements)
def xml_markup(
version: Version,
node_name: Node.Name,
range: Text.Range = Text.Range.full,
elements: Markup.Elements = Markup.Elements.full): XML.Body =
{
val node = version.nodes(node_name)
if (node_name.is_theory) {
val markup_index = Command.Markup_Index.markup
(for {
command <- node.commands.iterator
command_range <- command.range.try_restrict(range).iterator
markup = command_markup(version, command, markup_index, command_range, elements)
tree <- markup.to_XML(command_range, command.source, elements).iterator
} yield tree).toList
}
else {
val node_source = node.source
Text.Range(0, node_source.length).try_restrict(range) match {
case None => Nil
case Some(node_range) =>
val markup =
version.nodes.commands_loading(node_name).headOption match {
case None => Markup_Tree.empty
case Some(command) =>
val chunk_name = Symbol.Text_Chunk.File(node_name.node)
val markup_index = Command.Markup_Index(false, chunk_name)
command_markup(version, command, markup_index, node_range, elements)
}
markup.to_XML(node_range, node_source, elements)
}
}
}
def node_initialized(version: Version, name: Node.Name): Boolean =
name.is_theory &&
(version.nodes(name).commands.iterator.find(_.potentially_initialized) match {
case None => false
case Some(command) => command_states(version, command).headOption.exists(_.initialized)
})
def node_maybe_consolidated(version: Version, name: Node.Name): Boolean =
name.is_theory &&
version.nodes(name).commands.reverse.iterator.forall(command_maybe_consolidated(version, _))
def node_consolidated(version: Version, name: Node.Name): Boolean =
!name.is_theory ||
{
val it = version.nodes(name).commands.reverse.iterator
- it.hasNext && command_states(version, it.next).exists(_.consolidated)
+ it.hasNext && command_states(version, it.next()).exists(_.consolidated)
}
def snapshot(
node_name: Node.Name = Node.Name.empty,
pending_edits: List[Text.Edit] = Nil,
snippet_command: Option[Command] = None): Snapshot =
{
val stable = recent_stable
val version = stable.version.get_finished
val rev_pending_changes =
for {
change <- history.undo_list.takeWhile(_ != stable)
(name, edits) <- change.rev_edits
if name == node_name
} yield edits
val edits =
(pending_edits /: rev_pending_changes)({
case (edits, Node.Edits(es)) => es ::: edits
case (edits, _) => edits
})
new Snapshot(this, version, node_name, edits, snippet_command)
}
def snippet(command: Command): Snapshot =
snapshot().snippet(command)
}
}
diff --git a/src/Pure/PIDE/document_status.scala b/src/Pure/PIDE/document_status.scala
--- a/src/Pure/PIDE/document_status.scala
+++ b/src/Pure/PIDE/document_status.scala
@@ -1,306 +1,306 @@
/* Title: Pure/PIDE/document_status.scala
Author: Makarius
Document status based on markup information.
*/
package isabelle
object Document_Status
{
/* command status */
object Command_Status
{
val proper_elements: Markup.Elements =
Markup.Elements(Markup.ACCEPTED, Markup.FORKED, Markup.JOINED, Markup.RUNNING,
Markup.FINISHED, Markup.FAILED, Markup.CANCELED)
val liberal_elements: Markup.Elements =
proper_elements + Markup.WARNING + Markup.LEGACY + Markup.ERROR
def make(markup_iterator: Iterator[Markup]): Command_Status =
{
var touched = false
var accepted = false
var warned = false
var failed = false
var canceled = false
var finalized = false
var forks = 0
var runs = 0
for (markup <- markup_iterator) {
markup.name match {
case Markup.ACCEPTED => accepted = true
case Markup.FORKED => touched = true; forks += 1
case Markup.JOINED => forks -= 1
case Markup.RUNNING => touched = true; runs += 1
case Markup.FINISHED => runs -= 1
case Markup.WARNING | Markup.LEGACY => warned = true
case Markup.FAILED | Markup.ERROR => failed = true
case Markup.CANCELED => canceled = true
case Markup.FINALIZED => finalized = true
case _ =>
}
}
Command_Status(
touched = touched,
accepted = accepted,
warned = warned,
failed = failed,
canceled = canceled,
finalized = finalized,
forks = forks,
runs = runs)
}
val empty: Command_Status = make(Iterator.empty)
def merge(status_iterator: Iterator[Command_Status]): Command_Status =
if (status_iterator.hasNext) {
- val status0 = status_iterator.next
+ val status0 = status_iterator.next()
(status0 /: status_iterator)(_ + _)
}
else empty
}
sealed case class Command_Status(
private val touched: Boolean,
private val accepted: Boolean,
private val warned: Boolean,
private val failed: Boolean,
private val canceled: Boolean,
private val finalized: Boolean,
forks: Int,
runs: Int)
{
def + (that: Command_Status): Command_Status =
Command_Status(
touched = touched || that.touched,
accepted = accepted || that.accepted,
warned = warned || that.warned,
failed = failed || that.failed,
canceled = canceled || that.canceled,
finalized = finalized || that.finalized,
forks = forks + that.forks,
runs = runs + that.runs)
def is_unprocessed: Boolean = accepted && !failed && (!touched || (forks != 0 && runs == 0))
def is_running: Boolean = runs != 0
def is_warned: Boolean = warned
def is_failed: Boolean = failed
def is_finished: Boolean = !failed && touched && forks == 0 && runs == 0
def is_canceled: Boolean = canceled
def is_finalized: Boolean = finalized
def is_terminated: Boolean = canceled || touched && forks == 0 && runs == 0
}
/* node status */
object Node_Status
{
def make(
state: Document.State,
version: Document.Version,
name: Document.Node.Name): Node_Status =
{
val node = version.nodes(name)
var unprocessed = 0
var running = 0
var warned = 0
var failed = 0
var finished = 0
var canceled = false
var terminated = true
var finalized = false
for (command <- node.commands.iterator) {
val states = state.command_states(version, command)
val status = Command_Status.merge(states.iterator.map(_.document_status))
if (status.is_running) running += 1
else if (status.is_failed) failed += 1
else if (status.is_warned) warned += 1
else if (status.is_finished) finished += 1
else unprocessed += 1
if (status.is_canceled) canceled = true
if (!status.is_terminated) terminated = false
if (status.is_finalized) finalized = true
}
val initialized = state.node_initialized(version, name)
val consolidated = state.node_consolidated(version, name)
Node_Status(
is_suppressed = version.nodes.is_suppressed(name),
unprocessed = unprocessed,
running = running,
warned = warned,
failed = failed,
finished = finished,
canceled = canceled,
terminated = terminated,
initialized = initialized,
finalized = finalized,
consolidated = consolidated)
}
}
sealed case class Node_Status(
is_suppressed: Boolean,
unprocessed: Int,
running: Int,
warned: Int,
failed: Int,
finished: Int,
canceled: Boolean,
terminated: Boolean,
initialized: Boolean,
finalized: Boolean,
consolidated: Boolean)
{
def ok: Boolean = failed == 0
def total: Int = unprocessed + running + warned + failed + finished
def quasi_consolidated: Boolean = !is_suppressed && !finalized && terminated
def percentage: Int =
if (consolidated) 100
else if (total == 0) 0
else (((total - unprocessed).toDouble / total) * 100).toInt min 99
def json: JSON.Object.T =
JSON.Object("ok" -> ok, "total" -> total, "unprocessed" -> unprocessed,
"running" -> running, "warned" -> warned, "failed" -> failed, "finished" -> finished,
"canceled" -> canceled, "consolidated" -> consolidated,
"percentage" -> percentage)
}
/* overall timing */
object Overall_Timing
{
val empty: Overall_Timing = Overall_Timing(0.0, Map.empty)
def make(
state: Document.State,
version: Document.Version,
commands: Iterable[Command],
threshold: Double = 0.0): Overall_Timing =
{
var total = 0.0
var command_timings = Map.empty[Command, Double]
for {
command <- commands.iterator
st <- state.command_states(version, command)
} {
val command_timing =
(0.0 /: st.status)({
case (timing, Markup.Timing(t)) => timing + t.elapsed.seconds
case (timing, _) => timing
})
total += command_timing
if (command_timing > 0.0 && command_timing >= threshold) {
command_timings += (command -> command_timing)
}
}
Overall_Timing(total, command_timings)
}
}
sealed case class Overall_Timing(total: Double, command_timings: Map[Command, Double])
{
def command_timing(command: Command): Double =
command_timings.getOrElse(command, 0.0)
}
/* nodes status */
object Overall_Node_Status extends Enumeration
{
val ok, failed, pending = Value
}
object Nodes_Status
{
val empty: Nodes_Status = new Nodes_Status(Map.empty, Document.Nodes.empty)
}
final class Nodes_Status private(
private val rep: Map[Document.Node.Name, Node_Status],
nodes: Document.Nodes)
{
def is_empty: Boolean = rep.isEmpty
def apply(name: Document.Node.Name): Node_Status = rep(name)
def get(name: Document.Node.Name): Option[Node_Status] = rep.get(name)
def present: List[(Document.Node.Name, Node_Status)] =
(for {
name <- nodes.topological_order.iterator
node_status <- get(name)
} yield (name, node_status)).toList
def quasi_consolidated(name: Document.Node.Name): Boolean =
rep.get(name) match {
case Some(st) => st.quasi_consolidated
case None => false
}
def overall_node_status(name: Document.Node.Name): Overall_Node_Status.Value =
rep.get(name) match {
case Some(st) if st.consolidated =>
if (st.ok) Overall_Node_Status.ok else Overall_Node_Status.failed
case _ => Overall_Node_Status.pending
}
def update(
resources: Resources,
state: Document.State,
version: Document.Version,
domain: Option[Set[Document.Node.Name]] = None,
trim: Boolean = false): (Boolean, Nodes_Status) =
{
val nodes1 = version.nodes
val update_iterator =
for {
name <- domain.getOrElse(nodes1.domain).iterator
if !resources.is_hidden(name) && !resources.session_base.loaded_theory(name)
st = Document_Status.Node_Status.make(state, version, name)
if !rep.isDefinedAt(name) || rep(name) != st
} yield (name -> st)
val rep1 = rep ++ update_iterator
val rep2 = if (trim) rep1 -- rep1.keysIterator.filterNot(nodes1.domain) else rep1
(rep != rep2, new Nodes_Status(rep2, nodes1))
}
override def hashCode: Int = rep.hashCode
override def equals(that: Any): Boolean =
that match {
case other: Nodes_Status => rep == other.rep
case _ => false
}
override def toString: String =
{
var ok = 0
var failed = 0
var pending = 0
var canceled = 0
for (name <- rep.keysIterator) {
overall_node_status(name) match {
case Overall_Node_Status.ok => ok += 1
case Overall_Node_Status.failed => failed += 1
case Overall_Node_Status.pending => pending += 1
}
if (apply(name).canceled) canceled += 1
}
"Nodes_Status(ok = " + ok + ", failed = " + failed + ", pending = " + pending +
", canceled = " + canceled + ")"
}
}
}
diff --git a/src/Pure/PIDE/editor.scala b/src/Pure/PIDE/editor.scala
--- a/src/Pure/PIDE/editor.scala
+++ b/src/Pure/PIDE/editor.scala
@@ -1,53 +1,53 @@
/* Title: Pure/PIDE/editor.scala
Author: Makarius
General editor operations.
*/
package isabelle
abstract class Editor[Context]
{
/* session */
def session: Session
def flush(): Unit
def invoke(): Unit
/* current situation */
def current_node(context: Context): Option[Document.Node.Name]
def current_node_snapshot(context: Context): Option[Document.Snapshot]
def node_snapshot(name: Document.Node.Name): Document.Snapshot
def current_command(context: Context, snapshot: Document.Snapshot): Option[Command]
/* overlays */
def node_overlays(name: Document.Node.Name): Document.Node.Overlays
- def insert_overlay(command: Command, fn: String, args: List[String])
- def remove_overlay(command: Command, fn: String, args: List[String])
+ def insert_overlay(command: Command, fn: String, args: List[String]): Unit
+ def remove_overlay(command: Command, fn: String, args: List[String]): Unit
/* hyperlinks */
abstract class Hyperlink
{
def external: Boolean = false
def follow(context: Context): Unit
}
def hyperlink_command(
focus: Boolean, snapshot: Document.Snapshot, id: Document_ID.Generic, offset: Symbol.Offset = 0)
: Option[Hyperlink]
/* dispatcher thread */
def assert_dispatcher[A](body: => A): A
def require_dispatcher[A](body: => A): A
def send_dispatcher(body: => Unit): Unit
def send_wait_dispatcher(body: => Unit): Unit
}
diff --git a/src/Pure/PIDE/headless.scala b/src/Pure/PIDE/headless.scala
--- a/src/Pure/PIDE/headless.scala
+++ b/src/Pure/PIDE/headless.scala
@@ -1,661 +1,661 @@
/* Title: Pure/PIDE/headless.scala
Author: Makarius
Headless PIDE session and resources from file-system.
*/
package isabelle
import java.io.{File => JFile}
import scala.annotation.tailrec
import scala.collection.mutable
object Headless
{
/** session **/
private def stable_snapshot(
state: Document.State, version: Document.Version, name: Document.Node.Name): Document.Snapshot =
{
val snapshot = state.snapshot(name)
assert(version.id == snapshot.version.id)
snapshot
}
class Use_Theories_Result private[Headless](
val state: Document.State,
val version: Document.Version,
val nodes: List[(Document.Node.Name, Document_Status.Node_Status)],
val nodes_committed: List[(Document.Node.Name, Document_Status.Node_Status)])
{
def nodes_pending: List[(Document.Node.Name, Document_Status.Node_Status)] =
{
val committed = nodes_committed.iterator.map(_._1).toSet
nodes.filter(p => !committed(p._1))
}
def snapshot(name: Document.Node.Name): Document.Snapshot =
stable_snapshot(state, version, name)
def ok: Boolean =
(nodes.iterator ++ nodes_committed.iterator).forall({ case (_, st) => st.ok })
}
class Session private[Headless](
session_name: String,
_session_options: => Options,
override val resources: Resources) extends isabelle.Session(_session_options, resources)
{
session =>
private def loaded_theory(name: Document.Node.Name): Boolean =
resources.session_base.loaded_theory(name.theory)
/* options */
override def consolidate_delay: Time = session_options.seconds("headless_consolidate_delay")
override def prune_delay: Time = session_options.seconds("headless_prune_delay")
def default_check_delay: Time = session_options.seconds("headless_check_delay")
def default_check_limit: Int = session_options.int("headless_check_limit")
def default_nodes_status_delay: Time = session_options.seconds("headless_nodes_status_delay")
def default_watchdog_timeout: Time = session_options.seconds("headless_watchdog_timeout")
def default_commit_cleanup_delay: Time = session_options.seconds("headless_commit_cleanup_delay")
/* temporary directory */
val tmp_dir: JFile = Isabelle_System.tmp_dir("server_session")
val tmp_dir_name: String = File.path(tmp_dir).implode
def master_directory(master_dir: String): String =
proper_string(master_dir) getOrElse tmp_dir_name
override def toString: String = session_name
override def stop(): Process_Result =
{
try { super.stop() }
finally { Isabelle_System.rm_tree(tmp_dir) }
}
/* theories */
private object Load_State
{
def finished: Load_State = Load_State(Nil, Nil, 0)
def count_file(name: Document.Node.Name): Long =
if (loaded_theory(name)) 0 else name.path.file.length
}
private case class Load_State(
pending: List[Document.Node.Name], rest: List[Document.Node.Name], load_limit: Long)
{
def next(
dep_graph: Document.Node.Name.Graph[Unit],
finished: Document.Node.Name => Boolean): (List[Document.Node.Name], Load_State) =
{
def load_requirements(pending1: List[Document.Node.Name], rest1: List[Document.Node.Name])
: (List[Document.Node.Name], Load_State) =
{
val load_theories = dep_graph.all_preds_rev(pending1).filterNot(finished)
(load_theories, Load_State(pending1, rest1, load_limit))
}
if (!pending.forall(finished)) (Nil, this)
else if (rest.isEmpty) (Nil, Load_State.finished)
else if (load_limit == 0) load_requirements(rest, Nil)
else {
val reachable =
dep_graph.reachable_limit(load_limit, Load_State.count_file, dep_graph.imm_preds, rest)
val (pending1, rest1) = rest.partition(reachable)
load_requirements(pending1, rest1)
}
}
}
private sealed case class Use_Theories_State(
dep_graph: Document.Node.Name.Graph[Unit],
load_state: Load_State,
watchdog_timeout: Time,
commit: Option[(Document.Snapshot, Document_Status.Node_Status) => Unit],
last_update: Time = Time.now(),
nodes_status: Document_Status.Nodes_Status = Document_Status.Nodes_Status.empty,
already_committed: Map[Document.Node.Name, Document_Status.Node_Status] = Map.empty,
result: Option[Exn.Result[Use_Theories_Result]] = None)
{
def update(new_nodes_status: Document_Status.Nodes_Status): Use_Theories_State =
copy(last_update = Time.now(), nodes_status = new_nodes_status)
def watchdog: Boolean =
watchdog_timeout > Time.zero && Time.now() - last_update > watchdog_timeout
def finished_result: Boolean = result.isDefined
def join_result: Option[(Exn.Result[Use_Theories_Result], Use_Theories_State)] =
if (finished_result) Some((result.get, this)) else None
def cancel_result: Use_Theories_State =
if (finished_result) this else copy(result = Some(Exn.Exn(Exn.Interrupt())))
def clean_theories: (List[Document.Node.Name], Use_Theories_State) =
{
@tailrec def frontier(base: List[Document.Node.Name], front: Set[Document.Node.Name])
: Set[Document.Node.Name] =
{
val add = base.filter(name => dep_graph.imm_succs(name).forall(front))
if (add.isEmpty) front
else {
val preds = add.map(dep_graph.imm_preds)
val base1 = (preds.head /: preds.tail)(_ ++ _).toList.filter(already_committed.keySet)
frontier(base1, front ++ add)
}
}
if (already_committed.isEmpty) (Nil, this)
else {
val base =
(for {
(name, (_, (_, succs))) <- dep_graph.iterator
if succs.isEmpty && already_committed.isDefinedAt(name)
} yield name).toList
val clean = frontier(base, Set.empty)
if (clean.isEmpty) (Nil, this)
else {
(dep_graph.topological_order.filter(clean),
copy(dep_graph = dep_graph.exclude(clean)))
}
}
}
def check(state: Document.State, version: Document.Version, beyond_limit: Boolean)
: (List[Document.Node.Name], Use_Theories_State) =
{
val already_committed1 =
commit match {
case None => already_committed
case Some(commit_fn) =>
(already_committed /: dep_graph.topological_order)(
{ case (committed, name) =>
def parents_committed: Boolean =
version.nodes(name).header.imports.forall(parent =>
loaded_theory(parent) || committed.isDefinedAt(parent))
if (!committed.isDefinedAt(name) && parents_committed &&
state.node_consolidated(version, name))
{
val snapshot = stable_snapshot(state, version, name)
val status = Document_Status.Node_Status.make(state, version, name)
commit_fn(snapshot, status)
committed + (name -> status)
}
else committed
})
}
def finished_theory(name: Document.Node.Name): Boolean =
loaded_theory(name) ||
(if (commit.isDefined) already_committed1.isDefinedAt(name)
else state.node_consolidated(version, name))
val result1 =
if (!finished_result &&
(beyond_limit || watchdog ||
dep_graph.keys_iterator.forall(name =>
finished_theory(name) || nodes_status.quasi_consolidated(name))))
{
val nodes =
(for {
name <- dep_graph.keys_iterator
if !loaded_theory(name)
} yield { (name -> Document_Status.Node_Status.make(state, version, name)) }).toList
val nodes_committed =
(for {
name <- dep_graph.keys_iterator
status <- already_committed1.get(name)
} yield (name -> status)).toList
Some(Exn.Res(new Use_Theories_Result(state, version, nodes, nodes_committed)))
}
else result
val (load_theories, load_state1) = load_state.next(dep_graph, finished_theory)
(load_theories,
copy(already_committed = already_committed1, result = result1, load_state = load_state1))
}
}
def use_theories(
theories: List[String],
qualifier: String = Sessions.DRAFT,
master_dir: String = "",
unicode_symbols: Boolean = false,
check_delay: Time = default_check_delay,
check_limit: Int = default_check_limit,
watchdog_timeout: Time = default_watchdog_timeout,
nodes_status_delay: Time = default_nodes_status_delay,
id: UUID.T = UUID.random(),
// commit: must not block, must not fail
commit: Option[(Document.Snapshot, Document_Status.Node_Status) => Unit] = None,
commit_cleanup_delay: Time = default_commit_cleanup_delay,
progress: Progress = new Progress): Use_Theories_Result =
{
val dependencies =
{
val import_names =
theories.map(thy =>
resources.import_name(qualifier, master_directory(master_dir), thy) -> Position.none)
resources.dependencies(import_names, progress = progress).check_errors
}
val dep_theories = dependencies.theories
val dep_theories_set = dep_theories.toSet
val dep_files =
for (path <- dependencies.loaded_files)
yield Document.Node.Name(resources.append("", path))
val use_theories_state =
{
val dep_graph = dependencies.theory_graph
val maximals = dep_graph.maximals
val rest =
if (maximals.isEmpty || maximals.tail.isEmpty) maximals
else {
val depth = dep_graph.node_depth(Load_State.count_file)
maximals.sortBy(node => - depth(node))
}
val load_limit =
if (commit.isDefined) (session_options.real("headless_load_limit") * 1024 * 1024).round
else 0
val load_state = Load_State(Nil, rest, load_limit)
Synchronized(Use_Theories_State(dep_graph, load_state, watchdog_timeout, commit))
}
- def check_state(beyond_limit: Boolean = false)
+ def check_state(beyond_limit: Boolean = false): Unit =
{
val state = session.get_state()
for {
version <- state.stable_tip_version
load_theories = use_theories_state.change_result(_.check(state, version, beyond_limit))
if load_theories.nonEmpty
} resources.load_theories(session, id, load_theories, dep_files, unicode_symbols, progress)
}
val check_progress =
{
var check_count = 0
Event_Timer.request(Time.now(), repeat = Some(check_delay))
{
if (progress.stopped) use_theories_state.change(_.cancel_result)
else {
check_count += 1
check_state(check_limit > 0 && check_count > check_limit)
}
}
}
val consumer =
{
val delay_nodes_status =
Delay.first(nodes_status_delay max Time.zero) {
progress.nodes_status(use_theories_state.value.nodes_status)
}
val delay_commit_clean =
Delay.first(commit_cleanup_delay max Time.zero) {
val clean_theories = use_theories_state.change_result(_.clean_theories)
if (clean_theories.nonEmpty) {
progress.echo("Removing " + clean_theories.length + " theories ...")
resources.clean_theories(session, id, clean_theories)
}
}
Session.Consumer[Session.Commands_Changed](getClass.getName) {
case changed =>
if (changed.nodes.exists(dep_theories_set)) {
val snapshot = session.snapshot()
val state = snapshot.state
val version = snapshot.version
val theory_progress =
use_theories_state.change_result(st =>
{
val domain =
if (st.nodes_status.is_empty) dep_theories_set
else changed.nodes.iterator.filter(dep_theories_set).toSet
val (nodes_status_changed, nodes_status1) =
st.nodes_status.update(resources, state, version,
domain = Some(domain), trim = changed.assignment)
if (nodes_status_delay >= Time.zero && nodes_status_changed) {
delay_nodes_status.invoke
}
val theory_progress =
(for {
(name, node_status) <- nodes_status1.present.iterator
if changed.nodes.contains(name) && !st.already_committed.isDefinedAt(name)
p1 = node_status.percentage
if p1 > 0 && Some(p1) != st.nodes_status.get(name).map(_.percentage)
} yield Progress.Theory(name.theory, percentage = Some(p1))).toList
(theory_progress, st.update(nodes_status1))
})
theory_progress.foreach(progress.theory)
check_state()
if (commit.isDefined && commit_cleanup_delay > Time.zero) {
if (use_theories_state.value.finished_result)
delay_commit_clean.revoke
else delay_commit_clean.invoke
}
}
}
}
try {
session.commands_changed += consumer
check_state()
use_theories_state.guarded_access(_.join_result)
check_progress.cancel
}
finally {
session.commands_changed -= consumer
resources.unload_theories(session, id, dep_theories)
}
Exn.release(use_theories_state.guarded_access(_.join_result))
}
def purge_theories(
theories: List[String],
qualifier: String = Sessions.DRAFT,
master_dir: String = "",
all: Boolean = false): (List[Document.Node.Name], List[Document.Node.Name]) =
{
val nodes =
if (all) None
else Some(theories.map(resources.import_name(qualifier, master_directory(master_dir), _)))
resources.purge_theories(session, nodes)
}
}
/** resources **/
object Resources
{
def apply(options: Options, base_info: Sessions.Base_Info, log: Logger = No_Logger): Resources =
new Resources(options, base_info, log = log)
def make(
options: Options,
session_name: String,
session_dirs: List[Path] = Nil,
include_sessions: List[String] = Nil,
progress: Progress = new Progress,
log: Logger = No_Logger): Resources =
{
val base_info =
Sessions.base_info(options, session_name, dirs = session_dirs,
include_sessions = include_sessions, progress = progress)
apply(options, base_info, log = log)
}
final class Theory private[Headless](
val node_name: Document.Node.Name,
val node_header: Document.Node.Header,
val text: String,
val node_required: Boolean)
{
override def toString: String = node_name.toString
def node_perspective: Document.Node.Perspective_Text =
Document.Node.Perspective(node_required, Text.Perspective.empty, Document.Node.Overlays.empty)
def make_edits(text_edits: List[Text.Edit]): List[Document.Edit_Text] =
List(node_name -> Document.Node.Deps(node_header),
node_name -> Document.Node.Edits(text_edits),
node_name -> node_perspective)
def node_edits(old: Option[Theory]): List[Document.Edit_Text] =
{
val (text_edits, old_required) =
if (old.isEmpty) (Text.Edit.inserts(0, text), false)
else (Text.Edit.replace(0, old.get.text, text), old.get.node_required)
if (text_edits.isEmpty && node_required == old_required) Nil
else make_edits(text_edits)
}
def purge_edits: List[Document.Edit_Text] =
make_edits(Text.Edit.removes(0, text))
def required(required: Boolean): Theory =
if (required == node_required) this
else new Theory(node_name, node_header, text, required)
}
sealed case class State(
blobs: Map[Document.Node.Name, Document.Blob] = Map.empty,
theories: Map[Document.Node.Name, Theory] = Map.empty,
required: Multi_Map[Document.Node.Name, UUID.T] = Multi_Map.empty)
{
/* blobs */
def doc_blobs: Document.Blobs = Document.Blobs(blobs)
def update_blobs(names: List[Document.Node.Name]): (Document.Blobs, State) =
{
val new_blobs =
names.flatMap(name =>
{
val bytes = Bytes.read(name.path)
def new_blob: Document.Blob =
{
val text = bytes.text
Document.Blob(bytes, text, Symbol.Text_Chunk(text), changed = true)
}
blobs.get(name) match {
case Some(blob) => if (blob.bytes == bytes) None else Some(name -> new_blob)
case None => Some(name -> new_blob)
}
})
val blobs1 = (blobs /: new_blobs)(_ + _)
val blobs2 = (blobs /: new_blobs)({ case (map, (a, b)) => map + (a -> b.unchanged) })
(Document.Blobs(blobs1), copy(blobs = blobs2))
}
def blob_edits(name: Document.Node.Name, old_blob: Option[Document.Blob])
: List[Document.Edit_Text] =
{
val blob = blobs.getOrElse(name, error("Missing blob " + quote(name.toString)))
val text_edits =
old_blob match {
case None => List(Text.Edit.insert(0, blob.source))
case Some(blob0) => Text.Edit.replace(0, blob0.source, blob.source)
}
if (text_edits.isEmpty) Nil
else List(name -> Document.Node.Blob(blob), name -> Document.Node.Edits(text_edits))
}
/* theories */
lazy val theory_graph: Document.Node.Name.Graph[Unit] =
Document.Node.Name.make_graph(
for ((name, theory) <- theories.toList)
yield ((name, ()), theory.node_header.imports.filter(theories.isDefinedAt)))
def is_required(name: Document.Node.Name): Boolean = required.isDefinedAt(name)
def insert_required(id: UUID.T, names: List[Document.Node.Name]): State =
copy(required = (required /: names)(_.insert(_, id)))
def remove_required(id: UUID.T, names: List[Document.Node.Name]): State =
copy(required = (required /: names)(_.remove(_, id)))
def update_theories(update: List[(Document.Node.Name, Theory)]): State =
copy(theories =
(theories /: update)({ case (thys, (name, thy)) =>
thys.get(name) match {
case Some(thy1) if thy1 == thy => thys
case _ => thys + (name -> thy)
}
}))
def remove_theories(remove: List[Document.Node.Name]): State =
{
require(remove.forall(name => !is_required(name)), "attempt to remove required nodes")
copy(theories = theories -- remove)
}
def unload_theories(session: Session, id: UUID.T, theories: List[Document.Node.Name])
: (List[Document.Edit_Text], State) =
{
val st1 = remove_required(id, theories)
val theory_edits =
for {
node_name <- theories
theory <- st1.theories.get(node_name)
}
yield {
val theory1 = theory.required(st1.is_required(node_name))
val edits = theory1.node_edits(Some(theory))
(edits, (node_name, theory1))
}
(theory_edits.flatMap(_._1), st1.update_theories(theory_edits.map(_._2)))
}
def purge_theories(session: Session, nodes: Option[List[Document.Node.Name]])
: ((List[Document.Node.Name], List[Document.Node.Name], List[Document.Edit_Text]), State) =
{
val all_nodes = theory_graph.topological_order
val purge = nodes.getOrElse(all_nodes).filterNot(is_required).toSet
val retain = theory_graph.all_preds(all_nodes.filterNot(purge)).toSet
val (retained, purged) = all_nodes.partition(retain)
val purge_edits = purged.flatMap(name => theories(name).purge_edits)
((purged, retained, purge_edits), remove_theories(purged))
}
}
}
class Resources private[Headless](
val options: Options,
val session_base_info: Sessions.Base_Info,
log: Logger = No_Logger)
extends isabelle.Resources(
session_base_info.sessions_structure, session_base_info.check.base, log = log)
{
resources =>
val store: Sessions.Store = Sessions.store(options)
/* session */
def start_session(print_mode: List[String] = Nil, progress: Progress = new Progress): Session =
{
val session = new Session(session_base_info.session, options, resources)
progress.echo("Starting session " + session_base_info.session + " ...")
Isabelle_Process(session, options, session_base_info.sessions_structure, store,
logic = session_base_info.session, modes = print_mode).await_startup
session
}
/* theories */
private val state = Synchronized(Resources.State())
def load_theories(
session: Session,
id: UUID.T,
theories: List[Document.Node.Name],
files: List[Document.Node.Name],
unicode_symbols: Boolean,
- progress: Progress)
+ progress: Progress): Unit =
{
val loaded_theories =
for (node_name <- theories)
yield {
val path = node_name.path
if (!node_name.is_theory) error("Not a theory file: " + path)
progress.expose_interrupt()
val text0 = File.read(path)
val text = if (unicode_symbols) Symbol.decode(text0) else text0
val node_header = resources.check_thy(node_name, Scan.char_reader(text))
new Resources.Theory(node_name, node_header, text, true)
}
val loaded = loaded_theories.length
if (loaded > 1) progress.echo("Loading " + loaded + " theories ...")
state.change(st =>
{
val (doc_blobs1, st1) = st.insert_required(id, theories).update_blobs(files)
val theory_edits =
for (theory <- loaded_theories)
yield {
val node_name = theory.node_name
val theory1 = theory.required(st1.is_required(node_name))
val edits = theory1.node_edits(st1.theories.get(node_name))
(edits, (node_name, theory1))
}
val file_edits =
for { node_name <- files if doc_blobs1.changed(node_name) }
yield st1.blob_edits(node_name, st.blobs.get(node_name))
session.update(doc_blobs1, theory_edits.flatMap(_._1) ::: file_edits.flatten)
st1.update_theories(theory_edits.map(_._2))
})
}
- def unload_theories(session: Session, id: UUID.T, theories: List[Document.Node.Name])
+ def unload_theories(session: Session, id: UUID.T, theories: List[Document.Node.Name]): Unit =
{
state.change(st =>
{
val (edits, st1) = st.unload_theories(session, id, theories)
session.update(st.doc_blobs, edits)
st1
})
}
- def clean_theories(session: Session, id: UUID.T, theories: List[Document.Node.Name])
+ def clean_theories(session: Session, id: UUID.T, theories: List[Document.Node.Name]): Unit =
{
state.change(st =>
{
val (edits1, st1) = st.unload_theories(session, id, theories)
val ((_, _, edits2), st2) = st1.purge_theories(session, None)
session.update(st.doc_blobs, edits1 ::: edits2)
st2
})
}
def purge_theories(session: Session, nodes: Option[List[Document.Node.Name]])
: (List[Document.Node.Name], List[Document.Node.Name]) =
{
state.change_result(st =>
{
val ((purged, retained, _), st1) = st.purge_theories(session, nodes)
((purged, retained), st1)
})
}
}
}
diff --git a/src/Pure/PIDE/protocol.scala b/src/Pure/PIDE/protocol.scala
--- a/src/Pure/PIDE/protocol.scala
+++ b/src/Pure/PIDE/protocol.scala
@@ -1,449 +1,449 @@
/* Title: Pure/PIDE/protocol.scala
Author: Makarius
Protocol message formats for interactive proof documents.
*/
package isabelle
object Protocol
{
/* markers for inlined messages */
val Loading_Theory_Marker = Protocol_Message.Marker("loading_theory")
val Meta_Info_Marker = Protocol_Message.Marker("meta_info")
val Command_Timing_Marker = Protocol_Message.Marker("command_timing")
val Theory_Timing_Marker = Protocol_Message.Marker("theory_timing")
val Session_Timing_Marker = Protocol_Message.Marker("session_timing")
val ML_Statistics_Marker = Protocol_Message.Marker("ML_statistics")
val Task_Statistics_Marker = Protocol_Message.Marker("task_statistics")
val Error_Message_Marker = Protocol_Message.Marker("error_message")
/* batch build */
object Loading_Theory
{
def unapply(props: Properties.T): Option[(Document.Node.Name, Document_ID.Exec)] =
(props, props, props) match {
case (Markup.Name(name), Position.File(file), Position.Id(id))
if Path.is_wellformed(file) =>
val master_dir = Path.explode(file).dir.implode
Some((Document.Node.Name(file, master_dir, name), id))
case _ => None
}
}
/* document editing */
object Commands_Accepted
{
def unapply(text: String): Option[List[Document_ID.Command]] =
try { Some(space_explode(',', text).map(Value.Long.parse)) }
catch { case ERROR(_) => None }
val message: XML.Elem = XML.elem(Markup.STATUS, List(XML.elem(Markup.ACCEPTED)))
}
object Assign_Update
{
def unapply(text: String)
: Option[(Document_ID.Version, List[String], Document.Assign_Update)] =
{
try {
import XML.Decode._
def decode_upd(body: XML.Body): (Long, List[Long]) =
space_explode(',', string(body)).map(Value.Long.parse) match {
case a :: bs => (a, bs)
case _ => throw new XML.XML_Body(body)
}
Some(triple(long, list(string), list(decode_upd))(Symbol.decode_yxml(text)))
}
catch {
case ERROR(_) => None
case _: XML.Error => None
}
}
}
object Removed
{
def unapply(text: String): Option[List[Document_ID.Version]] =
try {
import XML.Decode._
Some(list(long)(Symbol.decode_yxml(text)))
}
catch {
case ERROR(_) => None
case _: XML.Error => None
}
}
/* command timing */
object Command_Timing
{
def unapply(props: Properties.T): Option[(Properties.T, Document_ID.Generic, isabelle.Timing)] =
props match {
case Markup.Command_Timing(args) =>
(args, args) match {
case (Position.Id(id), Markup.Timing_Properties(timing)) => Some((args, id, timing))
case _ => None
}
case _ => None
}
}
/* theory timing */
object Theory_Timing
{
def unapply(props: Properties.T): Option[(String, isabelle.Timing)] =
props match {
case Markup.Theory_Timing(args) =>
(args, args) match {
case (Markup.Name(name), Markup.Timing_Properties(timing)) => Some((name, timing))
case _ => None
}
case _ => None
}
}
/* result messages */
def is_result(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.RESULT, _), _) => true
case _ => false
}
def is_tracing(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.TRACING, _), _) => true
case XML.Elem(Markup(Markup.TRACING_MESSAGE, _), _) => true
case _ => false
}
def is_state(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.STATE, _), _) => true
case XML.Elem(Markup(Markup.STATE_MESSAGE, _), _) => true
case _ => false
}
def is_information(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.INFORMATION, _), _) => true
case XML.Elem(Markup(Markup.INFORMATION_MESSAGE, _), _) => true
case _ => false
}
def is_writeln(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.WRITELN, _), _) => true
case XML.Elem(Markup(Markup.WRITELN_MESSAGE, _), _) => true
case _ => false
}
def is_warning(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.WARNING, _), _) => true
case XML.Elem(Markup(Markup.WARNING_MESSAGE, _), _) => true
case _ => false
}
def is_legacy(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.LEGACY, _), _) => true
case XML.Elem(Markup(Markup.LEGACY_MESSAGE, _), _) => true
case _ => false
}
def is_error(msg: XML.Tree): Boolean =
msg match {
case XML.Elem(Markup(Markup.ERROR, _), _) => true
case XML.Elem(Markup(Markup.ERROR_MESSAGE, _), _) => true
case _ => false
}
def is_inlined(msg: XML.Tree): Boolean =
!(is_result(msg) || is_tracing(msg) || is_state(msg))
def is_exported(msg: XML.Tree): Boolean =
is_writeln(msg) || is_warning(msg) || is_legacy(msg) || is_error(msg)
def message_text(elem: XML.Elem,
heading: Boolean = false,
pos: Position.T = Position.none,
margin: Double = Pretty.default_margin,
breakgain: Double = Pretty.default_breakgain,
metric: Pretty.Metric = Pretty.Default_Metric): String =
{
val text1 =
if (heading) {
val h =
if (is_warning(elem) || is_legacy(elem)) "Warning"
else if (is_error(elem)) "Error"
else if (is_information(elem)) "Information"
else if (is_tracing(elem)) "Tracing"
else if (is_state(elem)) "State"
else "Output"
"\n" + h + Position.here(pos) + ":\n"
}
else ""
val body =
Pretty.string_of(Protocol_Message.expose_no_reports(List(elem)),
margin = margin, breakgain = breakgain, metric = metric)
val text2 =
if (is_warning(elem) || is_legacy(elem)) Output.warning_prefix(body)
else if (is_error(elem)) Output.error_prefix(body)
else body
text1 + text2
}
/* export */
object Export
{
sealed case class Args(
id: Option[String] = None,
serial: Long = 0L,
theory_name: String,
name: String,
executable: Boolean = false,
compress: Boolean = true,
strict: Boolean = true)
{
def compound_name: String = isabelle.Export.compound_name(theory_name, name)
}
def unapply(props: Properties.T): Option[Args] =
props match {
case
List(
(Markup.FUNCTION, Markup.EXPORT),
(Markup.ID, id),
(Markup.SERIAL, Value.Long(serial)),
(Markup.THEORY_NAME, theory_name),
(Markup.NAME, name),
(Markup.EXECUTABLE, Value.Boolean(executable)),
(Markup.COMPRESS, Value.Boolean(compress)),
(Markup.STRICT, Value.Boolean(strict))) =>
Some(Args(proper_string(id), serial, theory_name, name, executable, compress, strict))
case _ => None
}
}
/* breakpoints */
object ML_Breakpoint
{
def unapply(tree: XML.Tree): Option[Long] =
tree match {
case XML.Elem(Markup(Markup.ML_BREAKPOINT, Markup.Serial(breakpoint)), _) => Some(breakpoint)
case _ => None
}
}
/* dialogs */
object Dialog_Args
{
def unapply(props: Properties.T): Option[(Document_ID.Generic, Long, String)] =
(props, props, props) match {
case (Position.Id(id), Markup.Serial(serial), Markup.Result(result)) =>
Some((id, serial, result))
case _ => None
}
}
object Dialog
{
def unapply(tree: XML.Tree): Option[(Document_ID.Generic, Long, String)] =
tree match {
case XML.Elem(Markup(Markup.DIALOG, Dialog_Args(id, serial, result)), _) =>
Some((id, serial, result))
case _ => None
}
}
object Dialog_Result
{
def apply(id: Document_ID.Generic, serial: Long, result: String): XML.Elem =
{
val props = Position.Id(id) ::: Markup.Serial(serial)
XML.Elem(Markup(Markup.RESULT, props), List(XML.Text(result)))
}
def unapply(tree: XML.Tree): Option[String] =
tree match {
case XML.Elem(Markup(Markup.RESULT, _), List(XML.Text(result))) => Some(result)
case _ => None
}
}
}
trait Protocol
{
/* protocol commands */
def protocol_command_raw(name: String, args: List[Bytes]): Unit
- def protocol_command_args(name: String, args: List[String])
+ def protocol_command_args(name: String, args: List[String]): Unit
def protocol_command(name: String, args: String*): Unit
/* options */
def options(opts: Options): Unit =
protocol_command("Prover.options", Symbol.encode_yxml(opts.encode))
/* resources */
def init_session(resources: Resources): Unit =
protocol_command("Prover.init_session", resources.init_session_yxml)
/* interned items */
def define_blob(digest: SHA1.Digest, bytes: Bytes): Unit =
protocol_command_raw("Document.define_blob", List(Bytes(digest.toString), bytes))
private def encode_command(resources: Resources, command: Command)
: (String, String, String, String, String, List[String]) =
{
import XML.Encode._
val parents = command.theory_parents(resources).map(name => File.standard_url(name.node))
val parents_yxml = Symbol.encode_yxml(list(string)(parents))
val blobs_yxml =
{
val encode_blob: T[Exn.Result[Command.Blob]] =
variant(List(
{ case Exn.Res(Command.Blob(a, b, c)) =>
(Nil, triple(string, string, option(string))(
(a.node, b.implode, c.map(p => p._1.toString)))) },
{ case Exn.Exn(e) => (Nil, string(Exn.message(e))) }))
Symbol.encode_yxml(pair(list(encode_blob), int)(command.blobs, command.blobs_index))
}
val toks_yxml =
{
val encode_tok: T[Token] = (tok => pair(int, int)((tok.kind.id, Symbol.length(tok.source))))
Symbol.encode_yxml(list(encode_tok)(command.span.content))
}
val toks_sources = command.span.content.map(tok => Symbol.encode(tok.source))
(Document_ID(command.id), Symbol.encode(command.span.name), parents_yxml,
blobs_yxml, toks_yxml, toks_sources)
}
- def define_command(resources: Resources, command: Command)
+ def define_command(resources: Resources, command: Command): Unit =
{
val (command_id, name, parents_yxml, blobs_yxml, toks_yxml, toks_sources) =
encode_command(resources, command)
protocol_command_args(
"Document.define_command", command_id :: name :: parents_yxml :: blobs_yxml ::
toks_yxml :: toks_sources)
}
- def define_commands(resources: Resources, commands: List[Command])
+ def define_commands(resources: Resources, commands: List[Command]): Unit =
{
protocol_command_args("Document.define_commands",
commands.map(command =>
{
import XML.Encode._
val (command_id, name, parents_yxml, blobs_yxml, toks_yxml, toks_sources) =
encode_command(resources, command)
val body =
pair(string, pair(string, pair(string, pair(string, pair(string, list(string))))))(
command_id, (name, (parents_yxml, (blobs_yxml, (toks_yxml, toks_sources)))))
YXML.string_of_body(body)
}))
}
- def define_commands_bulk(resources: Resources, commands: List[Command])
+ def define_commands_bulk(resources: Resources, commands: List[Command]): Unit =
{
val (irregular, regular) = commands.partition(command => YXML.detect(command.source))
irregular.foreach(define_command(resources, _))
regular match {
case Nil =>
case List(command) => define_command(resources, command)
case _ => define_commands(resources, regular)
}
}
/* execution */
def discontinue_execution(): Unit =
protocol_command("Document.discontinue_execution")
def cancel_exec(id: Document_ID.Exec): Unit =
protocol_command("Document.cancel_exec", Document_ID(id))
/* document versions */
def update(old_id: Document_ID.Version, new_id: Document_ID.Version,
- edits: List[Document.Edit_Command], consolidate: List[Document.Node.Name])
+ edits: List[Document.Edit_Command], consolidate: List[Document.Node.Name]): Unit =
{
val consolidate_yxml =
{
import XML.Encode._
Symbol.encode_yxml(list(string)(consolidate.map(_.node)))
}
val edits_yxml =
{
import XML.Encode._
def id: T[Command] = (cmd => long(cmd.id))
def encode_edit(name: Document.Node.Name)
: T[Document.Node.Edit[Command.Edit, Command.Perspective]] =
variant(List(
{ case Document.Node.Edits(a) => (Nil, list(pair(option(id), option(id)))(a)) },
{ case Document.Node.Deps(header) =>
val master_dir = File.standard_url(name.master_dir)
val imports = header.imports.map(_.node)
val keywords = header.keywords.map({ case (a, spec) => (a, (spec.kind, spec.tags)) })
(Nil,
pair(string, pair(string, pair(list(string),
pair(list(pair(string, pair(string, list(string)))), list(string)))))(
(master_dir, (name.theory, (imports, (keywords, header.errors)))))) },
{ case Document.Node.Perspective(a, b, c) =>
(bool_atom(a) :: b.commands.map(cmd => long_atom(cmd.id)),
list(pair(id, pair(string, list(string))))(c.dest)) }))
edits.map({ case (name, edit) =>
Symbol.encode_yxml(pair(string, encode_edit(name))(name.node, edit)) })
}
protocol_command_args("Document.update",
Document_ID(old_id) :: Document_ID(new_id) :: consolidate_yxml :: edits_yxml)
}
- def remove_versions(versions: List[Document.Version])
+ def remove_versions(versions: List[Document.Version]): Unit =
{
val versions_yxml =
{ import XML.Encode._
Symbol.encode_yxml(list(long)(versions.map(_.id))) }
protocol_command("Document.remove_versions", versions_yxml)
}
/* dialog via document content */
def dialog_result(serial: Long, result: String): Unit =
protocol_command("Document.dialog_result", Value.Long(serial), result)
}
diff --git a/src/Pure/PIDE/prover.scala b/src/Pure/PIDE/prover.scala
--- a/src/Pure/PIDE/prover.scala
+++ b/src/Pure/PIDE/prover.scala
@@ -1,368 +1,368 @@
/* Title: Pure/PIDE/prover.scala
Author: Makarius
Options: :folding=explicit:
Prover process wrapping.
*/
package isabelle
import java.io.{InputStream, OutputStream, BufferedOutputStream, IOException}
object Prover
{
/* messages */
sealed abstract class Message
type Receiver = Message => Unit
class Input(val name: String, val args: List[String]) extends Message
{
override def toString: String =
XML.Elem(Markup(Markup.PROVER_COMMAND, List((Markup.NAME, name))),
args.flatMap(s =>
List(XML.newline, XML.elem(Markup.PROVER_ARG, YXML.parse_body(s))))).toString
}
class Output(val message: XML.Elem) extends Message
{
def kind: String = message.markup.name
def properties: Properties.T = message.markup.properties
def body: XML.Body = message.body
def is_init: Boolean = kind == Markup.INIT
def is_exit: Boolean = kind == Markup.EXIT
def is_stdout: Boolean = kind == Markup.STDOUT
def is_stderr: Boolean = kind == Markup.STDERR
def is_system: Boolean = kind == Markup.SYSTEM
def is_status: Boolean = kind == Markup.STATUS
def is_report: Boolean = kind == Markup.REPORT
def is_syslog: Boolean = is_init || is_exit || is_system || is_stderr
override def toString: String =
{
val res =
if (is_status || is_report) message.body.map(_.toString).mkString
else Pretty.string_of(message.body, metric = Symbol.Metric)
if (properties.isEmpty)
kind.toString + " [[" + res + "]]"
else
kind.toString + " " +
(for ((x, y) <- properties) yield x + "=" + y).mkString("{", ",", "}") + " [[" + res + "]]"
}
}
class Protocol_Output(props: Properties.T, val bytes: Bytes)
extends Output(XML.Elem(Markup(Markup.PROTOCOL, props), Nil))
{
lazy val text: String = bytes.text
}
}
class Prover(
receiver: Prover.Receiver,
cache: XML.Cache,
channel: System_Channel,
process: Bash.Process) extends Protocol
{
/** receiver output **/
- private def system_output(text: String)
+ private def system_output(text: String): Unit =
{
receiver(new Prover.Output(XML.Elem(Markup(Markup.SYSTEM, Nil), List(XML.Text(text)))))
}
- private def protocol_output(props: Properties.T, bytes: Bytes)
+ private def protocol_output(props: Properties.T, bytes: Bytes): Unit =
{
receiver(new Prover.Protocol_Output(cache.props(props), bytes))
}
- private def output(kind: String, props: Properties.T, body: XML.Body)
+ private def output(kind: String, props: Properties.T, body: XML.Body): Unit =
{
val main = XML.Elem(Markup(kind, props), Protocol_Message.clean_reports(body))
val reports = Protocol_Message.reports(props, body)
for (msg <- main :: reports) receiver(new Prover.Output(cache.elem(msg)))
}
- private def exit_message(result: Process_Result)
+ private def exit_message(result: Process_Result): Unit =
{
output(Markup.EXIT, Markup.Process_Result(result),
List(XML.Text(result.print_return_code)))
}
/** process manager **/
private val process_result: Future[Process_Result] =
Future.thread("process_result") {
val rc = process.join
val timing = process.get_timing
Process_Result(rc, timing = timing)
}
- private def terminate_process()
+ private def terminate_process(): Unit =
{
try { process.terminate }
catch {
case exn @ ERROR(_) => system_output("Failed to terminate prover process: " + exn.getMessage)
}
}
private val process_manager = Isabelle_Thread.fork(name = "process_manager")
{
val stdout = physical_output(false)
val (startup_failed, startup_errors) =
{
var finished: Option[Boolean] = None
val result = new StringBuilder(100)
while (finished.isEmpty && (process.stderr.ready || !process_result.is_finished)) {
while (finished.isEmpty && process.stderr.ready) {
try {
val c = process.stderr.read
if (c == 2) finished = Some(true)
else result += c.toChar
}
catch { case _: IOException => finished = Some(false) }
}
Time.seconds(0.05).sleep
}
(finished.isEmpty || !finished.get, result.toString.trim)
}
if (startup_errors != "") system_output(startup_errors)
if (startup_failed) {
terminate_process()
process_result.join
stdout.join
exit_message(Process_Result(127))
}
else {
val (command_stream, message_stream) = channel.rendezvous()
command_input_init(command_stream)
val stderr = physical_output(true)
val message = message_output(message_stream)
val result = process_result.join
system_output("process terminated")
command_input_close()
for (thread <- List(stdout, stderr, message)) thread.join
system_output("process_manager terminated")
exit_message(result)
}
channel.shutdown()
}
/* management methods */
- def join() { process_manager.join() }
+ def join(): Unit = process_manager.join()
- def terminate()
+ def terminate(): Unit =
{
system_output("Terminating prover process")
command_input_close()
var count = 10
while (!process_result.is_finished && count > 0) {
Time.seconds(0.1).sleep
count -= 1
}
if (!process_result.is_finished) terminate_process()
}
/** process streams **/
/* command input */
private var command_input: Option[Consumer_Thread[List[Bytes]]] = None
private def command_input_close(): Unit = command_input.foreach(_.shutdown)
- private def command_input_init(raw_stream: OutputStream)
+ private def command_input_init(raw_stream: OutputStream): Unit =
{
val name = "command_input"
val stream = new BufferedOutputStream(raw_stream)
command_input =
Some(
Consumer_Thread.fork(name)(
consume =
{
case chunks =>
try {
Bytes(chunks.map(_.length).mkString("", ",", "\n")).write_stream(stream)
chunks.foreach(_.write_stream(stream))
stream.flush
true
}
catch { case e: IOException => system_output(name + ": " + e.getMessage); false }
},
finish = { case () => stream.close; system_output(name + " terminated") }
)
)
}
/* physical output */
private def physical_output(err: Boolean): Thread =
{
val (name, reader, markup) =
if (err) ("standard_error", process.stderr, Markup.STDERR)
else ("standard_output", process.stdout, Markup.STDOUT)
Isabelle_Thread.fork(name = name) {
try {
var result = new StringBuilder(100)
var finished = false
while (!finished) {
//{{{
var c = -1
var done = false
while (!done && (result.isEmpty || reader.ready)) {
c = reader.read
if (c >= 0) result.append(c.asInstanceOf[Char])
else done = true
}
if (result.nonEmpty) {
output(markup, Nil, List(XML.Text(Symbol.decode(result.toString))))
result.clear
}
else {
reader.close
finished = true
}
//}}}
}
}
catch { case e: IOException => system_output(name + ": " + e.getMessage) }
system_output(name + " terminated")
}
}
/* message output */
private def message_output(stream: InputStream): Thread =
{
class EOF extends Exception
class Protocol_Error(msg: String) extends Exception(msg)
val name = "message_output"
Isabelle_Thread.fork(name = name) {
val default_buffer = new Array[Byte](65536)
var c = -1
def read_int(): Int =
//{{{
{
var n = 0
c = stream.read
if (c == -1) throw new EOF
while (48 <= c && c <= 57) {
n = 10 * n + (c - 48)
c = stream.read
}
if (c != 10)
throw new Protocol_Error("malformed header: expected integer followed by newline")
else n
}
//}}}
def read_chunk_bytes(): (Array[Byte], Int) =
//{{{
{
val n = read_int()
val buf =
if (n <= default_buffer.length) default_buffer
else new Array[Byte](n)
var i = 0
var m = 0
do {
m = stream.read(buf, i, n - i)
if (m != -1) i += m
}
while (m != -1 && n > i)
if (i != n)
throw new Protocol_Error("bad chunk (unexpected EOF after " + i + " of " + n + " bytes)")
(buf, n)
}
//}}}
def read_chunk(): XML.Body =
{
val (buf, n) = read_chunk_bytes()
YXML.parse_body_failsafe(UTF8.decode_chars(Symbol.decode, buf, 0, n))
}
try {
do {
try {
val header = read_chunk()
header match {
case List(XML.Elem(Markup(name, props), Nil)) =>
val kind = name.intern
if (kind == Markup.PROTOCOL) {
val (buf, n) = read_chunk_bytes()
protocol_output(props, Bytes(buf, 0, n))
}
else {
val body = read_chunk()
output(kind, props, body)
}
case _ =>
read_chunk()
throw new Protocol_Error("bad header: " + header.toString)
}
}
catch { case _: EOF => }
}
while (c != -1)
}
catch {
case e: IOException => system_output("Cannot read message:\n" + e.getMessage)
case e: Protocol_Error => system_output("Malformed message:\n" + e.getMessage)
}
stream.close
system_output(name + " terminated")
}
}
/** protocol commands **/
var trace: Boolean = false
def protocol_command_raw(name: String, args: List[Bytes]): Unit =
command_input match {
case Some(thread) if thread.is_active =>
if (trace) {
val payload = (0 /: args)({ case (n, b) => n + b.length })
Output.writeln(
"protocol_command " + name + ", args = " + args.length + ", payload = " + payload)
}
thread.send(Bytes(name) :: args)
case _ => error("Inactive prover input thread for command " + quote(name))
}
- def protocol_command_args(name: String, args: List[String])
+ def protocol_command_args(name: String, args: List[String]): Unit =
{
receiver(new Prover.Input(name, args))
protocol_command_raw(name, args.map(Bytes(_)))
}
def protocol_command(name: String, args: String*): Unit =
protocol_command_args(name, args.toList)
}
diff --git a/src/Pure/PIDE/query_operation.scala b/src/Pure/PIDE/query_operation.scala
--- a/src/Pure/PIDE/query_operation.scala
+++ b/src/Pure/PIDE/query_operation.scala
@@ -1,243 +1,245 @@
/* Title: Pure/PIDE/query_operation.scala
Author: Makarius
One-shot query operations via asynchronous print functions and temporary
document overlays.
*/
package isabelle
object Query_Operation
{
object Status extends Enumeration
{
val WAITING = Value("waiting")
val RUNNING = Value("running")
val FINISHED = Value("finished")
}
object State
{
val empty: State = State()
def make(command: Command, query: List[String]): State =
State(instance = Document_ID.make().toString,
location = Some(command),
query = query,
status = Status.WAITING)
}
sealed case class State(
instance: String = Document_ID.none.toString,
location: Option[Command] = None,
query: List[String] = Nil,
update_pending: Boolean = false,
output: List[XML.Tree] = Nil,
status: Status.Value = Status.FINISHED,
exec_id: Document_ID.Exec = Document_ID.none)
}
class Query_Operation[Editor_Context](
editor: Editor[Editor_Context],
editor_context: Editor_Context,
operation_name: String,
consume_status: Query_Operation.Status.Value => Unit,
consume_output: (Document.Snapshot, Command.Results, XML.Body) => Unit)
{
private val print_function = operation_name + "_query"
/* implicit state -- owned by editor thread */
private val current_state = Synchronized(Query_Operation.State.empty)
def get_location: Option[Command] = current_state.value.location
- private def remove_overlay()
+ private def remove_overlay(): Unit =
{
val state = current_state.value
for (command <- state.location) {
editor.remove_overlay(command, print_function, state.instance :: state.query)
}
}
/* content update */
- private def content_update()
+ private def content_update(): Unit =
{
editor.require_dispatcher {}
/* snapshot */
val state0 = current_state.value
val (snapshot, command_results, results, removed) =
state0.location match {
case Some(cmd) =>
val snapshot = editor.node_snapshot(cmd.node_name)
val command_results = snapshot.command_results(cmd)
val results =
(for {
(_, elem @ XML.Elem(Markup(Markup.RESULT, props), _)) <- command_results.iterator
if props.contains((Markup.INSTANCE, state0.instance))
} yield elem).toList
val removed = !snapshot.get_node(cmd.node_name).commands.contains(cmd)
(snapshot, command_results, results, removed)
case None =>
(Document.Snapshot.init, Command.Results.empty, Nil, true)
}
/* resolve sendback: static command id */
def resolve_sendback(body: XML.Body): XML.Body =
{
state0.location match {
case None => body
case Some(command) =>
def resolve(body: XML.Body): XML.Body =
body map {
case XML.Wrapped_Elem(m, b1, b2) => XML.Wrapped_Elem(m, resolve(b1), resolve(b2))
case XML.Elem(Markup(Markup.SENDBACK, props), b) =>
val props1 =
props.map({
case (Markup.ID, Value.Long(id)) if id == state0.exec_id =>
(Markup.ID, Value.Long(command.id))
case p => p
})
XML.Elem(Markup(Markup.SENDBACK, props1), resolve(b))
case XML.Elem(m, b) => XML.Elem(m, resolve(b))
case t => t
}
resolve(body)
}
}
/* output */
val new_output =
for {
XML.Elem(_, List(XML.Elem(markup, body))) <- results
if Markup.messages.contains(markup.name)
body1 = resolve_sendback(body)
} yield XML.Elem(Markup(Markup.message(markup.name), markup.properties), body1)
/* status */
def get_status(name: String, status: Query_Operation.Status.Value)
: Option[Query_Operation.Status.Value] =
results.collectFirst({ case XML.Elem(_, List(elem: XML.Elem)) if elem.name == name => status })
val new_status =
if (removed) Query_Operation.Status.FINISHED
else
get_status(Markup.FINISHED, Query_Operation.Status.FINISHED) orElse
get_status(Markup.RUNNING, Query_Operation.Status.RUNNING) getOrElse
Query_Operation.Status.WAITING
/* state update */
if (new_status == Query_Operation.Status.RUNNING)
results.collectFirst(
{
case XML.Elem(Markup(_, Position.Id(id)), List(elem: XML.Elem))
if elem.name == Markup.RUNNING => id
}).foreach(id => current_state.change(_.copy(exec_id = id)))
if (state0.output != new_output || state0.status != new_status) {
if (snapshot.is_outdated)
current_state.change(_.copy(update_pending = true))
else {
current_state.change(_.copy(update_pending = false))
if (state0.output != new_output && !removed) {
current_state.change(_.copy(output = new_output))
consume_output(snapshot, command_results, new_output)
}
if (state0.status != new_status) {
current_state.change(_.copy(status = new_status))
consume_status(new_status)
if (new_status == Query_Operation.Status.FINISHED)
remove_overlay()
}
}
}
}
/* query operations */
def cancel_query(): Unit =
editor.require_dispatcher { editor.session.cancel_exec(current_state.value.exec_id) }
- def apply_query(query: List[String])
+ def apply_query(query: List[String]): Unit =
{
editor.require_dispatcher {}
editor.current_node_snapshot(editor_context) match {
case Some(snapshot) =>
remove_overlay()
current_state.change(_ => Query_Operation.State.empty)
consume_output(Document.Snapshot.init, Command.Results.empty, Nil)
editor.current_command(editor_context, snapshot) match {
case Some(command) =>
val state = Query_Operation.State.make(command, query)
current_state.change(_ => state)
editor.insert_overlay(command, print_function, state.instance :: query)
case None =>
}
consume_status(current_state.value.status)
editor.flush()
case None =>
}
}
- def locate_query()
+ def locate_query(): Unit =
{
editor.require_dispatcher {}
val state = current_state.value
for {
command <- state.location
snapshot = editor.node_snapshot(command.node_name)
link <- editor.hyperlink_command(true, snapshot, command.id)
} link.follow(editor_context)
}
/* main */
private val main =
Session.Consumer[Session.Commands_Changed](getClass.getName) {
case changed =>
val state = current_state.value
state.location match {
case Some(command)
if state.update_pending ||
(state.status != Query_Operation.Status.FINISHED &&
changed.commands.contains(command)) =>
editor.send_dispatcher { content_update() }
case _ =>
}
}
- def activate() {
+ def activate(): Unit =
+ {
editor.session.commands_changed += main
}
- def deactivate() {
+ def deactivate(): Unit =
+ {
editor.session.commands_changed -= main
remove_overlay()
current_state.change(_ => Query_Operation.State.empty)
consume_output(Document.Snapshot.init, Command.Results.empty, Nil)
consume_status(Query_Operation.Status.FINISHED)
}
}
diff --git a/src/Pure/PIDE/resources.scala b/src/Pure/PIDE/resources.scala
--- a/src/Pure/PIDE/resources.scala
+++ b/src/Pure/PIDE/resources.scala
@@ -1,449 +1,449 @@
/* Title: Pure/PIDE/resources.scala
Author: Makarius
Resources for theories and auxiliary files.
*/
package isabelle
import scala.util.parsing.input.Reader
import java.io.{File => JFile}
object Resources
{
def empty: Resources =
new Resources(Sessions.Structure.empty, Sessions.Structure.empty.bootstrap)
}
class Resources(
val sessions_structure: Sessions.Structure,
val session_base: Sessions.Base,
val log: Logger = No_Logger,
command_timings: List[Properties.T] = Nil)
{
resources =>
/* init session */
def init_session_yxml: String =
{
import XML.Encode._
YXML.string_of_body(
pair(list(pair(string, properties)),
pair(list(pair(string, string)),
pair(list(pair(string, string)),
pair(list(pair(string, list(string))),
pair(list(properties),
pair(list(pair(string, properties)),
pair(list(pair(string, string)), list(string))))))))(
(sessions_structure.session_positions,
(sessions_structure.dest_session_directories,
(sessions_structure.session_chapters,
(sessions_structure.bibtex_entries,
(command_timings,
(Scala.functions.map(fun => (fun.name, fun.position)),
(session_base.global_theories.toList,
session_base.loaded_theories.keys)))))))))
}
/* file formats */
def make_theory_name(name: Document.Node.Name): Option[Document.Node.Name] =
File_Format.registry.get(name).flatMap(_.make_theory_name(resources, name))
def make_theory_content(thy_name: Document.Node.Name): Option[String] =
File_Format.registry.get_theory(thy_name).flatMap(_.make_theory_content(resources, thy_name))
def is_hidden(name: Document.Node.Name): Boolean =
!name.is_theory || name.theory == Sessions.root_name || File_Format.registry.is_theory(name)
def html_document(snapshot: Document.Snapshot): Option[Presentation.HTML_Document] =
File_Format.registry.get(snapshot.node_name).flatMap(_.html_document(snapshot))
/* file-system operations */
def append(dir: String, source_path: Path): String =
(Path.explode(dir) + source_path).expand.implode
def append(node_name: Document.Node.Name, source_path: Path): String =
append(node_name.master_dir, source_path)
def file_node(file: Path, dir: String = "", theory: String = ""): Document.Node.Name =
{
val node = append(dir, file)
val master_dir = append(dir, file.dir)
Document.Node.Name(node, master_dir, theory)
}
def loaded_theory_node(theory: String): Document.Node.Name =
Document.Node.Name(theory, "", theory)
/* source files of Isabelle/ML bootstrap */
def source_file(raw_name: String): Option[String] =
{
if (Path.is_wellformed(raw_name)) {
if (Path.is_valid(raw_name)) {
def check(p: Path): Option[Path] = if (p.is_file) Some(p) else None
val path = Path.explode(raw_name)
val path1 =
if (path.is_absolute || path.is_current) check(path)
else {
check(Path.explode("~~/src/Pure") + path) orElse
(if (Isabelle_System.getenv("ML_SOURCES") == "") None
else check(Path.explode("$ML_SOURCES") + path))
}
Some(File.platform_path(path1 getOrElse path))
}
else None
}
else Some(raw_name)
}
/* theory files */
def load_commands(syntax: Outer_Syntax, name: Document.Node.Name)
: () => List[Command_Span.Span] =
{
val (is_utf8, raw_text) =
with_thy_reader(name, reader => (Scan.reader_is_utf8(reader), reader.source.toString))
() =>
{
if (syntax.has_load_commands(raw_text)) {
val text = Symbol.decode(Scan.reader_decode_utf8(is_utf8, raw_text))
syntax.parse_spans(text).filter(_.is_load_command(syntax))
}
else Nil
}
}
def loaded_files(syntax: Outer_Syntax, name: Document.Node.Name, spans: List[Command_Span.Span])
: List[Path] =
{
val dir = name.master_dir_path
for { span <- spans; file <- span.loaded_files(syntax).files }
yield (dir + Path.explode(file)).expand
}
def pure_files(syntax: Outer_Syntax): List[Path] =
{
val pure_dir = Path.explode("~~/src/Pure")
for {
(name, theory) <- Thy_Header.ml_roots
path = (pure_dir + Path.explode(name)).expand
node_name = Document.Node.Name(path.implode, path.dir.implode, theory)
file <- loaded_files(syntax, node_name, load_commands(syntax, node_name)())
} yield file
}
def theory_name(qualifier: String, theory: String): String =
if (Long_Name.is_qualified(theory) || session_base.global_theories.isDefinedAt(theory))
theory
else Long_Name.qualify(qualifier, theory)
def find_theory_node(theory: String): Option[Document.Node.Name] =
{
val thy_file = Path.basic(Long_Name.base_name(theory)).thy
val session = session_base.theory_qualifier(theory)
val dirs =
sessions_structure.get(session) match {
case Some(info) => info.dirs
case None => Nil
}
dirs.collectFirst({
case dir if (dir + thy_file).is_file => file_node(dir + thy_file, theory = theory) })
}
def import_name(qualifier: String, dir: String, s: String): Document.Node.Name =
{
val theory = theory_name(qualifier, Thy_Header.import_name(s))
def theory_node = file_node(Path.explode(s).thy, dir = dir, theory = theory)
if (!Thy_Header.is_base_name(s)) theory_node
else if (session_base.loaded_theory(theory)) loaded_theory_node(theory)
else {
find_theory_node(theory) match {
case Some(node_name) => node_name
case None => if (Long_Name.is_qualified(s)) loaded_theory_node(theory) else theory_node
}
}
}
def import_name(name: Document.Node.Name, s: String): Document.Node.Name =
import_name(session_base.theory_qualifier(name), name.master_dir, s)
def import_name(info: Sessions.Info, s: String): Document.Node.Name =
import_name(info.name, info.dir.implode, s)
def find_theory(file: JFile): Option[Document.Node.Name] =
{
for {
qualifier <- session_base.session_directories.get(File.canonical(file).getParentFile)
theory_base <- proper_string(Thy_Header.theory_name(file.getName))
theory = theory_name(qualifier, theory_base)
theory_node <- find_theory_node(theory)
if File.eq(theory_node.path.file, file)
} yield theory_node
}
def complete_import_name(context_name: Document.Node.Name, s: String): List[String] =
{
val context_session = session_base.theory_qualifier(context_name)
val context_dir =
try { Some(context_name.master_dir_path) }
catch { case ERROR(_) => None }
(for {
(session, (info, _)) <- sessions_structure.imports_graph.iterator
dir <- (if (session == context_session) context_dir.toList else info.dirs).iterator
theory <- Thy_Header.try_read_dir(dir).iterator
if Completion.completed(s)(theory)
} yield {
if (session == context_session || session_base.global_theories.isDefinedAt(theory)) theory
else Long_Name.qualify(session, theory)
}).toList.sorted
}
def with_thy_reader[A](name: Document.Node.Name, f: Reader[Char] => A): A =
{
val path = name.path
if (path.is_file) using(Scan.byte_reader(path.file))(f)
else if (name.node == name.theory)
error("Cannot load theory " + quote(name.theory))
else error ("Cannot load theory file " + path)
}
def check_thy(node_name: Document.Node.Name, reader: Reader[Char],
command: Boolean = true, strict: Boolean = true): Document.Node.Header =
{
if (node_name.is_theory && reader.source.length > 0) {
try {
val header = Thy_Header.read(node_name, reader, command = command, strict = strict)
val imports =
header.imports.map({ case (s, pos) =>
val name = import_name(node_name, s)
if (Sessions.exclude_theory(name.theory_base_name))
error("Bad theory name " + quote(name.theory_base_name) + Position.here(pos))
(name, pos)
})
Document.Node.Header(imports, header.keywords, header.abbrevs)
}
catch { case exn: Throwable => Document.Node.bad_header(Exn.message(exn)) }
}
else Document.Node.no_header
}
/* special header */
def special_header(name: Document.Node.Name): Option[Document.Node.Header] =
{
val imports =
if (name.theory == Sessions.root_name) List(import_name(name, Sessions.theory_name))
else if (Thy_Header.is_ml_root(name.theory)) List(import_name(name, Thy_Header.ML_BOOTSTRAP))
else if (Thy_Header.is_bootstrap(name.theory)) List(import_name(name, Thy_Header.PURE))
else Nil
if (imports.isEmpty) None
else Some(Document.Node.Header(imports.map((_, Position.none))))
}
/* blobs */
def undefined_blobs(nodes: Document.Nodes): List[Document.Node.Name] =
(for {
(node_name, node) <- nodes.iterator
if !session_base.loaded_theory(node_name)
cmd <- node.load_commands.iterator
name <- cmd.blobs_undefined.iterator
} yield name).toList
/* document changes */
def parse_change(
reparse_limit: Int,
previous: Document.Version,
doc_blobs: Document.Blobs,
edits: List[Document.Edit_Text],
consolidate: List[Document.Node.Name]): Session.Change =
Thy_Syntax.parse_change(resources, reparse_limit, previous, doc_blobs, edits, consolidate)
- def commit(change: Session.Change) { }
+ def commit(change: Session.Change): Unit = {}
/* theory and file dependencies */
def dependencies(
thys: List[(Document.Node.Name, Position.T)],
progress: Progress = new Progress): Dependencies[Unit] =
Dependencies.empty[Unit].require_thys((), thys, progress = progress)
def session_dependencies(info: Sessions.Info, progress: Progress = new Progress)
: Dependencies[Options] =
{
(Dependencies.empty[Options] /: info.theories)({ case (dependencies, (options, thys)) =>
dependencies.require_thys(options,
for { (thy, pos) <- thys } yield (import_name(info, thy), pos),
progress = progress)
})
}
object Dependencies
{
def empty[A]: Dependencies[A] = new Dependencies[A](Nil, Map.empty)
private def show_path(names: List[Document.Node.Name]): String =
names.map(name => quote(name.theory)).mkString(" via ")
private def cycle_msg(names: List[Document.Node.Name]): String =
"Cyclic dependency of " + show_path(names)
private def required_by(initiators: List[Document.Node.Name]): String =
if (initiators.isEmpty) ""
else "\n(required by " + show_path(initiators.reverse) + ")"
}
final class Dependencies[A] private(
rev_entries: List[Document.Node.Entry],
seen: Map[Document.Node.Name, A])
{
private def cons(entry: Document.Node.Entry): Dependencies[A] =
new Dependencies[A](entry :: rev_entries, seen)
def require_thy(adjunct: A,
thy: (Document.Node.Name, Position.T),
initiators: List[Document.Node.Name] = Nil,
progress: Progress = new Progress): Dependencies[A] =
{
val (name, pos) = thy
def message: String =
"The error(s) above occurred for theory " + quote(name.theory) +
Dependencies.required_by(initiators) + Position.here(pos)
if (seen.isDefinedAt(name)) this
else {
val dependencies1 = new Dependencies[A](rev_entries, seen + (name -> adjunct))
if (session_base.loaded_theory(name)) dependencies1
else {
try {
if (initiators.contains(name)) error(Dependencies.cycle_msg(initiators))
progress.expose_interrupt()
val header =
try {
with_thy_reader(name, check_thy(name, _, command = false)).cat_errors(message)
}
catch { case ERROR(msg) => cat_error(msg, message) }
val entry = Document.Node.Entry(name, header)
dependencies1.require_thys(adjunct, header.imports_pos,
initiators = name :: initiators, progress = progress).cons(entry)
}
catch {
case e: Throwable =>
dependencies1.cons(Document.Node.Entry(name, Document.Node.bad_header(Exn.message(e))))
}
}
}
}
def require_thys(adjunct: A,
thys: List[(Document.Node.Name, Position.T)],
progress: Progress = new Progress,
initiators: List[Document.Node.Name] = Nil): Dependencies[A] =
(this /: thys)(_.require_thy(adjunct, _, progress = progress, initiators = initiators))
def entries: List[Document.Node.Entry] = rev_entries.reverse
def theories: List[Document.Node.Name] = entries.map(_.name)
def theories_adjunct: List[(Document.Node.Name, A)] = theories.map(name => (name, seen(name)))
def errors: List[String] = entries.flatMap(_.header.errors)
def check_errors: Dependencies[A] =
errors match {
case Nil => this
case errs => error(cat_lines(errs))
}
lazy val theory_graph: Document.Node.Name.Graph[Unit] =
{
val regular = theories.toSet
val irregular =
(for {
entry <- entries.iterator
imp <- entry.header.imports
if !regular(imp)
} yield imp).toSet
Document.Node.Name.make_graph(
irregular.toList.map(name => ((name, ()), Nil)) :::
entries.map(entry => ((entry.name, ()), entry.header.imports)))
}
lazy val loaded_theories: Graph[String, Outer_Syntax] =
(session_base.loaded_theories /: entries)({ case (graph, entry) =>
val name = entry.name.theory
val imports = entry.header.imports.map(_.theory)
val graph1 = (graph /: (name :: imports))(_.default_node(_, Outer_Syntax.empty))
val graph2 = (graph1 /: imports)(_.add_edge(_, name))
val syntax0 = if (name == Thy_Header.PURE) List(Thy_Header.bootstrap_syntax) else Nil
val syntax1 = (name :: graph2.imm_preds(name).toList).map(graph2.get_node)
val syntax = Outer_Syntax.merge(syntax0 ::: syntax1) + entry.header
graph2.map_node(name, _ => syntax)
})
def get_syntax(name: Document.Node.Name): Outer_Syntax =
loaded_theories.get_node(name.theory)
def load_commands: List[(Document.Node.Name, List[Command_Span.Span])] =
theories.zip(
Par_List.map((e: () => List[Command_Span.Span]) => e(),
theories.map(name => resources.load_commands(get_syntax(name), name))))
.filter(p => p._2.nonEmpty)
def loaded_files(name: Document.Node.Name, spans: List[Command_Span.Span])
: (String, List[Path]) =
{
val theory = name.theory
val syntax = get_syntax(name)
val files1 = resources.loaded_files(syntax, name, spans)
val files2 = if (theory == Thy_Header.PURE) pure_files(syntax) else Nil
(theory, files1 ::: files2)
}
def loaded_files: List[Path] =
for {
(name, spans) <- load_commands
file <- loaded_files(name, spans)._2
} yield file
def imported_files: List[Path] =
{
val base_theories =
loaded_theories.all_preds(theories.map(_.theory)).
filter(session_base.loaded_theories.defined)
base_theories.map(theory => session_base.known_theories(theory).name.path) :::
base_theories.flatMap(session_base.known_loaded_files.withDefaultValue(Nil))
}
lazy val overall_syntax: Outer_Syntax =
Outer_Syntax.merge(loaded_theories.maximals.map(loaded_theories.get_node))
override def toString: String = entries.toString
}
}
diff --git a/src/Pure/PIDE/session.scala b/src/Pure/PIDE/session.scala
--- a/src/Pure/PIDE/session.scala
+++ b/src/Pure/PIDE/session.scala
@@ -1,776 +1,774 @@
/* Title: Pure/PIDE/session.scala
Author: Makarius
Options: :folding=explicit:
PIDE editor session, potentially with running prover process.
*/
package isabelle
import scala.collection.immutable.Queue
import scala.collection.mutable
import scala.annotation.tailrec
object Session
{
/* outlets */
object Consumer
{
def apply[A](name: String)(consume: A => Unit): Consumer[A] =
new Consumer[A](name, consume)
}
final class Consumer[-A] private(val name: String, val consume: A => Unit)
class Outlet[A](dispatcher: Consumer_Thread[() => Unit])
{
private val consumers = Synchronized[List[Consumer[A]]](Nil)
- def += (c: Consumer[A]) { consumers.change(Library.update(c)) }
- def -= (c: Consumer[A]) { consumers.change(Library.remove(c)) }
+ def += (c: Consumer[A]): Unit = consumers.change(Library.update(c))
+ def -= (c: Consumer[A]): Unit = consumers.change(Library.remove(c))
- def post(a: A)
+ def post(a: A): Unit =
{
for (c <- consumers.value.iterator) {
dispatcher.send(() =>
try { c.consume(a) }
catch {
case exn: Throwable =>
Output.error_message("Consumer failed: " + quote(c.name) + "\n" + Exn.message(exn))
})
}
}
}
/* change */
sealed case class Change(
previous: Document.Version,
syntax_changed: List[Document.Node.Name],
deps_changed: Boolean,
doc_edits: List[Document.Edit_Command],
consolidate: List[Document.Node.Name],
version: Document.Version)
case object Change_Flush
/* events */
//{{{
case class Command_Timing(props: Properties.T)
case class Theory_Timing(props: Properties.T)
case class Runtime_Statistics(props: Properties.T)
case class Task_Statistics(props: Properties.T)
case class Global_Options(options: Options)
case object Caret_Focus
case class Raw_Edits(doc_blobs: Document.Blobs, edits: List[Document.Edit_Text])
case class Dialog_Result(id: Document_ID.Generic, serial: Long, result: String)
case class Build_Theories(id: String, master_dir: Path, theories: List[(Options, List[Path])])
case class Commands_Changed(
assignment: Boolean, nodes: Set[Document.Node.Name], commands: Set[Command])
sealed abstract class Phase
{
def print: String =
this match {
case Terminated(result) => if (result.ok) "finished" else "failed"
case _ => Word.lowercase(this.toString)
}
}
case object Inactive extends Phase // stable
case object Startup extends Phase // transient
case object Ready extends Phase // metastable
case object Shutdown extends Phase // transient
case class Terminated(result: Process_Result) extends Phase // stable
//}}}
/* syslog */
private[Session] class Syslog(limit: Int)
{
private var queue = Queue.empty[XML.Elem]
private var length = 0
def += (msg: XML.Elem): Unit = synchronized {
queue = queue.enqueue(msg)
length += 1
if (length > limit) queue = queue.dequeue._2
}
def content: String = synchronized {
cat_lines(queue.iterator.map(XML.content)) +
(if (length > limit) "\n(A total of " + length + " messages...)" else "")
}
}
/* protocol handlers */
type Protocol_Function = Prover.Protocol_Output => Boolean
abstract class Protocol_Handler extends Isabelle_System.Service
{
def init(session: Session): Unit = {}
def exit(): Unit = {}
def functions: List[(String, Protocol_Function)] = Nil
def prover_options(options: Options): Options = options
}
}
class Session(_session_options: => Options, val resources: Resources) extends Document.Session
{
session =>
val cache: XML.Cache = XML.Cache.make()
def build_blobs_info(name: Document.Node.Name): Command.Blobs_Info =
Command.Blobs_Info.none
/* global flags */
@volatile var timing: Boolean = false
@volatile var verbose: Boolean = false
/* dynamic session options */
def session_options: Options = _session_options
def output_delay: Time = session_options.seconds("editor_output_delay")
def consolidate_delay: Time = session_options.seconds("editor_consolidate_delay")
def prune_delay: Time = session_options.seconds("editor_prune_delay")
def prune_size: Int = session_options.int("editor_prune_size")
def syslog_limit: Int = session_options.int("editor_syslog_limit")
def reparse_limit: Int = session_options.int("editor_reparse_limit")
/* dispatcher */
private val dispatcher =
Consumer_Thread.fork[() => Unit]("Session.dispatcher", daemon = true) { case e => e(); true }
def assert_dispatcher[A](body: => A): A =
{
assert(dispatcher.check_thread)
body
}
def require_dispatcher[A](body: => A): A =
{
require(dispatcher.check_thread, "not on dispatcher thread")
body
}
def send_dispatcher(body: => Unit): Unit =
{
if (dispatcher.check_thread) body
else dispatcher.send(() => body)
}
def send_wait_dispatcher(body: => Unit): Unit =
{
if (dispatcher.check_thread) body
else dispatcher.send_wait(() => body)
}
/* outlets */
val finished_theories = new Session.Outlet[Document.Snapshot](dispatcher)
val command_timings = new Session.Outlet[Session.Command_Timing](dispatcher)
val theory_timings = new Session.Outlet[Session.Theory_Timing](dispatcher)
val runtime_statistics = new Session.Outlet[Session.Runtime_Statistics](dispatcher)
val task_statistics = new Session.Outlet[Session.Task_Statistics](dispatcher)
val global_options = new Session.Outlet[Session.Global_Options](dispatcher)
val caret_focus = new Session.Outlet[Session.Caret_Focus.type](dispatcher)
val raw_edits = new Session.Outlet[Session.Raw_Edits](dispatcher)
val commands_changed = new Session.Outlet[Session.Commands_Changed](dispatcher)
val phase_changed = new Session.Outlet[Session.Phase](dispatcher)
val syslog_messages = new Session.Outlet[Prover.Output](dispatcher)
val raw_output_messages = new Session.Outlet[Prover.Output](dispatcher)
val trace_events = new Session.Outlet[Simplifier_Trace.Event.type](dispatcher)
val debugger_updates = new Session.Outlet[Debugger.Update.type](dispatcher)
val all_messages = new Session.Outlet[Prover.Message](dispatcher) // potential bottle-neck!
/** main protocol manager **/
/* internal messages */
private case class Start(start_prover: Prover.Receiver => Prover)
private case object Stop
private case class Get_State(promise: Promise[Document.State])
private case class Cancel_Exec(exec_id: Document_ID.Exec)
private case class Protocol_Command(name: String, args: List[String])
private case class Update_Options(options: Options)
private case object Consolidate_Execution
private case object Prune_History
/* phase */
private def post_phase(new_phase: Session.Phase): Session.Phase =
{
phase_changed.post(new_phase)
new_phase
}
private val _phase = Synchronized[Session.Phase](Session.Inactive)
private def phase_=(new_phase: Session.Phase): Unit = _phase.change(_ => post_phase(new_phase))
def phase: Session.Phase = _phase.value
def is_ready: Boolean = phase == Session.Ready
/* syslog */
private val syslog = new Session.Syslog(syslog_limit)
def syslog_content(): String = syslog.content
/* pipelined change parsing */
private case class Text_Edits(
previous: Future[Document.Version],
doc_blobs: Document.Blobs,
text_edits: List[Document.Edit_Text],
consolidate: List[Document.Node.Name],
version_result: Promise[Document.Version])
private val change_parser = Consumer_Thread.fork[Text_Edits]("change_parser", daemon = true)
{
case Text_Edits(previous, doc_blobs, text_edits, consolidate, version_result) =>
val prev = previous.get_finished
val change =
Timing.timeit("parse_change", timing) {
resources.parse_change(reparse_limit, prev, doc_blobs, text_edits, consolidate)
}
version_result.fulfill(change.version)
manager.send(change)
true
}
/* buffered changes */
private object change_buffer
{
private var assignment: Boolean = false
private var nodes: Set[Document.Node.Name] = Set.empty
private var commands: Set[Command] = Set.empty
def flush(): Unit = synchronized {
if (assignment || nodes.nonEmpty || commands.nonEmpty)
commands_changed.post(Session.Commands_Changed(assignment, nodes, commands))
if (nodes.nonEmpty) consolidation.update(nodes)
assignment = false
nodes = Set.empty
commands = Set.empty
}
private val delay_flush = Delay.first(output_delay) { flush() }
def invoke(assign: Boolean, edited_nodes: List[Document.Node.Name], cmds: List[Command]): Unit =
synchronized {
assignment |= assign
for (node <- edited_nodes) {
nodes += node
}
for (command <- cmds) {
nodes += command.node_name
command.blobs_names.foreach(nodes += _)
commands += command
}
delay_flush.invoke()
}
- def shutdown()
+ def shutdown(): Unit =
{
delay_flush.revoke()
flush()
}
}
/* postponed changes */
private object postponed_changes
{
private var postponed: List[Session.Change] = Nil
def store(change: Session.Change): Unit = synchronized { postponed ::= change }
def flush(state: Document.State): List[Session.Change] = synchronized {
val (assigned, unassigned) = postponed.partition(change => state.is_assigned(change.previous))
postponed = unassigned
assigned.reverse
}
}
/* node consolidation */
private object consolidation
{
private val delay =
Delay.first(consolidate_delay) { manager.send(Consolidate_Execution) }
private val init_state: Option[Set[Document.Node.Name]] = Some(Set.empty)
private val state = Synchronized(init_state)
- def exit()
+ def exit(): Unit =
{
delay.revoke()
state.change(_ => None)
}
- def update(new_nodes: Set[Document.Node.Name] = Set.empty)
+ def update(new_nodes: Set[Document.Node.Name] = Set.empty): Unit =
{
val active =
state.change_result(st =>
(st.isDefined, st.map(nodes => if (nodes.isEmpty) new_nodes else nodes ++ new_nodes)))
if (active) delay.invoke()
}
def flush(): Set[Document.Node.Name] =
state.change_result(st => if (st.isDefined) (st.get, init_state) else (Set.empty, None))
}
/* prover process */
private object prover
{
private val variable = Synchronized[Option[Prover]](None)
def defined: Boolean = variable.value.isDefined
def get: Prover = variable.value.get
- def set(p: Prover) { variable.change(_ => Some(p)) }
- def reset { variable.change(_ => None) }
- def await_reset() { variable.guarded_access({ case None => Some((), None) case _ => None }) }
+ def set(p: Prover): Unit = variable.change(_ => Some(p))
+ def reset: Unit = variable.change(_ => None)
+ def await_reset(): Unit = variable.guarded_access({ case None => Some((), None) case _ => None })
}
/* file formats and protocol handlers */
private lazy val file_formats: File_Format.Session =
File_Format.registry.start_session(session)
private val protocol_handlers = Protocol_Handlers.init(session)
def get_protocol_handler[C <: Session.Protocol_Handler](c: Class[C]): Option[C] =
protocol_handlers.get(c.getName) match {
case Some(h) if Library.is_subclass(h.getClass, c) => Some(h.asInstanceOf[C])
case _ => None
}
def init_protocol_handler(handler: Session.Protocol_Handler): Unit =
protocol_handlers.init(handler)
def prover_options(options: Options): Options =
protocol_handlers.prover_options(file_formats.prover_options(options))
/* debugger */
private val debugger_handler = new Debugger.Handler(this)
init_protocol_handler(debugger_handler)
def debugger: Debugger = debugger_handler.debugger
/* manager thread */
private val delay_prune =
Delay.first(prune_delay) { manager.send(Prune_History) }
private val manager: Consumer_Thread[Any] =
{
/* global state */
val global_state = Synchronized(Document.State.init)
/* raw edits */
def handle_raw_edits(
doc_blobs: Document.Blobs = Document.Blobs.empty,
edits: List[Document.Edit_Text] = Nil,
- consolidate: List[Document.Node.Name] = Nil)
+ consolidate: List[Document.Node.Name] = Nil): Unit =
//{{{
{
require(prover.defined, "prover process not defined (handle_raw_edits)")
if (edits.nonEmpty) prover.get.discontinue_execution()
val previous = global_state.value.history.tip.version
val version = Future.promise[Document.Version]
global_state.change(_.continue_history(previous, edits, version))
raw_edits.post(Session.Raw_Edits(doc_blobs, edits))
change_parser.send(Text_Edits(previous, doc_blobs, edits, consolidate, version))
}
//}}}
/* resulting changes */
- def handle_change(change: Session.Change)
+ def handle_change(change: Session.Change): Unit =
//{{{
{
require(prover.defined, "prover process not defined (handle_change)")
// define commands
{
val id_commands = new mutable.ListBuffer[Command]
- def id_command(command: Command)
+ def id_command(command: Command): Unit =
{
for {
(name, digest) <- command.blobs_defined
if !global_state.value.defined_blob(digest)
} {
change.version.nodes(name).get_blob match {
case Some(blob) =>
global_state.change(_.define_blob(digest))
prover.get.define_blob(digest, blob.bytes)
case None =>
Output.error_message("Missing blob " + quote(name.toString))
}
}
if (!global_state.value.defined_command(command.id)) {
global_state.change(_.define_command(command))
id_commands += command
}
}
for { (_, edit) <- change.doc_edits } {
edit.foreach({ case (c1, c2) => c1.foreach(id_command); c2.foreach(id_command) })
}
if (id_commands.nonEmpty) {
prover.get.define_commands_bulk(resources, id_commands.toList)
}
}
val assignment = global_state.value.the_assignment(change.previous).check_finished
global_state.change(_.define_version(change.version, assignment))
prover.get.update(change.previous.id, change.version.id, change.doc_edits, change.consolidate)
resources.commit(change)
}
//}}}
/* prover output */
- def handle_output(output: Prover.Output)
+ def handle_output(output: Prover.Output): Unit =
//{{{
{
- def bad_output()
+ def bad_output(): Unit =
{
if (verbose)
Output.warning("Ignoring bad prover output: " + output.message.toString)
}
- def change_command(f: Document.State => (Command.State, Document.State))
+ def change_command(f: Document.State => (Command.State, Document.State)): Unit =
{
try {
val st = global_state.change_result(f)
if (!st.command.span.is_theory) {
change_buffer.invoke(false, Nil, List(st.command))
}
}
catch { case _: Document.State.Fail => bad_output() }
}
output match {
case msg: Prover.Protocol_Output =>
val handled = protocol_handlers.invoke(msg)
if (!handled) {
msg.properties match {
case Protocol.Command_Timing(props, state_id, timing) if prover.defined =>
command_timings.post(Session.Command_Timing(props))
val message = XML.elem(Markup.STATUS, List(XML.Elem(Markup.Timing(timing), Nil)))
change_command(_.accumulate(state_id, cache.elem(message), cache))
case Markup.Theory_Timing(props) =>
theory_timings.post(Session.Theory_Timing(props))
case Markup.Task_Statistics(props) =>
task_statistics.post(Session.Task_Statistics(props))
case Protocol.Export(args)
if args.id.isDefined && Value.Long.unapply(args.id.get).isDefined =>
val id = Value.Long.unapply(args.id.get).get
val export = Export.make_entry("", args, msg.bytes, cache)
change_command(_.add_export(id, (args.serial, export)))
case Protocol.Loading_Theory(node_name, id) =>
val blobs_info = build_blobs_info(node_name)
try { global_state.change(_.begin_theory(node_name, id, msg.text, blobs_info)) }
catch { case _: Document.State.Fail => bad_output() }
case List(Markup.Commands_Accepted.PROPERTY) =>
msg.text match {
case Protocol.Commands_Accepted(ids) =>
ids.foreach(id =>
change_command(_.accumulate(id, Protocol.Commands_Accepted.message, cache)))
case _ => bad_output()
}
case List(Markup.Assign_Update.PROPERTY) =>
msg.text match {
case Protocol.Assign_Update(id, edited, update) =>
try {
val (edited_nodes, cmds) =
global_state.change_result(_.assign(id, edited, update))
change_buffer.invoke(true, edited_nodes, cmds)
manager.send(Session.Change_Flush)
}
catch { case _: Document.State.Fail => bad_output() }
case _ => bad_output()
}
delay_prune.invoke()
case List(Markup.Removed_Versions.PROPERTY) =>
msg.text match {
case Protocol.Removed(removed) =>
try {
global_state.change(_.removed_versions(removed))
manager.send(Session.Change_Flush)
}
catch { case _: Document.State.Fail => bad_output() }
case _ => bad_output()
}
case _ => bad_output()
}
}
case _ =>
output.properties match {
case Position.Id(state_id) =>
change_command(_.accumulate(state_id, output.message, cache))
case _ if output.is_init =>
val init_ok =
try {
Isabelle_System.make_services(classOf[Session.Protocol_Handler])
.foreach(init_protocol_handler)
true
}
catch {
case exn: Throwable =>
prover.get.protocol_command("Prover.stop", "1", Exn.message(exn))
false
}
if (init_ok) {
prover.get.options(prover_options(session_options))
prover.get.init_session(resources)
phase = Session.Ready
debugger.ready()
}
case Markup.Process_Result(result) if output.is_exit =>
if (prover.defined) protocol_handlers.exit()
for (id <- global_state.value.theories.keys) {
val snapshot = global_state.change_result(_.end_theory(id))
finished_theories.post(snapshot)
}
file_formats.stop_session
phase = Session.Terminated(result)
prover.reset
case _ =>
raw_output_messages.post(output)
}
}
}
//}}}
/* main thread */
Consumer_Thread.fork[Any]("Session.manager", daemon = true)
{
case arg: Any =>
//{{{
arg match {
case output: Prover.Output =>
if (output.is_syslog) {
syslog += output.message
syslog_messages.post(output)
}
if (output.is_stdout || output.is_stderr)
raw_output_messages.post(output)
else handle_output(output)
all_messages.post(output)
case input: Prover.Input =>
all_messages.post(input)
case Start(start_prover) if !prover.defined =>
prover.set(start_prover(manager.send(_)))
case Stop =>
consolidation.exit()
delay_prune.revoke()
if (prover.defined) {
global_state.change(_ => Document.State.init)
prover.get.terminate
}
case Get_State(promise) =>
promise.fulfill(global_state.value)
case Consolidate_Execution =>
if (prover.defined) {
val state = global_state.value
state.stable_tip_version match {
case None => consolidation.update()
case Some(version) =>
val consolidate =
consolidation.flush().iterator.filter(name =>
!resources.session_base.loaded_theory(name) &&
!state.node_consolidated(version, name) &&
state.node_maybe_consolidated(version, name)).toList
if (consolidate.nonEmpty) handle_raw_edits(consolidate = consolidate)
}
}
case Prune_History =>
if (prover.defined) {
val old_versions = global_state.change_result(_.remove_versions(prune_size))
if (old_versions.nonEmpty) prover.get.remove_versions(old_versions)
}
case Update_Options(options) =>
if (prover.defined && is_ready) {
prover.get.options(prover_options(options))
handle_raw_edits()
}
global_options.post(Session.Global_Options(options))
case Cancel_Exec(exec_id) if prover.defined =>
prover.get.cancel_exec(exec_id)
case Session.Raw_Edits(doc_blobs, edits) if prover.defined =>
handle_raw_edits(doc_blobs = doc_blobs, edits = edits)
case Session.Dialog_Result(id, serial, result) if prover.defined =>
prover.get.dialog_result(serial, result)
handle_output(new Prover.Output(Protocol.Dialog_Result(id, serial, result)))
case Protocol_Command(name, args) if prover.defined =>
prover.get.protocol_command_args(name, args)
case change: Session.Change if prover.defined =>
val state = global_state.value
if (!state.removing_versions && state.is_assigned(change.previous))
handle_change(change)
else postponed_changes.store(change)
case Session.Change_Flush if prover.defined =>
val state = global_state.value
if (!state.removing_versions)
postponed_changes.flush(state).foreach(handle_change)
case bad =>
if (verbose) Output.warning("Ignoring bad message: " + bad.toString)
}
true
//}}}
}
}
/* main operations */
def get_state(): Document.State =
{
if (manager.is_active) {
val promise = Future.promise[Document.State]
manager.send_wait(Get_State(promise))
promise.join
}
else Document.State.init
}
def snapshot(name: Document.Node.Name = Document.Node.Name.empty,
pending_edits: List[Text.Edit] = Nil): Document.Snapshot =
get_state().snapshot(name, pending_edits)
def recent_syntax(name: Document.Node.Name): Outer_Syntax =
get_state().recent_finished.version.get_finished.nodes(name).syntax getOrElse
resources.session_base.overall_syntax
@tailrec final def await_stable_snapshot(): Document.Snapshot =
{
val snapshot = this.snapshot()
if (snapshot.is_outdated) {
output_delay.sleep
await_stable_snapshot()
}
else snapshot
}
- def start(start_prover: Prover.Receiver => Prover)
+ def start(start_prover: Prover.Receiver => Prover): Unit =
{
file_formats
_phase.change(
{
case Session.Inactive =>
manager.send(Start(start_prover))
post_phase(Session.Startup)
case phase => error("Cannot start prover in phase " + quote(phase.print))
})
}
def stop(): Process_Result =
{
val was_ready =
_phase.guarded_access(
{
case Session.Startup | Session.Shutdown => None
case Session.Terminated(_) => Some((false, phase))
case Session.Inactive => Some((false, post_phase(Session.Terminated(Process_Result(0)))))
case Session.Ready => Some((true, post_phase(Session.Shutdown)))
})
if (was_ready) manager.send(Stop)
prover.await_reset()
change_parser.shutdown()
change_buffer.shutdown()
manager.shutdown()
dispatcher.shutdown()
phase match {
case Session.Terminated(result) => result
case phase => error("Bad session phase after shutdown: " + quote(phase.print))
}
}
- def protocol_command(name: String, args: String*)
- { manager.send(Protocol_Command(name, args.toList)) }
-
- def cancel_exec(exec_id: Document_ID.Exec)
- { manager.send(Cancel_Exec(exec_id)) }
+ def protocol_command(name: String, args: String*): Unit =
+ manager.send(Protocol_Command(name, args.toList))
- def update(doc_blobs: Document.Blobs, edits: List[Document.Edit_Text])
- {
- if (edits.nonEmpty) manager.send_wait(Session.Raw_Edits(doc_blobs, edits))
- }
+ def cancel_exec(exec_id: Document_ID.Exec): Unit =
+ manager.send(Cancel_Exec(exec_id))
- def update_options(options: Options)
- { manager.send_wait(Update_Options(options)) }
+ def update(doc_blobs: Document.Blobs, edits: List[Document.Edit_Text]): Unit =
+ if (edits.nonEmpty) manager.send_wait(Session.Raw_Edits(doc_blobs, edits))
- def dialog_result(id: Document_ID.Generic, serial: Long, result: String)
- { manager.send(Session.Dialog_Result(id, serial, result)) }
+ def update_options(options: Options): Unit =
+ manager.send_wait(Update_Options(options))
+
+ def dialog_result(id: Document_ID.Generic, serial: Long, result: String): Unit =
+ manager.send(Session.Dialog_Result(id, serial, result))
}
diff --git a/src/Pure/PIDE/text.scala b/src/Pure/PIDE/text.scala
--- a/src/Pure/PIDE/text.scala
+++ b/src/Pure/PIDE/text.scala
@@ -1,199 +1,199 @@
/* Title: Pure/PIDE/text.scala
Author: Fabian Immler, TU Munich
Author: Makarius
Basic operations on plain text.
*/
package isabelle
import scala.collection.mutable
import scala.util.Sorting
object Text
{
/* offset */
type Offset = Int
/* range -- with total quasi-ordering */
object Range
{
def apply(start: Offset): Range = Range(start, start)
val full: Range = apply(0, Integer.MAX_VALUE / 2)
val offside: Range = apply(-1)
object Ordering extends scala.math.Ordering[Range]
{
def compare(r1: Range, r2: Range): Int = r1 compare r2
}
}
sealed case class Range(start: Offset, stop: Offset)
{
// denotation: {start} Un {i. start < i & i < stop}
if (start > stop)
error("Bad range: [" + start.toString + ".." + stop.toString + "]")
override def toString: String = "[" + start.toString + ".." + stop.toString + "]"
def length: Offset = stop - start
def map(f: Offset => Offset): Range = Range(f(start), f(stop))
def +(i: Offset): Range = if (i == 0) this else map(_ + i)
def -(i: Offset): Range = if (i == 0) this else map(_ - i)
def is_singularity: Boolean = start == stop
def inflate_singularity: Range = if (is_singularity) Range(start, start + 1) else this
def touches(i: Offset): Boolean = start <= i && i <= stop
def contains(i: Offset): Boolean = start == i || start < i && i < stop
def contains(that: Range): Boolean = this.contains(that.start) && that.stop <= this.stop
def overlaps(that: Range): Boolean = this.contains(that.start) || that.contains(this.start)
def compare(that: Range): Int = if (overlaps(that)) 0 else this.start compare that.start
def apart(that: Range): Boolean =
(this.start max that.start) > (this.stop min that.stop)
def restrict(that: Range): Range =
Range(this.start max that.start, this.stop min that.stop)
def try_restrict(that: Range): Option[Range] =
if (this apart that) None
else Some(restrict(that))
def try_join(that: Range): Option[Range] =
if (this apart that) None
else Some(Range(this.start min that.start, this.stop max that.stop))
def substring(text: String): String = text.substring(start, stop)
def try_substring(text: String): Option[String] =
try { Some(substring(text)) }
catch { case _: IndexOutOfBoundsException => None }
}
/* perspective */
object Perspective
{
val empty: Perspective = Perspective(Nil)
def full: Perspective = Perspective(List(Range.full))
def apply(ranges: List[Range]): Perspective =
{
val result = new mutable.ListBuffer[Range]
var last: Option[Range] = None
- def ship(next: Option[Range]) { result ++= last; last = next }
+ def ship(next: Option[Range]): Unit = { result ++= last; last = next }
for (range <- ranges.sortBy(_.start))
{
last match {
case None => ship(Some(range))
case Some(last_range) =>
last_range.try_join(range) match {
case None => ship(Some(range))
case joined => last = joined
}
}
}
ship(None)
new Perspective(result.toList)
}
}
final class Perspective private(
val ranges: List[Range]) // visible text partitioning in canonical order
{
def is_empty: Boolean = ranges.isEmpty
def range: Range =
if (is_empty) Range(0)
else Range(ranges.head.start, ranges.last.stop)
override def hashCode: Int = ranges.hashCode
override def equals(that: Any): Boolean =
that match {
case other: Perspective => ranges == other.ranges
case _ => false
}
override def toString: String = ranges.toString
}
/* information associated with text range */
sealed case class Info[A](range: Range, info: A)
{
def restrict(r: Range): Info[A] = Info(range.restrict(r), info)
def try_restrict(r: Range): Option[Info[A]] = range.try_restrict(r).map(Info(_, info))
def map[B](f: A => B): Info[B] = Info(range, f(info))
}
type Markup = Info[XML.Elem]
/* editing */
object Edit
{
def insert(start: Offset, text: String): Edit = new Edit(true, start, text)
def remove(start: Offset, text: String): Edit = new Edit(false, start, text)
def inserts(start: Offset, text: String): List[Edit] =
if (text == "") Nil else List(insert(start, text))
def removes(start: Offset, text: String): List[Edit] =
if (text == "") Nil else List(remove(start, text))
def replace(start: Offset, old_text: String, new_text: String): List[Edit] =
if (old_text == new_text) Nil
else removes(start, old_text) ::: inserts(start, new_text)
}
final class Edit private(val is_insert: Boolean, val start: Offset, val text: String)
{
override def toString: String =
(if (is_insert) "Insert(" else "Remove(") + (start, text).toString + ")"
/* transform offsets */
private def transform(do_insert: Boolean, i: Offset): Offset =
if (i < start) i
else if (do_insert) i + text.length
else (i - text.length) max start
def convert(i: Offset): Offset = transform(is_insert, i)
def revert(i: Offset): Offset = transform(!is_insert, i)
/* edit strings */
private def insert(i: Offset, string: String): String =
string.substring(0, i) + text + string.substring(i)
private def remove(i: Offset, count: Offset, string: String): String =
string.substring(0, i) + string.substring(i + count)
def can_edit(string: String, shift: Offset): Boolean =
shift <= start && start < shift + string.length
def edit(string: String, shift: Offset): (Option[Edit], String) =
if (!can_edit(string, shift)) (Some(this), string)
else if (is_insert) (None, insert(start - shift, string))
else {
val i = start - shift
val count = text.length min (string.length - i)
val rest =
if (count == text.length) None
else Some(Edit.remove(start, text.substring(count)))
(rest, remove(i, count, string))
}
}
}
diff --git a/src/Pure/PIDE/xml.scala b/src/Pure/PIDE/xml.scala
--- a/src/Pure/PIDE/xml.scala
+++ b/src/Pure/PIDE/xml.scala
@@ -1,457 +1,457 @@
/* Title: Pure/PIDE/xml.scala
Author: Makarius
Untyped XML trees and basic data representation.
*/
package isabelle
object XML
{
/** XML trees **/
/* datatype representation */
type Attribute = Properties.Entry
type Attributes = Properties.T
sealed abstract class Tree { override def toString: String = string_of_tree(this) }
type Body = List[Tree]
case class Elem(markup: Markup, body: Body) extends Tree
{
private lazy val hash: Int = (markup, body).hashCode()
override def hashCode(): Int = hash
def name: String = markup.name
def update_attributes(more_attributes: Attributes): Elem =
if (more_attributes.isEmpty) this
else Elem(markup.update_properties(more_attributes), body)
def + (att: Attribute): Elem = Elem(markup + att, body)
}
case class Text(content: String) extends Tree
{
private lazy val hash: Int = content.hashCode()
override def hashCode(): Int = hash
}
def elem(markup: Markup): XML.Elem = XML.Elem(markup, Nil)
def elem(name: String, body: Body): XML.Elem = XML.Elem(Markup(name, Nil), body)
def elem(name: String): XML.Elem = XML.Elem(Markup(name, Nil), Nil)
val no_text: Text = Text("")
val newline: Text = Text("\n")
/* name space */
object Namespace
{
def apply(prefix: String, target: String): Namespace =
new Namespace(prefix, target)
}
final class Namespace private(prefix: String, target: String)
{
def apply(name: String): String = prefix + ":" + name
val attribute: XML.Attribute = ("xmlns:" + prefix, target)
override def toString: String = attribute.toString
}
/* wrapped elements */
val XML_ELEM = "xml_elem"
val XML_NAME = "xml_name"
val XML_BODY = "xml_body"
object Wrapped_Elem
{
def apply(markup: Markup, body1: Body, body2: Body): XML.Elem =
XML.Elem(Markup(XML_ELEM, (XML_NAME, markup.name) :: markup.properties),
XML.Elem(Markup(XML_BODY, Nil), body1) :: body2)
def unapply(tree: Tree): Option[(Markup, Body, Body)] =
tree match {
case
XML.Elem(Markup(XML_ELEM, (XML_NAME, name) :: props),
XML.Elem(Markup(XML_BODY, Nil), body1) :: body2) =>
Some(Markup(name, props), body1, body2)
case _ => None
}
}
object Root_Elem
{
def apply(body: Body): XML.Elem = XML.Elem(Markup(XML_ELEM, Nil), body)
def unapply(tree: Tree): Option[Body] =
tree match {
case XML.Elem(Markup(XML_ELEM, Nil), body) => Some(body)
case _ => None
}
}
/* traverse text */
def traverse_text[A](body: Body)(a: A)(op: (A, String) => A): A =
{
def traverse(x: A, t: Tree): A =
t match {
case XML.Wrapped_Elem(_, _, ts) => (x /: ts)(traverse)
case XML.Elem(_, ts) => (x /: ts)(traverse)
case XML.Text(s) => op(x, s)
}
(a /: body)(traverse)
}
def text_length(body: Body): Int = traverse_text(body)(0) { case (n, s) => n + s.length }
/* text content */
def content(body: Body): String =
{
val text = new StringBuilder(text_length(body))
traverse_text(body)(()) { case (_, s) => text.append(s) }
text.toString
}
def content(tree: Tree): String = content(List(tree))
/** string representation **/
val header: String = "\n"
- def output_char(s: StringBuilder, c: Char, permissive: Boolean = false)
+ def output_char(s: StringBuilder, c: Char, permissive: Boolean = false): Unit =
{
c match {
case '<' => s ++= "<"
case '>' => s ++= ">"
case '&' => s ++= "&"
case '"' if !permissive => s ++= """
case '\'' if !permissive => s ++= "'"
case _ => s += c
}
}
- def output_string(s: StringBuilder, str: String, permissive: Boolean = false)
+ def output_string(s: StringBuilder, str: String, permissive: Boolean = false): Unit =
{
if (str == null) s ++= str
else str.iterator.foreach(output_char(s, _, permissive = permissive))
}
- def output_elem(s: StringBuilder, markup: Markup, end: Boolean = false)
+ def output_elem(s: StringBuilder, markup: Markup, end: Boolean = false): Unit =
{
s += '<'
s ++= markup.name
for ((a, b) <- markup.properties) {
s += ' '
s ++= a
s += '='
s += '"'
output_string(s, b)
s += '"'
}
if (end) s += '/'
s += '>'
}
- def output_elem_end(s: StringBuilder, name: String)
+ def output_elem_end(s: StringBuilder, name: String): Unit =
{
s += '<'
s += '/'
s ++= name
s += '>'
}
def string_of_body(body: Body): String =
{
val s = new StringBuilder
def tree(t: Tree): Unit =
t match {
case XML.Elem(markup, Nil) =>
output_elem(s, markup, end = true)
case XML.Elem(markup, ts) =>
output_elem(s, markup)
ts.foreach(tree)
output_elem_end(s, markup.name)
case XML.Text(txt) => output_string(s, txt)
}
body.foreach(tree)
s.toString
}
def string_of_tree(tree: XML.Tree): String = string_of_body(List(tree))
/** cache **/
object Cache
{
def make(
xz: XZ.Cache = XZ.Cache.make(),
max_string: Int = isabelle.Cache.default_max_string,
initial_size: Int = isabelle.Cache.default_initial_size): Cache =
new Cache(xz, max_string, initial_size)
val none: Cache = make(XZ.Cache.none, max_string = 0)
}
class Cache private[XML](val xz: XZ.Cache, max_string: Int, initial_size: Int)
extends isabelle.Cache(max_string, initial_size)
{
protected def cache_props(x: Properties.T): Properties.T =
{
if (x.isEmpty) x
else
lookup(x) match {
case Some(y) => y
case None => store(x.map(p => (Library.isolate_substring(p._1).intern, cache_string(p._2))))
}
}
protected def cache_markup(x: Markup): Markup =
{
lookup(x) match {
case Some(y) => y
case None =>
x match {
case Markup(name, props) =>
store(Markup(cache_string(name), cache_props(props)))
}
}
}
protected def cache_tree(x: XML.Tree): XML.Tree =
{
lookup(x) match {
case Some(y) => y
case None =>
x match {
case XML.Elem(markup, body) =>
store(XML.Elem(cache_markup(markup), cache_body(body)))
case XML.Text(text) => store(XML.Text(cache_string(text)))
}
}
}
protected def cache_body(x: XML.Body): XML.Body =
{
if (x.isEmpty) x
else
lookup(x) match {
case Some(y) => y
case None => x.map(cache_tree)
}
}
// support hash-consing
def tree0(x: XML.Tree): XML.Tree =
if (no_cache) x else synchronized { lookup(x) getOrElse store(x) }
// main methods
def props(x: Properties.T): Properties.T =
if (no_cache) x else synchronized { cache_props(x) }
def markup(x: Markup): Markup =
if (no_cache) x else synchronized { cache_markup(x) }
def tree(x: XML.Tree): XML.Tree =
if (no_cache) x else synchronized { cache_tree(x) }
def body(x: XML.Body): XML.Body =
if (no_cache) x else synchronized { cache_body(x) }
def elem(x: XML.Elem): XML.Elem =
if (no_cache) x else synchronized { cache_tree(x).asInstanceOf[XML.Elem] }
}
/** XML as data representation language **/
abstract class Error(s: String) extends Exception(s)
class XML_Atom(s: String) extends Error(s)
class XML_Body(body: XML.Body) extends Error("")
object Encode
{
type T[A] = A => XML.Body
type V[A] = PartialFunction[A, (List[String], XML.Body)]
type P[A] = PartialFunction[A, List[String]]
/* atomic values */
def long_atom(i: Long): String = Library.signed_string_of_long(i)
def int_atom(i: Int): String = Library.signed_string_of_int(i)
def bool_atom(b: Boolean): String = if (b) "1" else "0"
def unit_atom(u: Unit) = ""
/* structural nodes */
private def node(ts: XML.Body): XML.Tree = XML.Elem(Markup(":", Nil), ts)
private def vector(xs: List[String]): XML.Attributes =
xs.zipWithIndex.map({ case (x, i) => (int_atom(i), x) })
private def tagged(tag: Int, data: (List[String], XML.Body)): XML.Tree =
XML.Elem(Markup(int_atom(tag), vector(data._1)), data._2)
/* representation of standard types */
val tree: T[XML.Tree] = (t => List(t))
val properties: T[Properties.T] =
(props => List(XML.Elem(Markup(":", props), Nil)))
val string: T[String] = (s => if (s.isEmpty) Nil else List(XML.Text(s)))
val long: T[Long] = (x => string(long_atom(x)))
val int: T[Int] = (x => string(int_atom(x)))
val bool: T[Boolean] = (x => string(bool_atom(x)))
val unit: T[Unit] = (x => string(unit_atom(x)))
def pair[A, B](f: T[A], g: T[B]): T[(A, B)] =
(x => List(node(f(x._1)), node(g(x._2))))
def triple[A, B, C](f: T[A], g: T[B], h: T[C]): T[(A, B, C)] =
(x => List(node(f(x._1)), node(g(x._2)), node(h(x._3))))
def list[A](f: T[A]): T[List[A]] =
(xs => xs.map((x: A) => node(f(x))))
def option[A](f: T[A]): T[Option[A]] =
{
case None => Nil
case Some(x) => List(node(f(x)))
}
def variant[A](fs: List[V[A]]): T[A] =
{
case x =>
val (f, tag) = fs.iterator.zipWithIndex.find(p => p._1.isDefinedAt(x)).get
List(tagged(tag, f(x)))
}
}
object Decode
{
type T[A] = XML.Body => A
type V[A] = (List[String], XML.Body) => A
type P[A] = PartialFunction[List[String], A]
/* atomic values */
def long_atom(s: String): Long =
try { java.lang.Long.parseLong(s) }
catch { case e: NumberFormatException => throw new XML_Atom(s) }
def int_atom(s: String): Int =
try { Integer.parseInt(s) }
catch { case e: NumberFormatException => throw new XML_Atom(s) }
def bool_atom(s: String): Boolean =
if (s == "1") true
else if (s == "0") false
else throw new XML_Atom(s)
def unit_atom(s: String): Unit =
if (s == "") () else throw new XML_Atom(s)
/* structural nodes */
private def node(t: XML.Tree): XML.Body =
t match {
case XML.Elem(Markup(":", Nil), ts) => ts
case _ => throw new XML_Body(List(t))
}
private def vector(atts: XML.Attributes): List[String] =
atts.iterator.zipWithIndex.map(
{ case ((a, x), i) => if (int_atom(a) == i) x else throw new XML_Atom(a) }).toList
private def tagged(t: XML.Tree): (Int, (List[String], XML.Body)) =
t match {
case XML.Elem(Markup(name, atts), ts) => (int_atom(name), (vector(atts), ts))
case _ => throw new XML_Body(List(t))
}
/* representation of standard types */
val tree: T[XML.Tree] =
{
case List(t) => t
case ts => throw new XML_Body(ts)
}
val properties: T[Properties.T] =
{
case List(XML.Elem(Markup(":", props), Nil)) => props
case ts => throw new XML_Body(ts)
}
val string: T[String] =
{
case Nil => ""
case List(XML.Text(s)) => s
case ts => throw new XML_Body(ts)
}
val long: T[Long] = (x => long_atom(string(x)))
val int: T[Int] = (x => int_atom(string(x)))
val bool: T[Boolean] = (x => bool_atom(string(x)))
val unit: T[Unit] = (x => unit_atom(string(x)))
def pair[A, B](f: T[A], g: T[B]): T[(A, B)] =
{
case List(t1, t2) => (f(node(t1)), g(node(t2)))
case ts => throw new XML_Body(ts)
}
def triple[A, B, C](f: T[A], g: T[B], h: T[C]): T[(A, B, C)] =
{
case List(t1, t2, t3) => (f(node(t1)), g(node(t2)), h(node(t3)))
case ts => throw new XML_Body(ts)
}
def list[A](f: T[A]): T[List[A]] =
(ts => ts.map(t => f(node(t))))
def option[A](f: T[A]): T[Option[A]] =
{
case Nil => None
case List(t) => Some(f(node(t)))
case ts => throw new XML_Body(ts)
}
def variant[A](fs: List[V[A]]): T[A] =
{
case List(t) =>
val (tag, (xs, ts)) = tagged(t)
val f =
try { fs(tag) }
catch { case _: IndexOutOfBoundsException => throw new XML_Body(List(t)) }
f(xs, ts)
case ts => throw new XML_Body(ts)
}
}
}
diff --git a/src/Pure/PIDE/yxml.scala b/src/Pure/PIDE/yxml.scala
--- a/src/Pure/PIDE/yxml.scala
+++ b/src/Pure/PIDE/yxml.scala
@@ -1,156 +1,150 @@
/* Title: Pure/PIDE/yxml.scala
Author: Makarius
Efficient text representation of XML trees. Suitable for direct
inlining into plain text.
*/
package isabelle
import scala.collection.mutable
object YXML
{
/* chunk markers */
val X = '\u0005'
val Y = '\u0006'
val is_X: Char => Boolean = _ == X
val is_Y: Char => Boolean = _ == Y
val X_string: String = X.toString
val Y_string: String = Y.toString
val XY_string: String = X_string + Y_string
val XYX_string: String = XY_string + X_string
def detect(s: String): Boolean = s.exists(c => c == X || c == Y)
def detect_elem(s: String): Boolean = s.startsWith(XY_string)
/* string representation */
- def traversal(string: String => Unit, body: XML.Body)
+ def traversal(string: String => Unit, body: XML.Body): Unit =
{
def tree(t: XML.Tree): Unit =
t match {
case XML.Elem(Markup(name, atts), ts) =>
string(XY_string)
string(name)
for ((a, x) <- atts) { string(Y_string); string(a); string("="); string(x) }
string(X_string)
ts.foreach(tree)
string(XYX_string)
case XML.Text(text) => string(text)
}
body.foreach(tree)
}
def string_of_body(body: XML.Body): String =
{
val s = new StringBuilder
traversal(str => s ++= str, body)
s.toString
}
def string_of_tree(tree: XML.Tree): String = string_of_body(List(tree))
/* parsing */
private def err(msg: String) = error("Malformed YXML: " + msg)
private def err_attribute() = err("bad attribute")
private def err_element() = err("bad element")
private def err_unbalanced(name: String) =
if (name == "") err("unbalanced element")
else err("unbalanced element " + quote(name))
private def parse_attrib(source: CharSequence): (String, String) =
{
val s = source.toString
val i = s.indexOf('=')
if (i <= 0) err_attribute()
(s.substring(0, i), s.substring(i + 1))
}
def parse_body(source: CharSequence, cache: XML.Cache = XML.Cache.none): XML.Body =
{
/* stack operations */
def buffer(): mutable.ListBuffer[XML.Tree] = new mutable.ListBuffer[XML.Tree]
var stack: List[(Markup, mutable.ListBuffer[XML.Tree])] = List((Markup.Empty, buffer()))
- def add(x: XML.Tree)
- {
+ def add(x: XML.Tree): Unit =
(stack: @unchecked) match { case (_, body) :: _ => body += x }
- }
- def push(name: String, atts: XML.Attributes)
- {
+ def push(name: String, atts: XML.Attributes): Unit =
if (name == "") err_element()
else stack = (cache.markup(Markup(name, atts)), buffer()) :: stack
- }
- def pop()
- {
+ def pop(): Unit =
(stack: @unchecked) match {
case (Markup.Empty, _) :: _ => err_unbalanced("")
case (markup, body) :: pending =>
stack = pending
add(cache.tree0(XML.Elem(markup, body.toList)))
}
- }
/* parse chunks */
for (chunk <- Library.separated_chunks(is_X, source) if chunk.length != 0) {
if (chunk.length == 1 && chunk.charAt(0) == Y) pop()
else {
Library.separated_chunks(is_Y, chunk).toList match {
case ch :: name :: atts if ch.length == 0 =>
push(name.toString, atts.map(parse_attrib))
case txts => for (txt <- txts) add(cache.tree0(XML.Text(cache.string(txt.toString))))
}
}
}
(stack: @unchecked) match {
case List((Markup.Empty, body)) => body.toList
case (Markup(name, _), _) :: _ => err_unbalanced(name)
}
}
def parse(source: CharSequence, cache: XML.Cache = XML.Cache.none): XML.Tree =
parse_body(source, cache = cache) match {
case List(result) => result
case Nil => XML.no_text
case _ => err("multiple XML trees")
}
def parse_elem(source: CharSequence, cache: XML.Cache = XML.Cache.none): XML.Tree =
parse_body(source, cache = cache) match {
case List(elem: XML.Elem) => elem
case _ => err("single XML element expected")
}
/* failsafe parsing */
private def markup_broken(source: CharSequence) =
XML.Elem(Markup.Broken, List(XML.Text(source.toString)))
def parse_body_failsafe(source: CharSequence, cache: XML.Cache = XML.Cache.none): XML.Body =
{
try { parse_body(source, cache = cache) }
catch { case ERROR(_) => List(markup_broken(source)) }
}
def parse_failsafe(source: CharSequence, cache: XML.Cache = XML.Cache.none): XML.Tree =
{
try { parse(source, cache = cache) }
catch { case ERROR(_) => markup_broken(source) }
}
}
diff --git a/src/Pure/System/bash.scala b/src/Pure/System/bash.scala
--- a/src/Pure/System/bash.scala
+++ b/src/Pure/System/bash.scala
@@ -1,234 +1,235 @@
/* Title: Pure/System/bash.scala
Author: Makarius
GNU bash processes, with propagation of interrupts.
*/
package isabelle
import java.io.{File => JFile, BufferedReader, InputStreamReader,
BufferedWriter, OutputStreamWriter}
import scala.annotation.tailrec
object Bash
{
/* concrete syntax */
private def bash_chr(c: Byte): String =
{
val ch = c.toChar
ch match {
case '\t' => "$'\\t'"
case '\n' => "$'\\n'"
case '\f' => "$'\\f'"
case '\r' => "$'\\r'"
case _ =>
if (Symbol.is_ascii_letter(ch) || Symbol.is_ascii_digit(ch) || "+,-./:_".contains(ch))
Symbol.ascii(ch)
else if (c < 0) "$'\\x" + Integer.toHexString(256 + c) + "'"
else if (c < 16) "$'\\x0" + Integer.toHexString(c) + "'"
else if (c < 32 || c >= 127) "$'\\x" + Integer.toHexString(c) + "'"
else "\\" + ch
}
}
def string(s: String): String =
if (s == "") "\"\""
else UTF8.bytes(s).iterator.map(bash_chr).mkString
def strings(ss: List[String]): String =
ss.iterator.map(Bash.string).mkString(" ")
/* process and result */
type Watchdog = (Time, Process => Boolean)
def process(script: String,
cwd: JFile = null,
env: Map[String, String] = Isabelle_System.settings(),
redirect: Boolean = false,
cleanup: () => Unit = () => ()): Process =
new Process(script, cwd, env, redirect, cleanup)
class Process private[Bash](
script: String,
cwd: JFile,
env: Map[String, String],
redirect: Boolean,
cleanup: () => Unit)
{
private val timing_file = Isabelle_System.tmp_file("bash_timing")
private val timing = Synchronized[Option[Timing]](None)
def get_timing: Timing = timing.value getOrElse Timing.zero
private val script_file = Isabelle_System.tmp_file("bash_script")
File.write(script_file, script)
private val proc =
Isabelle_System.process(
List(File.platform_path(Path.variable("ISABELLE_BASH_PROCESS")), "-",
File.standard_path(timing_file), "bash", File.standard_path(script_file)),
cwd = cwd, env = env, redirect = redirect)
// channels
val stdin: BufferedWriter =
new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset))
val stdout: BufferedReader =
new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset))
val stderr: BufferedReader =
new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset))
// signals
private val pid = stdout.readLine
@tailrec private def kill(signal: String, count: Int = 1): Boolean =
{
count <= 0 ||
{
Isabelle_System.kill(signal, pid)
val running = Isabelle_System.kill("0", pid)._2 == 0
if (running) {
Time.seconds(0.1).sleep
kill(signal, count - 1)
}
else false
}
}
def terminate(): Unit = Isabelle_Thread.try_uninterruptible
{
kill("INT", count = 7) && kill("TERM", count = 3) && kill("KILL")
proc.destroy
do_cleanup()
}
def interrupt(): Unit = Isabelle_Thread.try_uninterruptible
{
Isabelle_System.kill("INT", pid)
}
// JVM shutdown hook
private val shutdown_hook = Isabelle_Thread.create(() => terminate())
try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
catch { case _: IllegalStateException => }
// cleanup
- private def do_cleanup()
+ private def do_cleanup(): Unit =
{
try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
catch { case _: IllegalStateException => }
script_file.delete
timing.change {
case None =>
if (timing_file.isFile) {
val t =
Word.explode(File.read(timing_file)) match {
case List(Value.Long(elapsed), Value.Long(cpu)) =>
Timing(Time.ms(elapsed), Time.ms(cpu), Time.zero)
case _ => Timing.zero
}
timing_file.delete
Some(t)
}
else Some(Timing.zero)
case some => some
}
cleanup()
}
// join
def join: Int =
{
val rc = proc.waitFor
do_cleanup()
rc
}
// result
def result(
progress_stdout: String => Unit = (_: String) => (),
progress_stderr: String => Unit = (_: String) => (),
watchdog: Option[Watchdog] = None,
strict: Boolean = true): Process_Result =
{
stdin.close
val out_lines =
Future.thread("bash_stdout") { File.read_lines(stdout, progress_stdout) }
val err_lines =
Future.thread("bash_stderr") { File.read_lines(stderr, progress_stderr) }
val watchdog_thread =
for ((time, check) <- watchdog)
yield {
Future.thread("bash_watchdog") {
while (proc.isAlive) {
time.sleep
if (check(this)) interrupt()
}
}
}
val rc =
try { join }
catch { case Exn.Interrupt() => terminate(); Exn.Interrupt.return_code }
watchdog_thread.foreach(_.cancel)
if (strict && rc == Exn.Interrupt.return_code) throw Exn.Interrupt()
Process_Result(rc, out_lines.join, err_lines.join, get_timing)
}
}
/* Scala function */
object Process extends Scala.Fun("bash_process")
{
val here = Scala_Project.here
def apply(script: String): String =
{
val result = Exn.capture { Isabelle_System.bash(script) }
val is_interrupt =
result match {
case Exn.Res(res) => res.rc == Exn.Interrupt.return_code
case Exn.Exn(exn) => Exn.is_interrupt(exn)
}
result match {
case _ if is_interrupt => ""
case Exn.Exn(exn) => Exn.message(exn)
case Exn.Res(res) =>
- (res.rc.toString ::
- res.timing.elapsed.ms.toString ::
- res.timing.cpu.ms.toString ::
- res.out_lines.length.toString ::
- res.out_lines ::: res.err_lines).mkString("\u0000")
+ Library.cat_strings0(
+ res.rc.toString ::
+ res.timing.elapsed.ms.toString ::
+ res.timing.cpu.ms.toString ::
+ res.out_lines.length.toString ::
+ res.out_lines ::: res.err_lines)
}
}
}
}
diff --git a/src/Pure/System/command_line.scala b/src/Pure/System/command_line.scala
--- a/src/Pure/System/command_line.scala
+++ b/src/Pure/System/command_line.scala
@@ -1,44 +1,44 @@
/* Title: Pure/System/command_line.scala
Author: Makarius
Support for Isabelle/Scala command line tools.
*/
package isabelle
object Command_Line
{
object Chunks
{
private def chunks(list: List[String]): List[List[String]] =
list.indexWhere(_ == "\n") match {
case -1 => List(list)
case i =>
val (chunk, rest) = list.splitAt(i)
chunk :: chunks(rest.tail)
}
def unapplySeq(list: List[String]): Option[List[List[String]]] = Some(chunks(list))
}
var debug = false
- def tool(body: => Unit)
+ def tool(body: => Unit): Unit =
{
val thread =
Isabelle_Thread.fork(name = "command_line", inherit_locals = true) {
val rc =
try { body; 0 }
catch {
case exn: Throwable =>
Output.error_message(Exn.message(exn) + (if (debug) "\n" + Exn.trace(exn) else ""))
Exn.return_code(exn, 2)
}
sys.exit(rc)
}
thread.join
}
def ML_tool(body: List[String]): String =
"Command_Line.tool (fn () => (" + body.mkString("; ") + "));"
}
diff --git a/src/Pure/System/cygwin.scala b/src/Pure/System/cygwin.scala
--- a/src/Pure/System/cygwin.scala
+++ b/src/Pure/System/cygwin.scala
@@ -1,68 +1,68 @@
/* Title: Pure/System/cygwin.scala
Author: Makarius
Cygwin as POSIX emulation on Windows.
*/
package isabelle
import java.io.{File => JFile}
import java.nio.file.Files
import scala.annotation.tailrec
object Cygwin
{
/* init (e.g. after extraction via 7zip) */
- def init(isabelle_root: String, cygwin_root: String)
+ def init(isabelle_root: String, cygwin_root: String): Unit =
{
require(Platform.is_windows, "Windows platform expected")
- def exec(cmdline: String*)
+ def exec(cmdline: String*): Unit =
{
val cwd = new JFile(isabelle_root)
val env = sys.env + ("CYGWIN" -> "nodosfilewarning")
val proc = Isabelle_System.process(cmdline.toList, cwd = cwd, env = env, redirect = true)
val (output, rc) = Isabelle_System.process_output(proc)
if (rc != 0) error(output)
}
val uninitialized_file = new JFile(cygwin_root, "isabelle\\uninitialized")
val uninitialized = uninitialized_file.isFile && uninitialized_file.delete
if (uninitialized) {
val symlinks =
{
val path = (new JFile(cygwin_root + "\\isabelle\\symlinks")).toPath
Files.readAllLines(path, UTF8.charset).toArray.toList.asInstanceOf[List[String]]
}
@tailrec def recover_symlinks(list: List[String]): Unit =
{
list match {
case Nil | List("") =>
case target :: content :: rest =>
link(content, new JFile(isabelle_root, target))
recover_symlinks(rest)
case _ => error("Unbalanced symlinks list")
}
}
recover_symlinks(symlinks)
exec(cygwin_root + "\\bin\\dash.exe", "/isabelle/rebaseall")
exec(cygwin_root + "\\bin\\bash.exe", "/isabelle/postinstall")
}
}
- def link(content: String, target: JFile)
+ def link(content: String, target: JFile): Unit =
{
val target_path = target.toPath
using(Files.newBufferedWriter(target_path, UTF8.charset))(
_.write("!" + content + "\u0000"))
Files.setAttribute(target_path, "dos:system", true)
}
}
diff --git a/src/Pure/System/isabelle_fonts.scala b/src/Pure/System/isabelle_fonts.scala
--- a/src/Pure/System/isabelle_fonts.scala
+++ b/src/Pure/System/isabelle_fonts.scala
@@ -1,79 +1,79 @@
/* Title: Pure/System/isabelle_system.scala
Author: Makarius
Fonts from the Isabelle system environment, notably the "Isabelle DejaVu"
collection.
*/
package isabelle
import java.awt.{Font, GraphicsEnvironment}
object Isabelle_Fonts
{
/* standard names */
val mono: String = "Isabelle DejaVu Sans Mono"
val sans: String = "Isabelle DejaVu Sans"
val serif: String = "Isabelle DejaVu Serif"
/* environment entries */
object Entry
{
object Ordering extends scala.math.Ordering[Entry]
{
def compare(entry1: Entry, entry2: Entry): Int =
entry1.family compare entry2.family match {
case 0 => entry1.style compare entry2.style
case ord => ord
}
}
}
sealed case class Entry(path: Path, hidden: Boolean = false)
{
lazy val bytes: Bytes = Bytes.read(path)
lazy val font: Font = Font.createFont(Font.TRUETYPE_FONT, path.file)
def family: String = font.getFamily
def name: String = font.getFontName
def style: Int =
(if (is_bold) Font.BOLD else 0) +
(if (is_italic) Font.ITALIC else 0)
// educated guess for style: Font.createFont always produces Font.PLAIN
private lazy val name_lowercase = Word.lowercase(name)
def is_bold: Boolean =
name_lowercase.containsSlice("bold")
def is_italic: Boolean =
name_lowercase.containsSlice("italic") ||
name_lowercase.containsSlice("oblique")
}
def make_entries(
getenv: String => String = Isabelle_System.getenv_strict(_),
hidden: Boolean = false): List[Entry] =
{
Path.split(getenv("ISABELLE_FONTS")).map(Entry(_)) :::
(if (hidden) Path.split(getenv("ISABELLE_FONTS_HIDDEN")).map(Entry(_, hidden = true)) else Nil)
}
private lazy val all_fonts: List[Entry] =
make_entries(hidden = true).sorted(Entry.Ordering)
def fonts(hidden: Boolean = false): List[Entry] =
if (hidden) all_fonts else all_fonts.filter(entry => !entry.hidden)
/* system init */
- def init()
+ def init(): Unit =
{
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
for (entry <- fonts()) ge.registerFont(entry.font)
}
}
diff --git a/src/Pure/System/isabelle_process.scala b/src/Pure/System/isabelle_process.scala
--- a/src/Pure/System/isabelle_process.scala
+++ b/src/Pure/System/isabelle_process.scala
@@ -1,81 +1,81 @@
/* Title: Pure/System/isabelle_process.scala
Author: Makarius
Isabelle process wrapper.
*/
package isabelle
import java.io.{File => JFile}
object Isabelle_Process
{
def apply(
session: Session,
options: Options,
sessions_structure: Sessions.Structure,
store: Sessions.Store,
logic: String = "",
raw_ml_system: Boolean = false,
use_prelude: List[String] = Nil,
eval_main: String = "",
modes: List[String] = Nil,
cwd: JFile = null,
env: Map[String, String] = Isabelle_System.settings()): Isabelle_Process =
{
val channel = System_Channel()
val process =
try {
val channel_options =
options.string.update("system_channel_address", channel.address).
string.update("system_channel_password", channel.password)
ML_Process(channel_options, sessions_structure, store,
logic = logic, raw_ml_system = raw_ml_system,
use_prelude = use_prelude, eval_main = eval_main,
modes = modes, cwd = cwd, env = env)
}
catch { case exn @ ERROR(_) => channel.shutdown(); throw exn }
process.stdin.close
new Isabelle_Process(session, channel, process)
}
}
class Isabelle_Process private(session: Session, channel: System_Channel, process: Bash.Process)
{
private val startup = Future.promise[String]
private val terminated = Future.promise[Process_Result]
session.phase_changed +=
Session.Consumer(getClass.getName) {
case Session.Ready =>
startup.fulfill("")
case Session.Terminated(result) =>
if (!result.ok && !startup.is_finished) {
val syslog = session.syslog_content()
val err = "Session startup failed" + (if (syslog.isEmpty) "" else ":\n" + syslog)
startup.fulfill(err)
}
terminated.fulfill(result)
case _ =>
}
session.start(receiver => new Prover(receiver, session.cache, channel, process))
def await_startup(): Isabelle_Process =
startup.join match {
case "" => this
case err => session.stop(); error(err)
}
def await_shutdown(): Process_Result =
{
val result = terminated.join
session.stop()
result
}
- def terminate { process.terminate }
+ def terminate: Unit = process.terminate
}
diff --git a/src/Pure/System/isabelle_system.ML b/src/Pure/System/isabelle_system.ML
--- a/src/Pure/System/isabelle_system.ML
+++ b/src/Pure/System/isabelle_system.ML
@@ -1,119 +1,117 @@
(* Title: Pure/System/isabelle_system.ML
Author: Makarius
Isabelle system support.
*)
signature ISABELLE_SYSTEM =
sig
val bash_process: string -> Process_Result.T
val bash_output: string -> string * int
val bash: string -> int
val bash_functions: unit -> string list
val check_bash_function: Proof.context -> string * Position.T -> string
val make_directory: Path.T -> Path.T
val copy_dir: Path.T -> Path.T -> unit
val copy_file: Path.T -> Path.T -> unit
val copy_file_base: Path.T * Path.T -> Path.T -> unit
val create_tmp_path: string -> string -> Path.T
val with_tmp_file: string -> string -> (Path.T -> 'a) -> 'a
val rm_tree: Path.T -> unit
val with_tmp_dir: string -> (Path.T -> 'a) -> 'a
- val download: string -> string
+ val download: string -> Path.T -> unit
end;
structure Isabelle_System: ISABELLE_SYSTEM =
struct
(* bash *)
fun bash_process script =
Scala.function_thread "bash_process"
("export ISABELLE_TMP=" ^ Bash.string (getenv "ISABELLE_TMP") ^ "\n" ^ script)
- |> space_explode "\000"
+ |> split_strings0
|> (fn [] => raise Exn.Interrupt
| [err] => error err
| a :: b :: c :: d :: lines =>
let
val rc = Value.parse_int a;
val (elapsed, cpu) = apply2 (Time.fromMilliseconds o Value.parse_int) (b, c);
val (out_lines, err_lines) = chop (Value.parse_int d) lines;
in
Process_Result.make
{rc = rc,
out_lines = out_lines,
err_lines = err_lines,
timing = {elapsed = elapsed, cpu = cpu, gc = Time.zeroTime}}
end
| _ => raise Fail "Malformed Isabelle/Scala result");
val bash = bash_process #> Process_Result.print #> Process_Result.rc;
fun bash_output s =
let
val res = bash_process s;
val _ = warning (Process_Result.err res);
in (Process_Result.out res, Process_Result.rc res) end;
(* bash functions *)
fun bash_functions () =
bash_process "declare -Fx"
|> Process_Result.check
|> Process_Result.out_lines
|> map_filter (space_explode " " #> try List.last);
fun check_bash_function ctxt arg =
Completion.check_entity Markup.bash_functionN
(bash_functions () |> map (rpair Position.none)) ctxt arg;
(* directory and file operations *)
val absolute_path = Path.implode o File.absolute_path;
-fun scala_function0 name = ignore o Scala.function name o space_implode "\000";
+fun scala_function0 name = ignore o Scala.function name o cat_strings0;
fun scala_function name = scala_function0 name o map absolute_path;
fun make_directory path = (scala_function "make_directory" [path]; path);
fun copy_dir src dst = scala_function "copy_dir" [src, dst];
fun copy_file src dst = scala_function "copy_file" [src, dst];
fun copy_file_base (base_dir, src) target_dir =
scala_function0 "copy_file_base"
[absolute_path base_dir, Path.implode src, absolute_path target_dir];
(* tmp files *)
fun create_tmp_path name ext =
let
val path = File.tmp_path (Path.basic (name ^ serial_string ()) |> Path.ext ext);
val _ = File.exists path andalso
raise Fail ("Temporary file already exists: " ^ Path.print path);
in path end;
fun with_tmp_file name ext f =
let val path = create_tmp_path name ext
in Exn.release (Exn.capture f path before ignore (try File.rm path)) end;
(* tmp dirs *)
fun rm_tree path = scala_function "rm_tree" [path];
fun with_tmp_dir name f =
let val path = create_tmp_path name ""
in Exn.release (Exn.capture f (make_directory path) before ignore (try rm_tree path)) end;
(* download file *)
-fun download url =
- with_tmp_file "download" "" (fn path =>
- (scala_function0 "download" [url, absolute_path path];
- File.read path));
+fun download url file =
+ ignore (Scala.function_thread "download" (cat_strings0 [url, absolute_path file]));
end;
diff --git a/src/Pure/System/isabelle_system.scala b/src/Pure/System/isabelle_system.scala
--- a/src/Pure/System/isabelle_system.scala
+++ b/src/Pure/System/isabelle_system.scala
@@ -1,575 +1,568 @@
/* Title: Pure/System/isabelle_system.scala
Author: Makarius
Fundamental Isabelle system environment: quasi-static module with
optional init operation.
*/
package isabelle
import java.io.{File => JFile, IOException}
import java.nio.file.{Path => JPath, Files, SimpleFileVisitor, FileVisitResult,
StandardCopyOption, FileSystemException}
import java.nio.file.attribute.BasicFileAttributes
-import scala.annotation.tailrec
-
object Isabelle_System
{
/** bootstrap information **/
def jdk_home(): String =
{
val java_home = System.getProperty("java.home", "")
val home = new JFile(java_home)
val parent = home.getParent
if (home.getName == "jre" && parent != null &&
(new JFile(new JFile(parent, "bin"), "javac")).exists) parent
else java_home
}
def bootstrap_directory(
preference: String, envar: String, property: String, description: String): String =
{
val value =
proper_string(preference) orElse // explicit argument
proper_string(System.getenv(envar)) orElse // e.g. inherited from running isabelle tool
proper_string(System.getProperty(property)) getOrElse // e.g. via JVM application boot process
error("Unknown " + description + " directory")
if ((new JFile(value)).isDirectory) value
else error("Bad " + description + " directory " + quote(value))
}
/** implicit settings environment **/
abstract class Service
@volatile private var _settings: Option[Map[String, String]] = None
@volatile private var _services: Option[List[Class[Service]]] = None
def settings(): Map[String, String] =
{
if (_settings.isEmpty) init() // unsynchronized check
_settings.get
}
def services(): List[Class[Service]] =
{
if (_services.isEmpty) init() // unsynchronized check
_services.get
}
def make_services[C](c: Class[C]): List[C] =
for { c1 <- services() if Library.is_subclass(c1, c) }
yield c1.getDeclaredConstructor().newInstance().asInstanceOf[C]
def init(isabelle_root: String = "", cygwin_root: String = ""): Unit = synchronized
{
if (_settings.isEmpty || _services.isEmpty) {
val isabelle_root1 =
bootstrap_directory(isabelle_root, "ISABELLE_ROOT", "isabelle.root", "Isabelle root")
val cygwin_root1 =
if (Platform.is_windows)
bootstrap_directory(cygwin_root, "CYGWIN_ROOT", "cygwin.root", "Cygwin root")
else ""
if (Platform.is_windows) Cygwin.init(isabelle_root1, cygwin_root1)
- def set_cygwin_root()
+ def set_cygwin_root(): Unit =
{
if (Platform.is_windows)
_settings = Some(_settings.getOrElse(Map.empty) + ("CYGWIN_ROOT" -> cygwin_root1))
}
set_cygwin_root()
def default(env: Map[String, String], entry: (String, String)): Map[String, String] =
if (env.isDefinedAt(entry._1) || entry._2 == "") env
else env + entry
val env =
{
val temp_windows =
{
val temp = if (Platform.is_windows) System.getenv("TEMP") else null
if (temp != null && temp.contains('\\')) temp else ""
}
val user_home = System.getProperty("user.home", "")
val isabelle_app = System.getProperty("isabelle.app", "")
default(
default(
default(sys.env + ("ISABELLE_JDK_HOME" -> File.standard_path(jdk_home())),
"TEMP_WINDOWS" -> temp_windows),
"HOME" -> user_home),
"ISABELLE_APP" -> "true")
}
val settings =
{
val dump = JFile.createTempFile("settings", null)
dump.deleteOnExit
try {
val cmd1 =
if (Platform.is_windows)
List(cygwin_root1 + "\\bin\\bash", "-l",
File.standard_path(isabelle_root1 + "\\bin\\isabelle"))
else
List(isabelle_root1 + "/bin/isabelle")
val cmd = cmd1 ::: List("getenv", "-d", dump.toString)
val (output, rc) = process_output(process(cmd, env = env, redirect = true))
if (rc != 0) error(output)
val entries =
- (for (entry <- File.read(dump).split("\u0000") if entry != "") yield {
+ (for (entry <- Library.split_strings0(File.read(dump)) if entry != "") yield {
val i = entry.indexOf('=')
if (i <= 0) entry -> ""
else entry.substring(0, i) -> entry.substring(i + 1)
}).toMap
entries + ("PATH" -> entries("PATH_JVM")) - "PATH_JVM"
}
finally { dump.delete }
}
_settings = Some(settings)
set_cygwin_root()
val variable = "ISABELLE_SCALA_SERVICES"
val services =
for (name <- space_explode(':', settings.getOrElse(variable, getenv_error(variable))))
yield {
def err(msg: String): Nothing =
error("Bad entry " + quote(name) + " in " + variable + "\n" + msg)
try { Class.forName(name).asInstanceOf[Class[Service]] }
catch {
case _: ClassNotFoundException => err("Class not found")
case exn: Throwable => err(Exn.message(exn))
}
}
_services = Some(services)
}
}
/* getenv */
private def getenv_error(name: String): Nothing =
error("Undefined Isabelle environment variable: " + quote(name))
def getenv(name: String, env: Map[String, String] = settings()): String =
env.getOrElse(name, "")
def getenv_strict(name: String, env: Map[String, String] = settings()): String =
proper_string(getenv(name, env)) getOrElse
error("Undefined Isabelle environment variable: " + quote(name))
def cygwin_root(): String = getenv_strict("CYGWIN_ROOT")
def isabelle_id(): String =
proper_string(getenv("ISABELLE_ID")) getOrElse
Mercurial.repository(Path.explode("~~")).parent()
/** file-system operations **/
/* scala functions */
private def apply_paths(arg: String, fun: List[Path] => Unit): String =
- { fun(Library.space_explode('\u0000', arg).map(Path.explode)); "" }
+ { fun(Library.split_strings0(arg).map(Path.explode)); "" }
private def apply_paths1(arg: String, fun: Path => Unit): String =
apply_paths(arg, { case List(path) => fun(path) })
private def apply_paths2(arg: String, fun: (Path, Path) => Unit): String =
apply_paths(arg, { case List(path1, path2) => fun(path1, path2) })
private def apply_paths3(arg: String, fun: (Path, Path, Path) => Unit): String =
apply_paths(arg, { case List(path1, path2, path3) => fun(path1, path2, path3) })
/* permissions */
def chmod(arg: String, path: Path): Unit =
bash("chmod " + arg + " " + File.bash_path(path)).check
def chown(arg: String, path: Path): Unit =
bash("chown " + arg + " " + File.bash_path(path)).check
/* directories */
def make_directory(path: Path): Path =
{
if (!path.is_dir) {
try { Files.createDirectories(path.file.toPath) }
catch { case ERROR(_) => error("Failed to create directory: " + path.absolute) }
}
path
}
def new_directory(path: Path): Path =
if (path.is_dir) error("Directory already exists: " + path.absolute)
else make_directory(path)
def copy_dir(dir1: Path, dir2: Path): Unit =
{
val res = bash("cp -a " + File.bash_path(dir1) + " " + File.bash_path(dir2))
if (!res.ok) {
cat_error("Failed to copy directory " + dir1.absolute + " to " + dir2.absolute, res.err)
}
}
object Make_Directory extends Scala.Fun("make_directory")
{
val here = Scala_Project.here
def apply(arg: String): String = apply_paths1(arg, make_directory)
}
object Copy_Dir extends Scala.Fun("copy_dir")
{
val here = Scala_Project.here
def apply(arg: String): String = apply_paths2(arg, copy_dir)
}
/* copy files */
def copy_file(src: JFile, dst: JFile): Unit =
{
val target = if (dst.isDirectory) new JFile(dst, src.getName) else dst
if (!File.eq(src, target)) {
try {
Files.copy(src.toPath, target.toPath,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING)
}
catch {
case ERROR(msg) =>
cat_error("Failed top copy file " +
File.path(src).absolute + " to " + File.path(dst).absolute, msg)
}
}
}
def copy_file(src: Path, dst: Path): Unit = copy_file(src.file, dst.file)
def copy_file_base(base_dir: Path, src: Path, target_dir: Path): Unit =
{
val src1 = src.expand
val src1_dir = src1.dir
if (!src1.starts_basic) error("Illegal path specification " + src1 + " beyond base directory")
copy_file(base_dir + src1, Isabelle_System.make_directory(target_dir + src1_dir))
}
object Copy_File extends Scala.Fun("copy_file")
{
val here = Scala_Project.here
def apply(arg: String): String = apply_paths2(arg, copy_file)
}
object Copy_File_Base extends Scala.Fun("copy_file_base")
{
val here = Scala_Project.here
def apply(arg: String): String = apply_paths3(arg, copy_file_base)
}
/* move files */
- def move_file(src: JFile, dst: JFile)
+ def move_file(src: JFile, dst: JFile): Unit =
{
val target = if (dst.isDirectory) new JFile(dst, src.getName) else dst
if (!File.eq(src, target))
Files.move(src.toPath, target.toPath, StandardCopyOption.REPLACE_EXISTING)
}
def move_file(src: Path, dst: Path): Unit = move_file(src.file, dst.file)
/* symbolic link */
- def symlink(src: Path, dst: Path, force: Boolean = false)
+ def symlink(src: Path, dst: Path, force: Boolean = false): Unit =
{
val src_file = src.file
val dst_file = dst.file
val target = if (dst_file.isDirectory) new JFile(dst_file, src_file.getName) else dst_file
if (force) target.delete
try { Files.createSymbolicLink(target.toPath, src_file.toPath) }
catch {
case _: UnsupportedOperationException if Platform.is_windows =>
Cygwin.link(File.standard_path(src), target)
case _: FileSystemException if Platform.is_windows =>
Cygwin.link(File.standard_path(src), target)
}
}
/* tmp files */
def isabelle_tmp_prefix(): JFile =
{
val path = Path.explode("$ISABELLE_TMP_PREFIX")
path.file.mkdirs // low-level mkdirs to avoid recursion via Isabelle environment
File.platform_file(path)
}
def tmp_file(name: String, ext: String = "", base_dir: JFile = isabelle_tmp_prefix()): JFile =
{
val suffix = if (ext == "") "" else "." + ext
val file = Files.createTempFile(base_dir.toPath, name, suffix).toFile
file.deleteOnExit
file
}
def with_tmp_file[A](name: String, ext: String = "")(body: Path => A): A =
{
val file = tmp_file(name, ext)
try { body(File.path(file)) } finally { file.delete }
}
/* tmp dirs */
def rm_tree(root: JFile): Unit =
{
root.delete
if (root.isDirectory) {
Files.walkFileTree(root.toPath,
new SimpleFileVisitor[JPath] {
override def visitFile(file: JPath, attrs: BasicFileAttributes): FileVisitResult =
{
try { Files.deleteIfExists(file) }
catch { case _: IOException => }
FileVisitResult.CONTINUE
}
override def postVisitDirectory(dir: JPath, e: IOException): FileVisitResult =
{
if (e == null) {
try { Files.deleteIfExists(dir) }
catch { case _: IOException => }
FileVisitResult.CONTINUE
}
else throw e
}
}
)
}
}
def rm_tree(root: Path): Unit = rm_tree(root.file)
object Rm_Tree extends Scala.Fun("rm_tree")
{
val here = Scala_Project.here
def apply(arg: String): String = apply_paths1(arg, rm_tree)
}
def tmp_dir(name: String, base_dir: JFile = isabelle_tmp_prefix()): JFile =
{
val dir = Files.createTempDirectory(base_dir.toPath, name).toFile
dir.deleteOnExit
dir
}
def with_tmp_dir[A](name: String)(body: Path => A): A =
{
val dir = tmp_dir(name)
try { body(File.path(dir)) } finally { rm_tree(dir) }
}
/* quasi-atomic update of directory */
- def update_directory(dir: Path, f: Path => Unit)
+ def update_directory(dir: Path, f: Path => Unit): Unit =
{
val new_dir = dir.ext("new")
val old_dir = dir.ext("old")
rm_tree(new_dir)
rm_tree(old_dir)
f(new_dir)
if (dir.is_dir) move_file(dir, old_dir)
move_file(new_dir, dir)
rm_tree(old_dir)
}
/** external processes **/
/* raw process */
def process(command_line: List[String],
cwd: JFile = null,
env: Map[String, String] = settings(),
redirect: Boolean = false): Process =
{
val proc = new ProcessBuilder
proc.command(command_line:_*) // fragile on Windows
if (cwd != null) proc.directory(cwd)
if (env != null) {
proc.environment.clear
for ((x, y) <- env) proc.environment.put(x, y)
}
proc.redirectErrorStream(redirect)
proc.start
}
def process_output(proc: Process): (String, Int) =
{
proc.getOutputStream.close
val output = File.read_stream(proc.getInputStream)
val rc =
try { proc.waitFor }
finally {
proc.getInputStream.close
proc.getErrorStream.close
proc.destroy
Exn.Interrupt.dispose()
}
(output, rc)
}
def kill(signal: String, group_pid: String): (String, Int) =
{
val bash =
if (Platform.is_windows) List(cygwin_root() + "\\bin\\bash.exe")
else List("/usr/bin/env", "bash")
process_output(process(bash ::: List("-c", "kill -" + signal + " -" + group_pid)))
}
/* GNU bash */
def bash(script: String,
cwd: JFile = null,
env: Map[String, String] = settings(),
redirect: Boolean = false,
progress_stdout: String => Unit = (_: String) => (),
progress_stderr: String => Unit = (_: String) => (),
watchdog: Option[Bash.Watchdog] = None,
strict: Boolean = true,
cleanup: () => Unit = () => ()): Process_Result =
{
Bash.process(script, cwd = cwd, env = env, redirect = redirect, cleanup = cleanup).
result(progress_stdout = progress_stdout, progress_stderr = progress_stderr,
watchdog = watchdog, strict = strict)
}
private lazy val gnutar_check: Boolean =
try { bash("tar --version").check.out.containsSlice("GNU tar") || error("") }
catch { case ERROR(_) => false }
def gnutar(
args: String,
dir: Path = Path.current,
original_owner: Boolean = false,
redirect: Boolean = false): Process_Result =
{
val options =
(if (dir.is_current) "" else "-C " + File.bash_path(dir) + " ") +
(if (original_owner) "" else "--owner=root --group=staff ")
if (gnutar_check) bash("tar " + options + args, redirect = redirect)
else error("Expected to find GNU tar executable")
}
- def require_command(cmds: String*)
+ def require_command(cmds: String*): Unit =
{
for (cmd <- cmds) {
if (!bash(Bash.string(cmd) + " --version").ok) error("Missing command: " + quote(cmd))
}
}
def hostname(): String = bash("hostname -s").check.out
def open(arg: String): Unit =
bash("exec \"$ISABELLE_OPEN\" " + Bash.string(arg) + " >/dev/null 2>/dev/null &")
def pdf_viewer(arg: Path): Unit =
bash("exec \"$PDF_VIEWER\" " + File.bash_path(arg) + " >/dev/null 2>/dev/null &")
def open_external_file(name: String): Boolean =
{
val ext = Library.take_suffix((c: Char) => c != '.', name.toList)._2.mkString
val external =
ext.nonEmpty &&
Library.space_explode(':', getenv("ISABELLE_EXTERNAL_FILES")).contains(ext)
if (external) {
if (ext == "pdf" && Path.is_wellformed(name)) pdf_viewer(Path.explode(name))
else open(name)
}
external
}
def export_isabelle_identifier(isabelle_identifier: String): String =
if (isabelle_identifier == "") ""
else "export ISABELLE_IDENTIFIER=" + Bash.string(isabelle_identifier) + "\n"
/** Isabelle resources **/
/* repository clone with Admin */
def admin(): Boolean = Path.explode("~~/Admin").is_dir
/* components */
def components(): List[Path] =
Path.split(getenv_strict("ISABELLE_COMPONENTS"))
/* default logic */
def default_logic(args: String*): String =
{
args.find(_ != "") match {
case Some(logic) => logic
case None => getenv_strict("ISABELLE_LOGIC")
}
}
/* download file */
- private lazy val curl_check: Unit =
- try { require_command("curl") }
- catch { case ERROR(msg) => error(msg + " --- cannot download files") }
-
- def download(url: String, file: Path, progress: Progress = new Progress): Unit =
+ def download(url_name: String, file: Path, progress: Progress = new Progress): Unit =
{
- curl_check
- progress.echo("Getting " + quote(url))
- try {
- bash("curl --fail --silent --location " + Bash.string(url) +
- " > " + File.bash_path(file)).check
- }
- catch { case ERROR(msg) => cat_error("Failed to download " + quote(url), msg) }
+ val url = Url(url_name)
+ progress.echo("Getting " + quote(url_name))
+ val bytes =
+ try { Url.read_bytes(url) }
+ catch { case ERROR(msg) => cat_error("Failed to download " + quote(url_name), msg) }
+ Bytes.write(file, bytes)
}
object Download extends Scala.Fun("download")
{
val here = Scala_Project.here
def apply(arg: String): String =
- Library.space_explode('\u0000', arg) match {
+ Library.split_strings0(arg) match {
case List(url, file) => download(url, Path.explode(file)); ""
}
}
}
diff --git a/src/Pure/System/isabelle_tool.scala b/src/Pure/System/isabelle_tool.scala
--- a/src/Pure/System/isabelle_tool.scala
+++ b/src/Pure/System/isabelle_tool.scala
@@ -1,228 +1,228 @@
/* Title: Pure/System/isabelle_tool.scala
Author: Makarius
Author: Lars Hupel
Isabelle system tools: external executables or internal Scala functions.
*/
package isabelle
import java.net.URLClassLoader
import scala.reflect.runtime.universe
import scala.tools.reflect.{ToolBox, ToolBoxError}
object Isabelle_Tool
{
/* Scala source tools */
abstract class Body extends Function[List[String], Unit]
private def compile(path: Path): Body =
{
def err(msg: String): Nothing =
cat_error(msg, "The error(s) above occurred in Isabelle/Scala tool " + path)
val source = File.read(path)
val class_loader = new URLClassLoader(Array(), getClass.getClassLoader)
val tool_box = universe.runtimeMirror(class_loader).mkToolBox()
try {
val symbol = tool_box.parse(source) match {
case tree: universe.ModuleDef => tool_box.define(tree)
case _ => err("Source does not describe a module (Scala object)")
}
tool_box.compile(universe.Ident(symbol))() match {
case body: Body => body
case _ => err("Ill-typed source: Isabelle_Tool.Body expected")
}
}
catch {
case e: ToolBoxError =>
if (tool_box.frontEnd.hasErrors) {
val infos = tool_box.frontEnd.infos.toList
val msgs = infos.map(info => "Error in line " + info.pos.line + ":\n" + info.msg)
err(msgs.mkString("\n"))
}
else
err(e.toString)
}
}
/* external tools */
private def dirs(): List[Path] = Path.split(Isabelle_System.getenv_strict("ISABELLE_TOOLS"))
private def is_external(dir: Path, file_name: String): Boolean =
{
val file = (dir + Path.explode(file_name)).file
try {
file.isFile && file.canRead &&
(file_name.endsWith(".scala") || file.canExecute) &&
!file_name.endsWith("~") && !file_name.endsWith(".orig")
}
catch { case _: SecurityException => false }
}
private def find_external(name: String): Option[List[String] => Unit] =
dirs().collectFirst({
case dir if is_external(dir, name + ".scala") =>
compile(dir + Path.explode(name + ".scala"))
case dir if is_external(dir, name) =>
(args: List[String]) =>
{
val tool = dir + Path.explode(name)
val result = Isabelle_System.bash(File.bash_path(tool) + " " + Bash.strings(args))
sys.exit(result.print_stdout.rc)
}
})
/* internal tools */
private lazy val internal_tools: List[Isabelle_Tool] =
Isabelle_System.make_services(classOf[Isabelle_Scala_Tools]).flatMap(_.tools)
private def find_internal(name: String): Option[List[String] => Unit] =
internal_tools.collectFirst({
case tool if tool.name == name =>
args => Command_Line.tool { tool.body(args) }
})
/* list tools */
abstract class Entry
{
def name: String
def position: Properties.T
def description: String
def print: String =
description match {
case "" => name
case descr => name + " - " + descr
}
}
sealed case class External(name: String, path: Path) extends Entry
{
def position: Properties.T = Position.File(path.absolute.implode)
def description: String =
{
val Pattern = """.*\bDESCRIPTION: *(.*)""".r
split_lines(File.read(path)).collectFirst({ case Pattern(s) => s }) getOrElse ""
}
}
def external_tools(): List[External] =
{
for {
dir <- dirs() if dir.is_dir
file_name <- File.read_dir(dir) if is_external(dir, file_name)
} yield {
val path = dir + Path.explode(file_name)
val name = Library.perhaps_unsuffix(".scala", file_name)
External(name, path)
}
}
def isabelle_tools(): List[Entry] =
(external_tools() ::: internal_tools).sortBy(_.name)
object Isabelle_Tools extends Scala.Fun("isabelle_tools")
{
val here = Scala_Project.here
def apply(arg: String): String =
if (arg.nonEmpty) error("Bad argument: " + quote(arg))
else {
val result = isabelle_tools().map(entry => (entry.name, entry.position))
val body = { import XML.Encode._; list(pair(string, properties))(result) }
YXML.string_of_body(body)
}
}
/* command line entry point */
- def main(args: Array[String])
+ def main(args: Array[String]): Unit =
{
Command_Line.tool {
args.toList match {
case Nil | List("-?") =>
val tool_descriptions = isabelle_tools().map(_.print)
Getopts("""
Usage: isabelle TOOL [ARGS ...]
Start Isabelle TOOL with ARGS; pass "-?" for tool-specific help.
Available tools:""" + tool_descriptions.mkString("\n ", "\n ", "\n")).usage
case tool_name :: tool_args =>
find_external(tool_name) orElse find_internal(tool_name) match {
case Some(tool) => tool(tool_args)
case None => error("Unknown Isabelle tool: " + quote(tool_name))
}
}
}
}
}
sealed case class Isabelle_Tool(
name: String,
description: String,
here: Scala_Project.Here,
body: List[String] => Unit) extends Isabelle_Tool.Entry
{
def position: Position.T = here.position
}
class Isabelle_Scala_Tools(val tools: Isabelle_Tool*) extends Isabelle_System.Service
class Tools extends Isabelle_Scala_Tools(
Build.isabelle_tool,
Build_Docker.isabelle_tool,
Build_Job.isabelle_tool,
Doc.isabelle_tool,
Dump.isabelle_tool,
Export.isabelle_tool,
ML_Process.isabelle_tool,
Mercurial.isabelle_tool,
Mkroot.isabelle_tool,
Options.isabelle_tool,
Phabricator.isabelle_tool1,
Phabricator.isabelle_tool2,
Phabricator.isabelle_tool3,
Phabricator.isabelle_tool4,
Presentation.isabelle_tool,
Profiling_Report.isabelle_tool,
Server.isabelle_tool,
Sessions.isabelle_tool,
Scala_Project.isabelle_tool,
Update.isabelle_tool,
Update_Cartouches.isabelle_tool,
Update_Comments.isabelle_tool,
Update_Header.isabelle_tool,
Update_Then.isabelle_tool,
Update_Theorems.isabelle_tool,
isabelle.vscode.TextMate_Grammar.isabelle_tool,
isabelle.vscode.Language_Server.isabelle_tool)
class Admin_Tools extends Isabelle_Scala_Tools(
Build_CSDP.isabelle_tool,
Build_Cygwin.isabelle_tool,
Build_Doc.isabelle_tool,
Build_E.isabelle_tool,
Build_Fonts.isabelle_tool,
Build_JDK.isabelle_tool,
Build_PolyML.isabelle_tool1,
Build_PolyML.isabelle_tool2,
Build_SPASS.isabelle_tool,
Build_SQLite.isabelle_tool,
Build_Status.isabelle_tool,
Build_Vampire.isabelle_tool,
Build_VeriT.isabelle_tool,
Build_Zipperposition.isabelle_tool,
Check_Sources.isabelle_tool,
Components.isabelle_tool,
isabelle.vscode.Build_VSCode.isabelle_tool)
diff --git a/src/Pure/System/linux.scala b/src/Pure/System/linux.scala
--- a/src/Pure/System/linux.scala
+++ b/src/Pure/System/linux.scala
@@ -1,159 +1,157 @@
/* Title: Pure/System/linux.scala
Author: Makarius
Specific support for Linux, notably Ubuntu/Debian.
*/
package isabelle
import scala.util.matching.Regex
object Linux
{
/* check system */
def check_system(): Unit =
if (!Platform.is_linux) error("Not a Linux system")
def check_system_root(): Unit =
{
check_system()
if (Isabelle_System.bash("id -u").check.out != "0") error("Not running as superuser (root)")
}
/* release */
object Release
{
private val ID = """^Distributor ID:\s*(\S.*)$""".r
private val RELEASE = """^Release:\s*(\S.*)$""".r
private val DESCRIPTION = """^Description:\s*(\S.*)$""".r
def apply(): Release =
{
val lines = Isabelle_System.bash("lsb_release -a").check.out_lines
def find(R: Regex): String = lines.collectFirst({ case R(a) => a }).getOrElse("Unknown")
new Release(find(ID), find(RELEASE), find(DESCRIPTION))
}
}
final class Release private(val id: String, val release: String, val description: String)
{
override def toString: String = description
def is_ubuntu: Boolean = id == "Ubuntu"
def is_ubuntu_18_04: Boolean = is_ubuntu && release == "18.04"
def is_ubuntu_20_04: Boolean = is_ubuntu && release == "20.04"
}
/* packages */
def reboot_required(): Boolean =
Path.explode("/var/run/reboot-required").is_file
def check_reboot_required(): Unit =
if (reboot_required()) error("Reboot required")
def package_update(progress: Progress = new Progress): Unit =
progress.bash(
"""apt-get update -y && apt-get upgrade -y && apt autoremove -y""",
echo = true).check
def package_install(packages: List[String], progress: Progress = new Progress): Unit =
progress.bash("apt-get install -y -- " + Bash.strings(packages), echo = true).check
def package_installed(name: String): Boolean =
{
val result = Isabelle_System.bash("dpkg-query -s " + Bash.string(name))
val pattern = """^Status:.*installed.*$""".r.pattern
result.ok && result.out_lines.exists(line => pattern.matcher(line).matches)
}
/* users */
def user_exists(name: String): Boolean =
Isabelle_System.bash("id " + Bash.string(name)).ok
def user_entry(name: String, field: Int): String =
{
val result = Isabelle_System.bash("getent passwd " + Bash.string(name)).check
val fields = space_explode(':', result.out)
if (1 <= field && field <= fields.length) fields(field - 1)
else error("No passwd field " + field + " for user " + quote(name))
}
def user_description(name: String): String = user_entry(name, 5).takeWhile(_ != ',')
def user_home(name: String): String = user_entry(name, 6)
def user_add(name: String,
description: String = "",
system: Boolean = false,
- ssh_setup: Boolean = false)
+ ssh_setup: Boolean = false): Unit =
{
require(!description.contains(','), "malformed description")
if (user_exists(name)) error("User already exists: " + quote(name))
Isabelle_System.bash(
"adduser --quiet --disabled-password --gecos " + Bash.string(description) +
(if (system) " --system --group --shell /bin/bash " else "") +
" " + Bash.string(name)).check
if (ssh_setup) {
val id_rsa = user_home(name) + "/.ssh/id_rsa"
Isabelle_System.bash("""
if [ ! -f """ + Bash.string(id_rsa) + """ ]
then
yes '\n' | sudo -i -u """ + Bash.string(name) +
""" ssh-keygen -q -f """ + Bash.string(id_rsa) + """
fi
""").check
}
}
/* system services */
def service_operation(op: String, name: String): Unit =
Isabelle_System.bash("systemctl " + Bash.string(op) + " " + Bash.string(name)).check
- def service_enable(name: String) { service_operation("enable", name) }
- def service_disable(name: String) { service_operation("disable", name) }
- def service_start(name: String) { service_operation("start", name) }
- def service_stop(name: String) { service_operation("stop", name) }
- def service_restart(name: String) { service_operation("restart", name) }
+ def service_enable(name: String): Unit = service_operation("enable", name)
+ def service_disable(name: String): Unit = service_operation("disable", name)
+ def service_start(name: String): Unit = service_operation("start", name)
+ def service_stop(name: String): Unit = service_operation("stop", name)
+ def service_restart(name: String): Unit = service_operation("restart", name)
- def service_shutdown(name: String)
- {
+ def service_shutdown(name: String): Unit =
try { service_stop(name) }
catch { case ERROR(_) => }
- }
- def service_install(name: String, spec: String)
+ def service_install(name: String, spec: String): Unit =
{
service_shutdown(name)
val service_file = Path.explode("/lib/systemd/system") + Path.basic(name).ext("service")
File.write(service_file, spec)
Isabelle_System.chmod("644", service_file)
service_enable(name)
service_restart(name)
}
/* passwords */
def generate_password(length: Int = 10): String =
{
require(length >= 6, "password too short")
Isabelle_System.bash("pwgen " + length + " 1").check.out
}
}
diff --git a/src/Pure/System/mingw.scala b/src/Pure/System/mingw.scala
--- a/src/Pure/System/mingw.scala
+++ b/src/Pure/System/mingw.scala
@@ -1,51 +1,51 @@
/* Title: Pure/System/mingw.scala
Author: Makarius
Support for MSYS2/MinGW64 on Windows.
*/
package isabelle
object MinGW
{
def environment: List[String] =
List("PATH=/usr/bin:/bin:/mingw64/bin", "CONFIG_SITE=/mingw64/etc/config.site")
def environment_export: String =
environment.map(a => "export " + Bash.string(a)).mkString("", "\n", "\n")
val none: MinGW = new MinGW(None)
def apply(path: Path) = new MinGW(Some(path))
}
class MinGW private(val root: Option[Path])
{
override def toString: String =
root match {
case None => "MinGW.none"
case Some(msys_root) => "MinGW(" + msys_root.toString + ")"
}
def bash_script(script: String): String =
root match {
case None => script
case Some(msys_root) =>
File.bash_path(msys_root + Path.explode("usr/bin/bash")) +
" -c " + Bash.string(MinGW.environment_export + script)
}
def get_root: Path =
if (!Platform.is_windows) error("Windows platform required")
else if (root.isEmpty) error("Windows platform requires msys/mingw root specification")
else root.get
- def check
+ def check: Unit =
{
if (Platform.is_windows) {
get_root
try { require(Isabelle_System.bash(bash_script("uname -s")).check.out.startsWith("MSYS")) }
catch { case ERROR(msg) => cat_error("Bad msys/mingw installation " + get_root, msg) }
}
}
}
diff --git a/src/Pure/System/options.scala b/src/Pure/System/options.scala
--- a/src/Pure/System/options.scala
+++ b/src/Pure/System/options.scala
@@ -1,454 +1,454 @@
/* Title: Pure/System/options.scala
Author: Makarius
System options with external string representation.
*/
package isabelle
object Options
{
type Spec = (String, Option[String])
val empty: Options = new Options()
/* representation */
sealed abstract class Type
{
def print: String = Word.lowercase(toString)
}
case object Bool extends Type
case object Int extends Type
case object Real extends Type
case object String extends Type
case object Unknown extends Type
case class Opt(
public: Boolean,
pos: Position.T,
name: String,
typ: Type,
value: String,
default_value: String,
description: String,
section: String)
{
private def print(default: Boolean): String =
{
val x = if (default) default_value else value
"option " + name + " : " + typ.print + " = " +
(if (typ == Options.String) quote(x) else x) +
(if (description == "") "" else "\n -- " + quote(description))
}
def print: String = print(false)
def print_default: String = print(true)
def title(strip: String = ""): String =
{
val words = Word.explode('_', name)
val words1 =
words match {
case word :: rest if word == strip => rest
case _ => words
}
Word.implode(words1.map(Word.perhaps_capitalize))
}
def unknown: Boolean = typ == Unknown
}
/* parsing */
private val SECTION = "section"
private val PUBLIC = "public"
private val OPTION = "option"
private val OPTIONS = Path.explode("etc/options")
private val PREFS = Path.explode("$ISABELLE_HOME_USER/etc/preferences")
val options_syntax: Outer_Syntax =
Outer_Syntax.empty + ":" + "=" + "--" + Symbol.comment + Symbol.comment_decoded +
(SECTION, Keyword.DOCUMENT_HEADING) +
(PUBLIC, Keyword.BEFORE_COMMAND) +
(OPTION, Keyword.THY_DECL)
val prefs_syntax: Outer_Syntax = Outer_Syntax.empty + "="
trait Parser extends Parse.Parser
{
val option_name: Parser[String] = atom("option name", _.is_name)
val option_type: Parser[String] = atom("option type", _.is_name)
val option_value: Parser[String] =
opt(token("-", tok => tok.is_sym_ident && tok.content == "-")) ~ atom("nat", _.is_nat) ^^
{ case s ~ n => if (s.isDefined) "-" + n else n } |
atom("option value", tok => tok.is_name || tok.is_float)
}
private object Parser extends Parser
{
def comment_marker: Parser[String] =
$$$("--") | $$$(Symbol.comment) | $$$(Symbol.comment_decoded)
val option_entry: Parser[Options => Options] =
{
command(SECTION) ~! text ^^
{ case _ ~ a => (options: Options) => options.set_section(a) } |
opt($$$(PUBLIC)) ~ command(OPTION) ~! (position(option_name) ~ $$$(":") ~ option_type ~
$$$("=") ~ option_value ~ (comment_marker ~! text ^^ { case _ ~ x => x } | success(""))) ^^
{ case a ~ _ ~ ((b, pos) ~ _ ~ c ~ _ ~ d ~ e) =>
(options: Options) => options.declare(a.isDefined, pos, b, c, d, e) }
}
val prefs_entry: Parser[Options => Options] =
{
option_name ~ ($$$("=") ~! option_value) ^^
{ case a ~ (_ ~ b) => (options: Options) => options.add_permissive(a, b) }
}
def parse_file(options: Options, file_name: String, content: String,
syntax: Outer_Syntax = options_syntax,
parser: Parser[Options => Options] = option_entry): Options =
{
val toks = Token.explode(syntax.keywords, content)
val ops =
parse_all(rep(parser), Token.reader(toks, Token.Pos.file(file_name))) match {
case Success(result, _) => result
case bad => error(bad.toString)
}
try { (options.set_section("") /: ops) { case (opts, op) => op(opts) } }
catch { case ERROR(msg) => error(msg + Position.here(Position.File(file_name))) }
}
def parse_prefs(options: Options, content: String): Options =
parse_file(options, PREFS.file_name, content, syntax = prefs_syntax, parser = prefs_entry)
}
def read_prefs(file: Path = PREFS): String =
if (file.is_file) File.read(file) else ""
def init(prefs: String = read_prefs(PREFS), opts: List[String] = Nil): Options =
{
var options = empty
for {
dir <- Isabelle_System.components()
file = dir + OPTIONS if file.is_file
} { options = Parser.parse_file(options, file.implode, File.read(file)) }
(Options.Parser.parse_prefs(options, prefs) /: opts)(_ + _)
}
/* encode */
val encode: XML.Encode.T[Options] = (options => options.encode)
/* Isabelle tool wrapper */
val isabelle_tool = Isabelle_Tool("options", "print Isabelle system options",
Scala_Project.here, args =>
{
var build_options = false
var get_option = ""
var list_options = false
var export_file = ""
val getopts = Getopts("""
Usage: isabelle options [OPTIONS] [MORE_OPTIONS ...]
Options are:
-b include $ISABELLE_BUILD_OPTIONS
-g OPTION get value of OPTION
-l list options
-x FILE export options to FILE in YXML format
Report Isabelle system options, augmented by MORE_OPTIONS given as
arguments NAME=VAL or NAME.
""",
"b" -> (_ => build_options = true),
"g:" -> (arg => get_option = arg),
"l" -> (_ => list_options = true),
"x:" -> (arg => export_file = arg))
val more_options = getopts(args)
if (get_option == "" && !list_options && export_file == "") getopts.usage()
val options =
{
val options0 = Options.init()
val options1 =
if (build_options)
(options0 /: Word.explode(Isabelle_System.getenv("ISABELLE_BUILD_OPTIONS")))(_ + _)
else options0
(options1 /: more_options)(_ + _)
}
if (get_option != "")
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 == "")
Output.writeln(options.print, stdout = true)
})
}
final class Options private(
val options: Map[String, Options.Opt] = Map.empty,
val section: String = "")
{
override def toString: String = options.iterator.mkString("Options(", ",", ")")
private def print_opt(opt: Options.Opt): String =
if (opt.public) "public " + opt.print else opt.print
def print: String = cat_lines(options.toList.sortBy(_._1).map(p => print_opt(p._2)))
def description(name: String): String = check_name(name).description
/* check */
def check_name(name: String): Options.Opt =
options.get(name) match {
case Some(opt) if !opt.unknown => opt
case _ => error("Unknown option " + quote(name))
}
private def check_type(name: String, typ: Options.Type): Options.Opt =
{
val opt = check_name(name)
if (opt.typ == typ) opt
else error("Ill-typed option " + quote(name) + " : " + opt.typ.print + " vs. " + typ.print)
}
/* basic operations */
private def put[A](name: String, typ: Options.Type, value: String): Options =
{
val opt = check_type(name, typ)
new Options(options + (name -> opt.copy(value = value)), section)
}
private def get[A](name: String, typ: Options.Type, parse: String => Option[A]): A =
{
val opt = check_type(name, typ)
parse(opt.value) match {
case Some(x) => x
case None =>
error("Malformed value for option " + quote(name) +
" : " + typ.print + " =\n" + quote(opt.value))
}
}
/* internal lookup and update */
class Bool_Access
{
def apply(name: String): Boolean = get(name, Options.Bool, Value.Boolean.unapply)
def update(name: String, x: Boolean): Options =
put(name, Options.Bool, Value.Boolean(x))
}
val bool = new Bool_Access
class Int_Access
{
def apply(name: String): Int = get(name, Options.Int, Value.Int.unapply)
def update(name: String, x: Int): Options =
put(name, Options.Int, Value.Int(x))
}
val int = new Int_Access
class Real_Access
{
def apply(name: String): Double = get(name, Options.Real, Value.Double.unapply)
def update(name: String, x: Double): Options =
put(name, Options.Real, Value.Double(x))
}
val real = new Real_Access
class String_Access
{
def apply(name: String): String = get(name, Options.String, s => Some(s))
def update(name: String, x: String): Options = put(name, Options.String, x)
}
val string = new String_Access
def proper_string(name: String): Option[String] =
Library.proper_string(string(name))
def seconds(name: String): Time = Time.seconds(real(name))
/* external updates */
private def check_value(name: String): Options =
{
val opt = check_name(name)
opt.typ match {
case Options.Bool => bool(name); this
case Options.Int => int(name); this
case Options.Real => real(name); this
case Options.String => string(name); this
case Options.Unknown => this
}
}
def declare(
public: Boolean,
pos: Position.T,
name: String,
typ_name: String,
value: String,
description: String): Options =
{
options.get(name) match {
case Some(other) =>
error("Duplicate declaration of option " + quote(name) + Position.here(pos) +
Position.here(other.pos))
case None =>
val typ =
typ_name match {
case "bool" => Options.Bool
case "int" => Options.Int
case "real" => Options.Real
case "string" => Options.String
case _ =>
error("Unknown type for option " + quote(name) + " : " + quote(typ_name) +
Position.here(pos))
}
val opt = Options.Opt(public, pos, name, typ, value, value, description, section)
(new Options(options + (name -> opt), section)).check_value(name)
}
}
def add_permissive(name: String, value: String): Options =
{
if (options.isDefinedAt(name)) this + (name, value)
else {
val opt = Options.Opt(false, Position.none, name, Options.Unknown, value, value, "", "")
new Options(options + (name -> opt), section)
}
}
def + (name: String, value: String): Options =
{
val opt = check_name(name)
(new Options(options + (name -> opt.copy(value = value)), section)).check_value(name)
}
def + (name: String, opt_value: Option[String]): Options =
{
val opt = check_name(name)
opt_value match {
case Some(value) => this + (name, value)
case None if opt.typ == Options.Bool => this + (name, "true")
case None => error("Missing value for option " + quote(name) + " : " + opt.typ.print)
}
}
def + (str: String): Options =
{
str.indexOf('=') match {
case -1 => this + (str, None)
case i => this + (str.substring(0, i), str.substring(i + 1))
}
}
def ++ (specs: List[Options.Spec]): Options =
(this /: specs)({ case (x, (y, z)) => x + (y, z) })
/* sections */
def set_section(new_section: String): Options =
new Options(options, new_section)
def sections: List[(String, List[Options.Opt])] =
options.groupBy(_._2.section).toList.map({ case (a, opts) => (a, opts.toList.map(_._2)) })
/* encode */
def encode: XML.Body =
{
val opts =
for ((_, opt) <- options.toList; if !opt.unknown)
yield (opt.pos, (opt.name, (opt.typ.print, opt.value)))
import XML.Encode.{string => string_, _}
list(pair(properties, pair(string_, pair(string_, string_))))(opts)
}
/* save preferences */
- def save_prefs(file: Path = Options.PREFS)
+ def save_prefs(file: Path = Options.PREFS): Unit =
{
val defaults: Options = Options.init(prefs = "")
val changed =
(for {
(name, opt2) <- options.iterator
opt1 = defaults.options.get(name)
if opt1.isEmpty || opt1.get.value != opt2.value
} yield (name, opt2.value, if (opt1.isEmpty) " (* unknown *)" else "")).toList
val prefs =
changed.sortBy(_._1)
.map({ case (x, y, z) => x + " = " + Outer_Syntax.quote_string(y) + z + "\n" }).mkString
Isabelle_System.make_directory(file.dir)
File.write_backup(file, "(* generated by Isabelle " + Date.now() + " *)\n\n" + prefs)
}
}
class Options_Variable(init_options: Options)
{
private var options = init_options
def value: Options = synchronized { options }
private def upd(f: Options => Options): Unit = synchronized { options = f(options) }
def += (name: String, x: String): Unit = upd(opts => opts + (name, x))
class Bool_Access
{
def apply(name: String): Boolean = value.bool(name)
def update(name: String, x: Boolean): Unit = upd(opts => opts.bool.update(name, x))
}
val bool = new Bool_Access
class Int_Access
{
def apply(name: String): Int = value.int(name)
def update(name: String, x: Int): Unit = upd(opts => opts.int.update(name, x))
}
val int = new Int_Access
class Real_Access
{
def apply(name: String): Double = value.real(name)
def update(name: String, x: Double): Unit = upd(opts => opts.real.update(name, x))
}
val real = new Real_Access
class String_Access
{
def apply(name: String): String = value.string(name)
def update(name: String, x: String): Unit = upd(opts => opts.string.update(name, x))
}
val string = new String_Access
def proper_string(name: String): Option[String] =
Library.proper_string(string(name))
def seconds(name: String): Time = value.seconds(name)
}
diff --git a/src/Pure/System/posix_interrupt.scala b/src/Pure/System/posix_interrupt.scala
--- a/src/Pure/System/posix_interrupt.scala
+++ b/src/Pure/System/posix_interrupt.scala
@@ -1,29 +1,29 @@
/* Title: Pure/System/posix_interrupt.scala
Author: Makarius
Support for POSIX interrupts (bypassed on Windows).
*/
package isabelle
import sun.misc.{Signal, SignalHandler}
object POSIX_Interrupt
{
def handler[A](h: => Unit)(e: => A): A =
{
val SIGINT = new Signal("INT")
- val new_handler = new SignalHandler { def handle(s: Signal) { h } }
+ val new_handler = new SignalHandler { def handle(s: Signal): Unit = h }
val old_handler = Signal.handle(SIGINT, new_handler)
try { e } finally { Signal.handle(SIGINT, old_handler) }
}
def exception[A](e: => A): A =
{
val thread = Thread.currentThread
handler { thread.interrupt } { e }
}
}
diff --git a/src/Pure/System/progress.scala b/src/Pure/System/progress.scala
--- a/src/Pure/System/progress.scala
+++ b/src/Pure/System/progress.scala
@@ -1,87 +1,87 @@
/* Title: Pure/System/progress.scala
Author: Makarius
Progress context for system processes.
*/
package isabelle
import java.io.{File => JFile}
object Progress
{
sealed case class Theory(theory: String, session: String = "", percentage: Option[Int] = None)
{
def message: String = print_session + print_theory + print_percentage
def print_session: String = if (session == "") "" else session + ": "
def print_theory: String = "theory " + theory
def print_percentage: String =
percentage match { case None => "" case Some(p) => " " + p + "%" }
}
}
class Progress
{
- def echo(msg: String) {}
- def echo_if(cond: Boolean, msg: String) { if (cond) echo(msg) }
- def theory(theory: Progress.Theory) {}
- def nodes_status(nodes_status: Document_Status.Nodes_Status) {}
+ def echo(msg: String): Unit = {}
+ def echo_if(cond: Boolean, msg: String): Unit = { if (cond) echo(msg) }
+ def theory(theory: Progress.Theory): Unit = {}
+ def nodes_status(nodes_status: Document_Status.Nodes_Status): Unit = {}
- def echo_warning(msg: String) { echo(Output.warning_text(msg)) }
- def echo_error_message(msg: String) { echo(Output.error_message_text(msg)) }
+ def echo_warning(msg: String): Unit = echo(Output.warning_text(msg))
+ def echo_error_message(msg: String): Unit = echo(Output.error_message_text(msg))
def timeit[A](message: String = "", enabled: Boolean = true)(e: => A): A =
Timing.timeit(message, enabled, echo)(e)
@volatile protected var is_stopped = false
- def stop { is_stopped = true }
+ def stop: Unit = { is_stopped = true }
def stopped: Boolean =
{
if (Thread.interrupted) is_stopped = true
is_stopped
}
def interrupt_handler[A](e: => A): A = POSIX_Interrupt.handler { stop } { e }
- def expose_interrupt() { if (stopped) throw Exn.Interrupt() }
+ def expose_interrupt(): Unit = if (stopped) throw Exn.Interrupt()
override def toString: String = if (stopped) "Progress(stopped)" else "Progress"
def bash(script: String,
cwd: JFile = null,
env: Map[String, String] = Isabelle_System.settings(),
redirect: Boolean = false,
echo: Boolean = false,
watchdog: Time = Time.zero,
strict: Boolean = true): Process_Result =
{
val result =
Isabelle_System.bash(script, cwd = cwd, env = env, redirect = redirect,
progress_stdout = echo_if(echo, _),
progress_stderr = echo_if(echo, _),
watchdog = if (watchdog.is_zero) None else Some((watchdog, _ => stopped)),
strict = strict)
if (strict && stopped) throw Exn.Interrupt() else result
}
}
class Console_Progress(verbose: Boolean = false, stderr: Boolean = false) extends Progress
{
override def echo(msg: String): Unit =
Output.writeln(msg, stdout = !stderr, include_empty = true)
override def theory(theory: Progress.Theory): Unit =
if (verbose) echo(theory.message)
}
class File_Progress(path: Path, verbose: Boolean = false) extends Progress
{
override def echo(msg: String): Unit =
File.append(path, msg + "\n")
override def theory(theory: Progress.Theory): Unit =
if (verbose) echo(theory.message)
override def toString: String = path.toString
}
diff --git a/src/Pure/System/scala.scala b/src/Pure/System/scala.scala
--- a/src/Pure/System/scala.scala
+++ b/src/Pure/System/scala.scala
@@ -1,254 +1,254 @@
/* Title: Pure/System/scala.scala
Author: Makarius
Support for Scala at runtime.
*/
package isabelle
import java.io.{File => JFile, StringWriter, PrintWriter}
import scala.tools.nsc.{GenericRunnerSettings, ConsoleWriter, NewLinePrintWriter}
import scala.tools.nsc.interpreter.{IMain, Results}
import scala.tools.nsc.interpreter.shell.ReplReporterImpl
object Scala
{
/** registered functions **/
abstract class Fun(val name: String) extends Function[String, String]
{
override def toString: String = name
def position: Properties.T = here.position
def here: Scala_Project.Here
def apply(arg: String): String
}
class Functions(val functions: Fun*) extends Isabelle_System.Service
lazy val functions: List[Fun] =
Isabelle_System.make_services(classOf[Functions]).flatMap(_.functions)
/** demo functions **/
object Echo extends Fun("echo")
{
val here = Scala_Project.here
def apply(arg: String): String = arg
}
object Sleep extends Fun("sleep")
{
val here = Scala_Project.here
def apply(seconds: String): String =
{
val t =
seconds match {
case Value.Double(s) => Time.seconds(s)
case _ => error("Malformed argument: " + quote(seconds))
}
val t0 = Time.now()
t.sleep
val t1 = Time.now()
(t1 - t0).toString
}
}
/** compiler **/
object Compiler
{
def context(
error: String => Unit = Exn.error,
jar_dirs: List[JFile] = Nil): Context =
{
def find_jars(dir: JFile): List[String] =
File.find_files(dir, file => file.getName.endsWith(".jar")).
map(File.absolute_name)
val class_path =
for {
prop <- List("isabelle.scala.classpath", "java.class.path")
path = System.getProperty(prop, "") if path != "\"\""
elem <- space_explode(JFile.pathSeparatorChar, path)
} yield elem
val settings = new GenericRunnerSettings(error)
settings.classpath.value =
(class_path ::: jar_dirs.flatMap(find_jars)).mkString(JFile.pathSeparator)
new Context(settings)
}
def default_print_writer: PrintWriter =
new NewLinePrintWriter(new ConsoleWriter, true)
class Context private [Compiler](val settings: GenericRunnerSettings)
{
override def toString: String = settings.toString
def interpreter(
print_writer: PrintWriter = default_print_writer,
class_loader: ClassLoader = null): IMain =
{
new IMain(settings, new ReplReporterImpl(settings, print_writer))
{
override def parentClassLoader: ClassLoader =
if (class_loader == null) super.parentClassLoader
else class_loader
}
}
def toplevel(interpret: Boolean, source: String): List[String] =
{
val out = new StringWriter
val interp = interpreter(new PrintWriter(out))
val ok =
interp.withLabel("\u0001") {
if (interpret) interp.interpret(source) == Results.Success
else (new interp.ReadEvalPrint).compile(source)
}
out.close
val Error = """(?s)^\S* error: (.*)$""".r
val errors =
space_explode('\u0001', Library.strip_ansi_color(out.toString)).
collect({ case Error(msg) => "Scala error: " + Library.trim_line(msg) })
if (!ok && errors.isEmpty) List("Error") else errors
}
}
}
object Toplevel extends Fun("scala_toplevel")
{
val here = Scala_Project.here
def apply(arg: String): String =
{
val (interpret, source) =
YXML.parse_body(arg) match {
case Nil => (false, "")
case List(XML.Text(source)) => (false, source)
case body => import XML.Decode._; pair(bool, string)(body)
}
val errors =
try { Compiler.context().toplevel(interpret, source) }
catch { case ERROR(msg) => List(msg) }
locally { import XML.Encode._; YXML.string_of_body(list(string)(errors)) }
}
}
/** invoke Scala functions from ML **/
/* invoke function */
object Tag extends Enumeration
{
val NULL, OK, ERROR, FAIL, INTERRUPT = Value
}
def function(name: String, arg: String): (Tag.Value, String) =
functions.find(fun => fun.name == name) match {
case Some(fun) =>
Exn.capture { fun(arg) } match {
case Exn.Res(null) => (Tag.NULL, "")
case Exn.Res(res) => (Tag.OK, res)
case Exn.Exn(Exn.Interrupt()) => (Tag.INTERRUPT, "")
case Exn.Exn(e) => (Tag.ERROR, Exn.message(e))
}
case None => (Tag.FAIL, "Unknown Isabelle/Scala function: " + quote(name))
}
/* protocol handler */
class Handler extends Session.Protocol_Handler
{
private var session: Session = null
private var futures = Map.empty[String, Future[Unit]]
override def init(session: Session): Unit =
synchronized { this.session = session }
override def exit(): Unit = synchronized
{
for ((id, future) <- futures) cancel(id, future)
futures = Map.empty
}
private def result(id: String, tag: Scala.Tag.Value, res: String): Unit =
synchronized
{
if (futures.isDefinedAt(id)) {
session.protocol_command("Scala.result", id, tag.id.toString, res)
futures -= id
}
}
- private def cancel(id: String, future: Future[Unit])
+ private def cancel(id: String, future: Future[Unit]): Unit =
{
future.cancel
result(id, Scala.Tag.INTERRUPT, "")
}
private def invoke_scala(msg: Prover.Protocol_Output): Boolean = synchronized
{
msg.properties match {
case Markup.Invoke_Scala(name, id, thread) =>
- def body
+ def body: Unit =
{
val (tag, res) = Scala.function(name, msg.text)
result(id, tag, res)
}
val future =
if (thread) {
Future.thread(name = Isabelle_Thread.make_name(base = "invoke_scala"))(body)
}
else Future.fork(body)
futures += (id -> future)
true
case _ => false
}
}
private def cancel_scala(msg: Prover.Protocol_Output): Boolean = synchronized
{
msg.properties match {
case Markup.Cancel_Scala(id) =>
futures.get(id) match {
case Some(future) => cancel(id, future)
case None =>
}
true
case _ => false
}
}
override val functions =
List(
Markup.Invoke_Scala.name -> invoke_scala,
Markup.Cancel_Scala.name -> cancel_scala)
}
}
class Scala_Functions extends Scala.Functions(
Scala.Echo,
Scala.Sleep,
Scala.Toplevel,
Doc.Doc_Names,
Bash.Process,
Bibtex.Check_Database,
Isabelle_System.Make_Directory,
Isabelle_System.Copy_Dir,
Isabelle_System.Copy_File,
Isabelle_System.Copy_File_Base,
Isabelle_System.Rm_Tree,
Isabelle_System.Download,
Isabelle_Tool.Isabelle_Tools)
diff --git a/src/Pure/System/system_channel.scala b/src/Pure/System/system_channel.scala
--- a/src/Pure/System/system_channel.scala
+++ b/src/Pure/System/system_channel.scala
@@ -1,46 +1,46 @@
/* Title: Pure/System/system_channel.scala
Author: Makarius
Socket-based system channel for inter-process communication.
*/
package isabelle
import java.io.{InputStream, OutputStream}
import java.net.{ServerSocket, InetAddress}
object System_Channel
{
def apply(): System_Channel = new System_Channel
}
class System_Channel private
{
private val server = new ServerSocket(0, 50, Server.localhost)
val address: String = Server.print_address(server.getLocalPort)
val password: String = UUID.random().toString
override def toString: String = address
- def shutdown() { server.close }
+ def shutdown(): Unit = server.close
def rendezvous(): (OutputStream, InputStream) =
{
val socket = server.accept
try {
val out_stream = socket.getOutputStream
val in_stream = socket.getInputStream
if (Byte_Message.read_line(in_stream).map(_.text) == Some(password)) (out_stream, in_stream)
else {
out_stream.close
in_stream.close
error("Failed to connect system channel: bad password")
}
}
finally { shutdown() }
}
}
diff --git a/src/Pure/System/tty_loop.scala b/src/Pure/System/tty_loop.scala
--- a/src/Pure/System/tty_loop.scala
+++ b/src/Pure/System/tty_loop.scala
@@ -1,68 +1,68 @@
/* Title: Pure/System/tty_loop.scala
Author: Makarius
Line-oriented TTY loop.
*/
package isabelle
import java.io.{IOException, Writer, Reader, InputStreamReader, BufferedReader}
class TTY_Loop(
writer: Writer,
reader: Reader,
writer_lock: AnyRef = new Object)
{
private val console_output = Future.thread[Unit]("console_output", uninterruptible = true) {
try {
val result = new StringBuilder(100)
var finished = false
while (!finished) {
var c = -1
var done = false
while (!done && (result.isEmpty || reader.ready)) {
c = reader.read
if (c >= 0) result.append(c.asInstanceOf[Char])
else done = true
}
if (result.nonEmpty) {
System.out.print(result.toString)
System.out.flush()
result.clear
}
else {
reader.close()
finished = true
}
}
}
catch { case e: IOException => case Exn.Interrupt() => }
}
private val console_input = Future.thread[Unit]("console_input", uninterruptible = true) {
val console_reader = new BufferedReader(new InputStreamReader(System.in))
try {
var finished = false
while (!finished) {
console_reader.readLine() match {
case null =>
writer.close()
finished = true
case line =>
writer_lock.synchronized {
writer.write(line)
writer.write("\n")
writer.flush()
}
}
}
}
catch { case e: IOException => case Exn.Interrupt() => }
}
- def join { console_output.join; console_input.join }
+ def join: Unit = { console_output.join; console_input.join }
- def cancel { console_input.cancel }
+ def cancel: Unit = console_input.cancel
}
diff --git a/src/Pure/Thy/bibtex.scala b/src/Pure/Thy/bibtex.scala
--- a/src/Pure/Thy/bibtex.scala
+++ b/src/Pure/Thy/bibtex.scala
@@ -1,702 +1,702 @@
/* Title: Pure/Thy/bibtex.scala
Author: Makarius
BibTeX support.
*/
package isabelle
import java.io.{File => JFile}
import scala.collection.mutable
import scala.util.parsing.combinator.RegexParsers
import scala.util.parsing.input.Reader
object Bibtex
{
/** file format **/
def is_bibtex(name: String): Boolean = name.endsWith(".bib")
class File_Format extends isabelle.File_Format
{
val format_name: String = "bibtex"
val file_ext: String = "bib"
override def theory_suffix: String = "bibtex_file"
override def theory_content(name: String): String =
"""theory "bib" imports Pure begin bibtex_file """ +
Outer_Syntax.quote_string(name) + """ end"""
override def html_document(snapshot: Document.Snapshot): Option[Presentation.HTML_Document] =
{
val name = snapshot.node_name
if (detect(name.node)) {
val title = "Bibliography " + quote(snapshot.node_name.path.file_name)
val content =
Isabelle_System.with_tmp_file("bib", "bib") { bib =>
File.write(bib, snapshot.node.source)
Bibtex.html_output(List(bib), style = "unsort", title = title)
}
Some(Presentation.HTML_Document(title, content))
}
else None
}
}
/** bibtex errors **/
def bibtex_errors(dir: Path, root_name: String): List[String] =
{
val log_path = dir + Path.explode(root_name).ext("blg")
if (log_path.is_file) {
val Error1 = """^(I couldn't open database file .+)$""".r
val Error2 = """^(.+)---line (\d+) of file (.+)""".r
Line.logical_lines(File.read(log_path)).flatMap(line =>
line match {
case Error1(msg) => Some("Bibtex error: " + msg)
case Error2(msg, Value.Int(l), file) =>
val path = File.standard_path(file)
if (Path.is_wellformed(path)) {
val pos = Position.Line_File(l, (dir + Path.explode(path)).canonical.implode)
Some("Bibtex error" + Position.here(pos) + ":\n " + msg)
}
else None
case _ => None
})
}
else Nil
}
/** check database **/
def check_database(database: String): (List[(String, Position.T)], List[(String, Position.T)]) =
{
val chunks = parse(Line.normalize(database))
var chunk_pos = Map.empty[String, Position.T]
val tokens = new mutable.ListBuffer[(Token, Position.T)]
var line = 1
var offset = 1
def make_pos(length: Int): Position.T =
Position.Offset(offset) ::: Position.End_Offset(offset + length) ::: Position.Line(line)
- def advance_pos(tok: Token)
+ def advance_pos(tok: Token): Unit =
{
for (s <- Symbol.iterator(tok.source)) {
if (Symbol.is_newline(s)) line += 1
offset += 1
}
}
def get_line_pos(l: Int): Position.T =
if (0 < l && l <= tokens.length) tokens(l - 1)._2 else Position.none
for (chunk <- chunks) {
val name = chunk.name
if (name != "" && !chunk_pos.isDefinedAt(name)) {
chunk_pos += (name -> make_pos(chunk.heading_length))
}
for (tok <- chunk.tokens) {
tokens += (tok.copy(source = tok.source.replace("\n", " ")) -> make_pos(tok.source.length))
advance_pos(tok)
}
}
Isabelle_System.with_tmp_dir("bibtex")(tmp_dir =>
{
File.write(tmp_dir + Path.explode("root.bib"),
tokens.iterator.map(p => p._1.source).mkString("", "\n", "\n"))
File.write(tmp_dir + Path.explode("root.aux"),
"\\bibstyle{plain}\n\\bibdata{root}\n\\citation{*}")
Isabelle_System.bash("\"$ISABELLE_BIBTEX\" root", cwd = tmp_dir.file)
val Error = """^(.*)---line (\d+) of file root.bib$""".r
val Warning = """^Warning--(.+)$""".r
val Warning_Line = """--line (\d+) of file root.bib$""".r
val Warning_in_Chunk = """^Warning--(.+) in (.+)$""".r
val log_file = tmp_dir + Path.explode("root.blg")
val lines = if (log_file.is_file) Line.logical_lines(File.read(log_file)) else Nil
val (errors, warnings) =
if (lines.isEmpty) (Nil, Nil)
else {
lines.zip(lines.tail ::: List("")).flatMap(
{
case (Error(msg, Value.Int(l)), _) =>
Some((true, (msg, get_line_pos(l))))
case (Warning_in_Chunk(msg, name), _) if chunk_pos.isDefinedAt(name) =>
Some((false, (Word.capitalize(msg + " in entry " + quote(name)), chunk_pos(name))))
case (Warning(msg), Warning_Line(Value.Int(l))) =>
Some((false, (Word.capitalize(msg), get_line_pos(l))))
case (Warning(msg), _) =>
Some((false, (Word.capitalize(msg), Position.none)))
case _ => None
}
).partition(_._1)
}
(errors.map(_._2), warnings.map(_._2))
})
}
object Check_Database extends Scala.Fun("bibtex_check_database")
{
val here = Scala_Project.here
def apply(database: String): String =
{
import XML.Encode._
YXML.string_of_body(pair(list(pair(string, properties)), list(pair(string, properties)))(
check_database(database)))
}
}
/** document model **/
/* entries */
def entries(text: String): List[Text.Info[String]] =
{
val result = new mutable.ListBuffer[Text.Info[String]]
var offset = 0
for (chunk <- Bibtex.parse(text)) {
val end_offset = offset + chunk.source.length
if (chunk.name != "" && !chunk.is_command)
result += Text.Info(Text.Range(offset, end_offset), chunk.name)
offset = end_offset
}
result.toList
}
def entries_iterator[A, B <: Document.Model](models: Map[A, B])
: Iterator[Text.Info[(String, B)]] =
{
for {
(_, model) <- models.iterator
info <- model.bibtex_entries.iterator
} yield info.map((_, model))
}
/* completion */
def completion[A, B <: Document.Model](
history: Completion.History, rendering: Rendering, caret: Text.Offset,
models: Map[A, B]): Option[Completion.Result] =
{
for {
Text.Info(r, name) <- rendering.citations(rendering.before_caret_range(caret)).headOption
name1 <- Completion.clean_name(name)
original <- rendering.get_text(r)
original1 <- Completion.clean_name(Library.perhaps_unquote(original))
entries =
(for {
Text.Info(_, (entry, _)) <- entries_iterator(models)
if entry.toLowerCase.containsSlice(name1.toLowerCase) && entry != original1
} yield entry).toList
if entries.nonEmpty
items =
entries.sorted.map({
case entry =>
val full_name = Long_Name.qualify(Markup.CITATION, entry)
val description = List(entry, "(BibTeX entry)")
val replacement = quote(entry)
Completion.Item(r, original, full_name, description, replacement, 0, false)
}).sorted(history.ordering).take(rendering.options.int("completion_limit"))
} yield Completion.Result(r, original, false, items)
}
/** content **/
private val months = List(
"jan",
"feb",
"mar",
"apr",
"may",
"jun",
"jul",
"aug",
"sep",
"oct",
"nov",
"dec")
def is_month(s: String): Boolean = months.contains(s.toLowerCase)
private val commands = List("preamble", "string")
def is_command(s: String): Boolean = commands.contains(s.toLowerCase)
sealed case class Entry(
kind: String,
required: List[String],
optional_crossref: List[String],
optional_other: List[String])
{
val optional_standard: List[String] = List("url", "doi", "ee")
def is_required(s: String): Boolean = required.contains(s.toLowerCase)
def is_optional(s: String): Boolean =
optional_crossref.contains(s.toLowerCase) ||
optional_other.contains(s.toLowerCase) ||
optional_standard.contains(s.toLowerCase)
def fields: List[String] =
required ::: optional_crossref ::: optional_other ::: optional_standard
def template: String =
"@" + kind + "{,\n" + fields.map(x => " " + x + " = {},\n").mkString + "}\n"
}
val known_entries: List[Entry] =
List(
Entry("Article",
List("author", "title"),
List("journal", "year"),
List("volume", "number", "pages", "month", "note")),
Entry("InProceedings",
List("author", "title"),
List("booktitle", "year"),
List("editor", "volume", "number", "series", "pages", "month", "address",
"organization", "publisher", "note")),
Entry("InCollection",
List("author", "title", "booktitle"),
List("publisher", "year"),
List("editor", "volume", "number", "series", "type", "chapter", "pages",
"edition", "month", "address", "note")),
Entry("InBook",
List("author", "editor", "title", "chapter"),
List("publisher", "year"),
List("volume", "number", "series", "type", "address", "edition", "month", "pages", "note")),
Entry("Proceedings",
List("title", "year"),
List(),
List("booktitle", "editor", "volume", "number", "series", "address", "month",
"organization", "publisher", "note")),
Entry("Book",
List("author", "editor", "title"),
List("publisher", "year"),
List("volume", "number", "series", "address", "edition", "month", "note")),
Entry("Booklet",
List("title"),
List(),
List("author", "howpublished", "address", "month", "year", "note")),
Entry("PhdThesis",
List("author", "title", "school", "year"),
List(),
List("type", "address", "month", "note")),
Entry("MastersThesis",
List("author", "title", "school", "year"),
List(),
List("type", "address", "month", "note")),
Entry("TechReport",
List("author", "title", "institution", "year"),
List(),
List("type", "number", "address", "month", "note")),
Entry("Manual",
List("title"),
List(),
List("author", "organization", "address", "edition", "month", "year", "note")),
Entry("Unpublished",
List("author", "title", "note"),
List(),
List("month", "year")),
Entry("Misc",
List(),
List(),
List("author", "title", "howpublished", "month", "year", "note")))
def get_entry(kind: String): Option[Entry] =
known_entries.find(entry => entry.kind.toLowerCase == kind.toLowerCase)
def is_entry(kind: String): Boolean = get_entry(kind).isDefined
/** tokens and chunks **/
object Token
{
object Kind extends Enumeration
{
val COMMAND = Value("command")
val ENTRY = Value("entry")
val KEYWORD = Value("keyword")
val NAT = Value("natural number")
val STRING = Value("string")
val NAME = Value("name")
val IDENT = Value("identifier")
val SPACE = Value("white space")
val COMMENT = Value("ignored text")
val ERROR = Value("bad input")
}
}
sealed case class Token(kind: Token.Kind.Value, source: String)
{
def is_kind: Boolean =
kind == Token.Kind.COMMAND ||
kind == Token.Kind.ENTRY ||
kind == Token.Kind.IDENT
def is_name: Boolean =
kind == Token.Kind.NAME ||
kind == Token.Kind.IDENT
def is_ignored: Boolean =
kind == Token.Kind.SPACE ||
kind == Token.Kind.COMMENT
def is_malformed: Boolean =
kind == Token.Kind.ERROR
def is_open: Boolean =
kind == Token.Kind.KEYWORD && (source == "{" || source == "(")
}
case class Chunk(kind: String, tokens: List[Token])
{
val source = tokens.map(_.source).mkString
private val content: Option[List[Token]] =
tokens match {
case Token(Token.Kind.KEYWORD, "@") :: body if body.nonEmpty =>
(body.init.filterNot(_.is_ignored), body.last) match {
case (tok :: Token(Token.Kind.KEYWORD, "{") :: toks, Token(Token.Kind.KEYWORD, "}"))
if tok.is_kind => Some(toks)
case (tok :: Token(Token.Kind.KEYWORD, "(") :: toks, Token(Token.Kind.KEYWORD, ")"))
if tok.is_kind => Some(toks)
case _ => None
}
case _ => None
}
def name: String =
content match {
case Some(tok :: _) if tok.is_name => tok.source
case _ => ""
}
def heading_length: Int =
if (name == "") 1
else (0 /: tokens.takeWhile(tok => !tok.is_open)){ case (n, tok) => n + tok.source.length }
def is_ignored: Boolean = kind == "" && tokens.forall(_.is_ignored)
def is_malformed: Boolean = kind == "" || tokens.exists(_.is_malformed)
def is_command: Boolean = Bibtex.is_command(kind) && name != "" && content.isDefined
def is_entry: Boolean = Bibtex.is_entry(kind) && name != "" && content.isDefined
}
/** parsing **/
// context of partial line-oriented scans
abstract class Line_Context
case object Ignored extends Line_Context
case object At extends Line_Context
case class Item_Start(kind: String) extends Line_Context
case class Item_Open(kind: String, end: String) extends Line_Context
case class Item(kind: String, end: String, delim: Delimited) extends Line_Context
case class Delimited(quoted: Boolean, depth: Int)
val Closed = Delimited(false, 0)
private def token(kind: Token.Kind.Value)(source: String): Token = Token(kind, source)
private def keyword(source: String): Token = Token(Token.Kind.KEYWORD, source)
// See also https://ctan.org/tex-archive/biblio/bibtex/base/bibtex.web
// module @.
object Parsers extends RegexParsers
{
/* white space and comments */
override val whiteSpace = "".r
private val space = """[ \t\n\r]+""".r ^^ token(Token.Kind.SPACE)
private val spaces = rep(space)
/* ignored text */
private val ignored: Parser[Chunk] =
rep1("""(?i)([^@]+|@[ \t]*comment)""".r) ^^ {
case ss => Chunk("", List(Token(Token.Kind.COMMENT, ss.mkString))) }
private def ignored_line: Parser[(Chunk, Line_Context)] =
ignored ^^ { case a => (a, Ignored) }
/* delimited string: outermost "..." or {...} and body with balanced {...} */
// see also bibtex.web: scan_a_field_token_and_eat_white, scan_balanced_braces
private def delimited_depth(delim: Delimited): Parser[(String, Delimited)] =
new Parser[(String, Delimited)]
{
require(if (delim.quoted) delim.depth > 0 else delim.depth >= 0,
"bad delimiter depth")
def apply(in: Input) =
{
val start = in.offset
val end = in.source.length
var i = start
var q = delim.quoted
var d = delim.depth
var finished = false
while (!finished && i < end) {
val c = in.source.charAt(i)
if (c == '"' && d == 0) { i += 1; d = 1; q = true }
else if (c == '"' && d == 1 && q) {
i += 1; d = 0; q = false; finished = true
}
else if (c == '{') { i += 1; d += 1 }
else if (c == '}') {
if (d == 1 && !q || d > 1) { i += 1; d -= 1; if (d == 0) finished = true }
else {i = start; finished = true }
}
else if (d > 0) i += 1
else finished = true
}
if (i == start) Failure("bad input", in)
else {
val s = in.source.subSequence(start, i).toString
Success((s, Delimited(q, d)), in.drop(i - start))
}
}
}.named("delimited_depth")
private def delimited: Parser[Token] =
delimited_depth(Closed) ^?
{ case (s, delim) if delim == Closed => Token(Token.Kind.STRING, s) }
private def recover_delimited: Parser[Token] =
"""["{][^@]*""".r ^^ token(Token.Kind.ERROR)
def delimited_line(ctxt: Item): Parser[(Chunk, Line_Context)] =
delimited_depth(ctxt.delim) ^^ { case (s, delim1) =>
(Chunk(ctxt.kind, List(Token(Token.Kind.STRING, s))), ctxt.copy(delim = delim1)) } |
recover_delimited ^^ { case a => (Chunk(ctxt.kind, List(a)), Ignored) }
/* other tokens */
private val at = "@" ^^ keyword
private val nat = "[0-9]+".r ^^ token(Token.Kind.NAT)
private val name = """[\x21-\x7f&&[^"#%'(),={}]]+""".r ^^ token(Token.Kind.NAME)
private val identifier =
"""[\x21-\x7f&&[^"#%'(),={}0-9]][\x21-\x7f&&[^"#%'(),={}]]*""".r
private val ident = identifier ^^ token(Token.Kind.IDENT)
val other_token = "[=#,]".r ^^ keyword | (nat | (ident | space))
/* body */
private val body =
delimited | (recover_delimited | other_token)
private def body_line(ctxt: Item) =
if (ctxt.delim.depth > 0)
delimited_line(ctxt)
else
delimited_line(ctxt) |
other_token ^^ { case a => (Chunk(ctxt.kind, List(a)), ctxt) } |
ctxt.end ^^ { case a => (Chunk(ctxt.kind, List(keyword(a))), Ignored) }
/* items: command or entry */
private val item_kind =
identifier ^^ { case a =>
val kind =
if (is_command(a)) Token.Kind.COMMAND
else if (is_entry(a)) Token.Kind.ENTRY
else Token.Kind.IDENT
Token(kind, a)
}
private val item_begin =
"{" ^^ { case a => ("}", keyword(a)) } |
"(" ^^ { case a => (")", keyword(a)) }
private def item_name(kind: String) =
kind.toLowerCase match {
case "preamble" => failure("")
case "string" => identifier ^^ token(Token.Kind.NAME)
case _ => name
}
private val item_start =
at ~ spaces ~ item_kind ~ spaces ^^
{ case a ~ b ~ c ~ d => (c.source, List(a) ::: b ::: List(c) ::: d) }
private val item: Parser[Chunk] =
(item_start ~ item_begin ~ spaces) into
{ case (kind, a) ~ ((end, b)) ~ c =>
opt(item_name(kind)) ~ rep(body) ~ opt(end ^^ keyword) ^^ {
case d ~ e ~ f => Chunk(kind, a ::: List(b) ::: c ::: d.toList ::: e ::: f.toList) } }
private val recover_item: Parser[Chunk] =
at ~ "[^@]*".r ^^ { case a ~ b => Chunk("", List(a, Token(Token.Kind.ERROR, b))) }
/* chunks */
val chunk: Parser[Chunk] = ignored | (item | recover_item)
def chunk_line(ctxt: Line_Context): Parser[(Chunk, Line_Context)] =
{
ctxt match {
case Ignored =>
ignored_line |
at ^^ { case a => (Chunk("", List(a)), At) }
case At =>
space ^^ { case a => (Chunk("", List(a)), ctxt) } |
item_kind ^^ { case a => (Chunk(a.source, List(a)), Item_Start(a.source)) } |
recover_item ^^ { case a => (a, Ignored) } |
ignored_line
case Item_Start(kind) =>
space ^^ { case a => (Chunk(kind, List(a)), ctxt) } |
item_begin ^^ { case (end, a) => (Chunk(kind, List(a)), Item_Open(kind, end)) } |
recover_item ^^ { case a => (a, Ignored) } |
ignored_line
case Item_Open(kind, end) =>
space ^^ { case a => (Chunk(kind, List(a)), ctxt) } |
item_name(kind) ^^ { case a => (Chunk(kind, List(a)), Item(kind, end, Closed)) } |
body_line(Item(kind, end, Closed)) |
ignored_line
case item_ctxt: Item =>
body_line(item_ctxt) |
ignored_line
case _ => failure("")
}
}
}
/* parse */
def parse(input: CharSequence): List[Chunk] =
Parsers.parseAll(Parsers.rep(Parsers.chunk), Scan.char_reader(input)) match {
case Parsers.Success(result, _) => result
case _ => error("Unexpected failure to parse input:\n" + input.toString)
}
def parse_line(input: CharSequence, context: Line_Context): (List[Chunk], Line_Context) =
{
var in: Reader[Char] = Scan.char_reader(input)
val chunks = new mutable.ListBuffer[Chunk]
var ctxt = context
while (!in.atEnd) {
Parsers.parse(Parsers.chunk_line(ctxt), in) match {
case Parsers.Success((x, c), rest) => chunks += x; ctxt = c; in = rest
case Parsers.NoSuccess(_, rest) =>
error("Unepected failure to parse input:\n" + rest.source.toString)
}
}
(chunks.toList, ctxt)
}
/** HTML output **/
private val output_styles =
List(
"" -> "html-n",
"plain" -> "html-n",
"alpha" -> "html-a",
"named" -> "html-n",
"paragraph" -> "html-n",
"unsort" -> "html-u",
"unsortlist" -> "html-u")
def html_output(bib: List[Path],
title: String = "Bibliography",
body: Boolean = false,
citations: List[String] = List("*"),
style: String = "",
chronological: Boolean = false): String =
{
Isabelle_System.with_tmp_dir("bibtex")(tmp_dir =>
{
/* database files */
val bib_files = bib.map(_.drop_ext)
val bib_names =
{
val names0 = bib_files.map(_.file_name)
if (Library.duplicates(names0).isEmpty) names0
else names0.zipWithIndex.map({ case (name, i) => (i + 1).toString + "-" + name })
}
for ((a, b) <- bib_files zip bib_names) {
Isabelle_System.copy_file(a.ext("bib"), tmp_dir + Path.basic(b).ext("bib"))
}
/* style file */
val bst =
output_styles.toMap.get(style) match {
case Some(base) => base + (if (chronological) "c" else "") + ".bst"
case None =>
error("Bad style for bibtex HTML output: " + quote(style) +
"\n(expected: " + commas_quote(output_styles.map(_._1)) + ")")
}
Isabelle_System.copy_file(Path.explode("$BIB2XHTML_HOME/bst") + Path.explode(bst), tmp_dir)
/* result */
val in_file = Path.explode("bib.aux")
val out_file = Path.explode("bib.html")
File.write(tmp_dir + in_file,
bib_names.mkString("\\bibdata{", ",", "}\n") +
citations.map(cite => "\\citation{" + cite + "}\n").mkString)
Isabelle_System.bash(
"\"$BIB2XHTML_HOME/main/bib2xhtml.pl\" -B \"$ISABELLE_BIBTEX\"" +
" -u -s " + Bash.string(proper_string(style) getOrElse "empty") +
(if (chronological) " -c" else "") +
(if (title != "") " -h " + Bash.string(title) + " " else "") +
" " + File.bash_path(in_file) + " " + File.bash_path(out_file),
cwd = tmp_dir.file).check
val html = File.read(tmp_dir + out_file)
if (body) {
cat_lines(
split_lines(html).
dropWhile(line => !line.startsWith("