diff --git a/src/Pure/General/position.scala b/src/Pure/General/position.scala
--- a/src/Pure/General/position.scala
+++ b/src/Pure/General/position.scala
@@ -1,167 +1,164 @@
/* Title: Pure/General/position.scala
Author: Makarius
Position properties.
*/
package isabelle
-import java.io.{File => JFile}
-
-
object Position
{
type T = Properties.T
val none: T = Nil
val Line = new Properties.Int(Markup.LINE)
val Offset = new Properties.Int(Markup.OFFSET)
val End_Offset = new Properties.Int(Markup.END_OFFSET)
val File = new Properties.String(Markup.FILE)
val Id = new Properties.Long(Markup.ID)
val Id_String = new Properties.String(Markup.ID)
val Def_Line = new Properties.Int(Markup.DEF_LINE)
val Def_Offset = new Properties.Int(Markup.DEF_OFFSET)
val Def_End_Offset = new Properties.Int(Markup.DEF_END_OFFSET)
val Def_File = new Properties.String(Markup.DEF_FILE)
val Def_Id = new Properties.Long(Markup.DEF_ID)
val Def_Theory = new Properties.String(Markup.DEF_THEORY)
object Line_File
{
def apply(line: Int, file: String): T =
(if (line > 0) Line(line) else Nil) :::
(if (file != "") File(file) else Nil)
def unapply(pos: T): Option[(Int, String)] =
(pos, pos) match {
case (Line(i), File(name)) => Some((i, name))
case (_, File(name)) => Some((1, name))
case _ => None
}
}
object Def_Line_File
{
def unapply(pos: T): Option[(Int, String)] =
(pos, pos) match {
case (Def_Line(i), Def_File(name)) => Some((i, name))
case (_, Def_File(name)) => Some((1, name))
case _ => None
}
}
object Range
{
def apply(range: Symbol.Range): T = Offset(range.start) ::: End_Offset(range.stop)
def unapply(pos: T): Option[Symbol.Range] =
(pos, pos) match {
case (Offset(start), End_Offset(stop)) if start <= stop => Some(Text.Range(start, stop))
case (Offset(start), _) => Some(Text.Range(start, start + 1))
case _ => None
}
}
object Item_Id
{
def unapply(pos: T): Option[(Long, Symbol.Range)] =
pos match {
case Id(id) =>
val offset = Offset.unapply(pos) getOrElse 0
val end_offset = End_Offset.unapply(pos) getOrElse (offset + 1)
Some((id, Text.Range(offset, end_offset)))
case _ => None
}
}
object Item_Def_Id
{
def unapply(pos: T): Option[(Long, Symbol.Range)] =
pos match {
case Def_Id(id) =>
val offset = Def_Offset.unapply(pos) getOrElse 0
val end_offset = Def_End_Offset.unapply(pos) getOrElse (offset + 1)
Some((id, Text.Range(offset, end_offset)))
case _ => None
}
}
object Item_File
{
def unapply(pos: T): Option[(String, Int, Symbol.Range)] =
pos match {
case Line_File(line, name) =>
val offset = Offset.unapply(pos) getOrElse 0
val end_offset = End_Offset.unapply(pos) getOrElse (offset + 1)
Some((name, line, Text.Range(offset, end_offset)))
case _ => None
}
}
object Item_Def_File
{
def unapply(pos: T): Option[(String, Int, Symbol.Range)] =
pos match {
case Def_Line_File(line, name) =>
val offset = Def_Offset.unapply(pos) getOrElse 0
val end_offset = Def_End_Offset.unapply(pos) getOrElse (offset + 1)
Some((name, line, Text.Range(offset, end_offset)))
case _ => None
}
}
/* here: user output */
def here(props: T, delimited: Boolean = true): String =
{
val pos = props.filter(Markup.position_property)
if (pos.isEmpty) ""
else {
val s0 =
(Line.unapply(pos), File.unapply(pos)) match {
case (Some(i), None) => "line " + i.toString
case (Some(i), Some(name)) => "line " + i.toString + " of " + quote(name)
case (None, Some(name)) => "file " + quote(name)
case _ => ""
}
val s = if (s0 == "") s0 else if (delimited) " (" + s0 + ")" else s0
Markup(Markup.POSITION, pos).markup(s)
}
}
/* JSON representation */
object JSON
{
def apply(pos: T): isabelle.JSON.Object.T =
isabelle.JSON.Object.empty ++
Line.unapply(pos).map(Line.name -> _) ++
Offset.unapply(pos).map(Offset.name -> _) ++
End_Offset.unapply(pos).map(End_Offset.name -> _) ++
File.unapply(pos).map(File.name -> _) ++
Id.unapply(pos).map(Id.name -> _)
def unapply(json: isabelle.JSON.T): Option[T] =
for {
line <- isabelle.JSON.int_default(json, Line.name)
offset <- isabelle.JSON.int_default(json, Offset.name)
end_offset <- isabelle.JSON.int_default(json, End_Offset.name)
file <- isabelle.JSON.string_default(json, File.name)
id <- isabelle.JSON.long_default(json, Id.name)
}
yield {
def defined(name: String): Boolean = isabelle.JSON.value(json, name).isDefined
(if (defined(Line.name)) Line(line) else Nil) :::
(if (defined(Offset.name)) Offset(offset) else Nil) :::
(if (defined(End_Offset.name)) End_Offset(end_offset) else Nil) :::
(if (defined(File.name)) File(file) else Nil) :::
(if (defined(Id.name)) Id(id) else Nil)
}
}
}
diff --git a/src/Pure/Thy/export_theory.scala b/src/Pure/Thy/export_theory.scala
--- a/src/Pure/Thy/export_theory.scala
+++ b/src/Pure/Thy/export_theory.scala
@@ -1,799 +1,814 @@
/* Title: Pure/Thy/export_theory.scala
Author: Makarius
Export foundational theory content.
*/
package isabelle
import scala.collection.immutable.SortedMap
object Export_Theory
{
/** session content **/
sealed case class Session(name: String, theory_graph: Graph[String, Option[Theory]])
{
override def toString: String = name
def theory(theory_name: String): Option[Theory] =
if (theory_graph.defined(theory_name)) theory_graph.get_node(theory_name)
else None
def theories: List[Theory] =
theory_graph.topological_order.flatMap(theory)
}
def read_session(
store: Sessions.Store,
sessions_structure: Sessions.Structure,
session_name: String,
progress: Progress = new Progress,
cache: Term.Cache = Term.Cache.make()): Session =
{
val thys =
sessions_structure.build_requirements(List(session_name)).flatMap(session =>
using(store.open_database(session))(db =>
{
db.transaction {
for (theory <- Export.read_theory_names(db, session))
yield {
progress.echo("Reading theory " + theory)
val provider = Export.Provider.database(db, store.cache, session, theory)
read_theory(provider, session, theory, cache = cache)
}
}
}))
val graph0 =
thys.foldLeft(Graph.string[Option[Theory]]) {
case (g, thy) => g.default_node(thy.name, Some(thy))
}
val graph1 =
thys.foldLeft(graph0) {
case (g0, thy) =>
thy.parents.foldLeft(g0) {
case (g1, parent) => g1.default_node(parent, None).add_edge_acyclic(parent, thy.name)
}
}
Session(session_name, graph1)
}
/** theory content **/
sealed case class Theory(name: String, parents: List[String],
types: List[Entity[Type]],
consts: List[Entity[Const]],
axioms: List[Entity[Axiom]],
thms: List[Entity[Thm]],
classes: List[Entity[Class]],
locales: List[Entity[Locale]],
locale_dependencies: List[Entity[Locale_Dependency]],
classrel: List[Classrel],
arities: List[Arity],
constdefs: List[Constdef],
typedefs: List[Typedef],
datatypes: List[Datatype],
spec_rules: List[Spec_Rule],
others: Map[String, List[Entity[Other]]])
{
override def toString: String = name
def entity_iterator: Iterator[Entity[No_Content]] =
types.iterator.map(_.no_content) ++
consts.iterator.map(_.no_content) ++
axioms.iterator.map(_.no_content) ++
thms.iterator.map(_.no_content) ++
classes.iterator.map(_.no_content) ++
locales.iterator.map(_.no_content) ++
locale_dependencies.iterator.map(_.no_content) ++
(for { (_, xs) <- others; x <- xs.iterator } yield x.no_content)
+ lazy val entity_by_range: Map[Symbol.Range, List[Entity[No_Content]]] =
+ entity_iterator.toList.groupBy(_.range)
+
+ lazy val entity_by_kname: Map[String, Entity[No_Content]] =
+ entity_iterator.map(entity => (entity.kname, entity)).toMap
+
lazy val entity_kinds: Set[String] =
entity_iterator.map(_.kind).toSet
def cache(cache: Term.Cache): Theory =
Theory(cache.string(name),
parents.map(cache.string),
types.map(_.cache(cache)),
consts.map(_.cache(cache)),
axioms.map(_.cache(cache)),
thms.map(_.cache(cache)),
classes.map(_.cache(cache)),
locales.map(_.cache(cache)),
locale_dependencies.map(_.cache(cache)),
classrel.map(_.cache(cache)),
arities.map(_.cache(cache)),
constdefs.map(_.cache(cache)),
typedefs.map(_.cache(cache)),
datatypes.map(_.cache(cache)),
spec_rules.map(_.cache(cache)),
(for ((k, xs) <- others.iterator) yield cache.string(k) -> xs.map(_.cache(cache))).toMap)
}
def read_theory_parents(provider: Export.Provider, theory_name: String): Option[List[String]] =
{
if (theory_name == Thy_Header.PURE) Some(Nil)
else {
provider(Export.THEORY_PREFIX + "parents")
.map(entry => split_lines(entry.uncompressed.text))
}
}
+ def no_theory: Theory =
+ Theory("", Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Map.empty)
+
def read_theory(provider: Export.Provider, session_name: String, theory_name: String,
cache: Term.Cache = Term.Cache.none): Theory =
{
val parents =
read_theory_parents(provider, theory_name) getOrElse
error("Missing theory export in session " + quote(session_name) + ": " + quote(theory_name))
val theory =
Theory(theory_name, parents,
read_types(provider),
read_consts(provider),
read_axioms(provider),
read_thms(provider),
read_classes(provider),
read_locales(provider),
read_locale_dependencies(provider),
read_classrel(provider),
read_arities(provider),
read_constdefs(provider),
read_typedefs(provider),
read_datatypes(provider),
read_spec_rules(provider),
read_others(provider))
if (cache.no_cache) theory else theory.cache(cache)
}
def read_pure[A](store: Sessions.Store, read: (Export.Provider, String, String) => A): A =
{
val session_name = Thy_Header.PURE
val theory_name = Thy_Header.PURE
using(store.open_database(session_name))(db =>
{
db.transaction {
val provider = Export.Provider.database(db, store.cache, session_name, theory_name)
read(provider, session_name, theory_name)
}
})
}
def read_pure_theory(store: Sessions.Store, cache: Term.Cache = Term.Cache.none): Theory =
read_pure(store, read_theory(_, _, _, cache = cache))
def read_pure_proof(
store: Sessions.Store, id: Thm_Id, cache: Term.Cache = Term.Cache.none): Option[Proof] =
read_pure(store, (provider, _, _) => read_proof(provider, id, cache = cache))
/* entities */
object Kind
{
val TYPE = "type"
val CONST = "const"
val THM = "thm"
val PROOF = "proof"
val LOCALE_DEPENDENCY = "locale_dependency"
val DOCUMENT_HEADING = "document_heading"
val DOCUMENT_TEXT = "document_text"
val PROOF_TEXT = "proof_text"
}
def export_kind(kind: String): String =
if (kind == Markup.TYPE_NAME) Kind.TYPE
else if (kind == Markup.CONSTANT) Kind.CONST
else kind
+ def export_kind_name(kind: String, name: String): String =
+ name + "|" + export_kind(kind)
+
abstract class Content[T]
{
def cache(cache: Term.Cache): T
}
sealed case class No_Content() extends Content[No_Content]
{
def cache(cache: Term.Cache): No_Content = this
}
sealed case class Entity[A <: Content[A]](
kind: String,
name: String,
xname: String,
pos: Position.T,
id: Option[Long],
serial: Long,
content: Option[A])
{
+ val kname: String = export_kind_name(kind, name)
+ val range: Symbol.Range = Position.Range.unapply(pos).getOrElse(Text.Range.offside)
+
def export_kind: String = Export_Theory.export_kind(kind)
override def toString: String = export_kind + " " + quote(name)
def the_content: A =
if (content.isDefined) content.get else error("No content for " + toString)
def no_content: Entity[No_Content] = copy(content = None)
def cache(cache: Term.Cache): Entity[A] =
Entity(
cache.string(kind),
cache.string(name),
cache.string(xname),
cache.position(pos),
id,
serial,
content.map(_.cache(cache)))
}
def read_entities[A <: Content[A]](
provider: Export.Provider,
export_name: String,
kind: String,
decode: XML.Decode.T[A]): List[Entity[A]] =
{
def decode_entity(tree: XML.Tree): Entity[A] =
{
def err(): Nothing = throw new XML.XML_Body(List(tree))
tree match {
case XML.Elem(Markup(Markup.ENTITY, props), body) =>
val name = Markup.Name.unapply(props) getOrElse err()
val xname = Markup.XName.unapply(props) getOrElse err()
val pos = props.filter(p => Markup.position_property(p) && p._1 != Markup.ID)
val id = Position.Id.unapply(props)
val serial = Markup.Serial.unapply(props) getOrElse err()
val content = if (body.isEmpty) None else Some(decode(body))
Entity(kind, name, xname, pos, id, serial, content)
case _ => err()
}
}
provider.uncompressed_yxml(export_name).map(decode_entity)
}
/* approximative syntax */
object Assoc extends Enumeration
{
val NO_ASSOC, LEFT_ASSOC, RIGHT_ASSOC = Value
}
sealed abstract class Syntax
case object No_Syntax extends Syntax
case class Prefix(delim: String) extends Syntax
case class Infix(assoc: Assoc.Value, delim: String, pri: Int) extends Syntax
def decode_syntax: XML.Decode.T[Syntax] =
XML.Decode.variant(List(
{ case (Nil, Nil) => No_Syntax },
{ case (List(delim), Nil) => Prefix(delim) },
{ case (Nil, body) =>
import XML.Decode._
val (ass, delim, pri) = triple(int, string, int)(body)
Infix(Assoc(ass), delim, pri) }))
/* types */
sealed case class Type(syntax: Syntax, args: List[String], abbrev: Option[Term.Typ])
extends Content[Type]
{
override def cache(cache: Term.Cache): Type =
Type(
syntax,
args.map(cache.string),
abbrev.map(cache.typ))
}
def read_types(provider: Export.Provider): List[Entity[Type]] =
read_entities(provider, Export.THEORY_PREFIX + "types", Markup.TYPE_NAME, body =>
{
import XML.Decode._
val (syntax, args, abbrev) =
triple(decode_syntax, list(string), option(Term_XML.Decode.typ))(body)
Type(syntax, args, abbrev)
})
/* consts */
sealed case class Const(
syntax: Syntax,
typargs: List[String],
typ: Term.Typ,
abbrev: Option[Term.Term],
propositional: Boolean) extends Content[Const]
{
override def cache(cache: Term.Cache): Const =
Const(
syntax,
typargs.map(cache.string),
cache.typ(typ),
abbrev.map(cache.term),
propositional)
}
def read_consts(provider: Export.Provider): List[Entity[Const]] =
read_entities(provider, Export.THEORY_PREFIX + "consts", Markup.CONSTANT, body =>
{
import XML.Decode._
val (syntax, (typargs, (typ, (abbrev, propositional)))) =
pair(decode_syntax,
pair(list(string),
pair(Term_XML.Decode.typ,
pair(option(Term_XML.Decode.term), bool))))(body)
Const(syntax, typargs, typ, abbrev, propositional)
})
/* axioms */
sealed case class Prop(
typargs: List[(String, Term.Sort)],
args: List[(String, Term.Typ)],
term: Term.Term) extends Content[Prop]
{
override def cache(cache: Term.Cache): Prop =
Prop(
typargs.map({ case (name, sort) => (cache.string(name), cache.sort(sort)) }),
args.map({ case (name, typ) => (cache.string(name), cache.typ(typ)) }),
cache.term(term))
}
def decode_prop(body: XML.Body): Prop =
{
val (typargs, args, t) =
{
import XML.Decode._
import Term_XML.Decode._
triple(list(pair(string, sort)), list(pair(string, typ)), term)(body)
}
Prop(typargs, args, t)
}
sealed case class Axiom(prop: Prop) extends Content[Axiom]
{
override def cache(cache: Term.Cache): Axiom = Axiom(prop.cache(cache))
}
def read_axioms(provider: Export.Provider): List[Entity[Axiom]] =
read_entities(provider, Export.THEORY_PREFIX + "axioms", Markup.AXIOM,
body => Axiom(decode_prop(body)))
/* theorems */
sealed case class Thm_Id(serial: Long, theory_name: String)
{
def pure: Boolean = theory_name == Thy_Header.PURE
}
sealed case class Thm(
prop: Prop,
deps: List[String],
proof: Term.Proof) extends Content[Thm]
{
override def cache(cache: Term.Cache): Thm =
Thm(
prop.cache(cache),
deps.map(cache.string),
cache.proof(proof))
}
def read_thms(provider: Export.Provider): List[Entity[Thm]] =
read_entities(provider, Export.THEORY_PREFIX + "thms", Kind.THM, body =>
{
import XML.Decode._
import Term_XML.Decode._
val (prop, deps, prf) = triple(decode_prop, list(string), proof)(body)
Thm(prop, deps, prf)
})
sealed case class Proof(
typargs: List[(String, Term.Sort)],
args: List[(String, Term.Typ)],
term: Term.Term,
proof: Term.Proof)
{
def prop: Prop = Prop(typargs, args, term)
def cache(cache: Term.Cache): Proof =
Proof(
typargs.map({ case (name, sort) => (cache.string(name), cache.sort(sort)) }),
args.map({ case (name, typ) => (cache.string(name), cache.typ(typ)) }),
cache.term(term),
cache.proof(proof))
}
def read_proof(
provider: Export.Provider, id: Thm_Id, cache: Term.Cache = Term.Cache.none): Option[Proof] =
{
for { entry <- provider.focus(id.theory_name)(Export.PROOFS_PREFIX + id.serial) }
yield {
val body = entry.uncompressed_yxml
val (typargs, (args, (prop_body, proof_body))) =
{
import XML.Decode._
import Term_XML.Decode._
pair(list(pair(string, sort)), pair(list(pair(string, typ)), pair(x => x, x => x)))(body)
}
val env = args.toMap
val prop = Term_XML.Decode.term_env(env)(prop_body)
val proof = Term_XML.Decode.proof_env(env)(proof_body)
val result = Proof(typargs, args, prop, proof)
if (cache.no_cache) result else result.cache(cache)
}
}
def read_proof_boxes(
store: Sessions.Store,
provider: Export.Provider,
proof: Term.Proof,
suppress: Thm_Id => Boolean = _ => false,
cache: Term.Cache = Term.Cache.none): List[(Thm_Id, Proof)] =
{
var seen = Set.empty[Long]
var result = SortedMap.empty[Long, (Thm_Id, Proof)]
def boxes(context: Option[(Long, Term.Proof)], prf: Term.Proof): Unit =
{
prf match {
case Term.Abst(_, _, p) => boxes(context, p)
case Term.AbsP(_, _, p) => boxes(context, p)
case Term.Appt(p, _) => boxes(context, p)
case Term.AppP(p, q) => boxes(context, p); boxes(context, q)
case thm: Term.PThm if !seen(thm.serial) =>
seen += thm.serial
val id = Thm_Id(thm.serial, thm.theory_name)
if (!suppress(id)) {
val read =
if (id.pure) Export_Theory.read_pure_proof(store, id, cache = cache)
else Export_Theory.read_proof(provider, id, cache = cache)
read match {
case Some(p) =>
result += (thm.serial -> (id -> p))
boxes(Some((thm.serial, p.proof)), p.proof)
case None =>
error("Missing proof " + thm.serial + " (theory " + quote (thm.theory_name) + ")" +
(context match {
case None => ""
case Some((i, p)) => " in proof " + i + ":\n" + p
}))
}
}
case _ =>
}
}
boxes(None, proof)
result.iterator.map(_._2).toList
}
/* type classes */
sealed case class Class(params: List[(String, Term.Typ)], axioms: List[Prop])
extends Content[Class]
{
override def cache(cache: Term.Cache): Class =
Class(
params.map({ case (name, typ) => (cache.string(name), cache.typ(typ)) }),
axioms.map(_.cache(cache)))
}
def read_classes(provider: Export.Provider): List[Entity[Class]] =
read_entities(provider, Export.THEORY_PREFIX + "classes", Markup.CLASS, body =>
{
import XML.Decode._
import Term_XML.Decode._
val (params, axioms) = pair(list(pair(string, typ)), list(decode_prop))(body)
Class(params, axioms)
})
/* locales */
sealed case class Locale(
typargs: List[(String, Term.Sort)],
args: List[((String, Term.Typ), Syntax)],
axioms: List[Prop]) extends Content[Locale]
{
override def cache(cache: Term.Cache): Locale =
Locale(
typargs.map({ case (name, sort) => (cache.string(name), cache.sort(sort)) }),
args.map({ case ((name, typ), syntax) => ((cache.string(name), cache.typ(typ)), syntax) }),
axioms.map(_.cache(cache)))
}
def read_locales(provider: Export.Provider): List[Entity[Locale]] =
read_entities(provider, Export.THEORY_PREFIX + "locales", Markup.LOCALE, body =>
{
import XML.Decode._
import Term_XML.Decode._
val (typargs, args, axioms) =
triple(list(pair(string, sort)), list(pair(pair(string, typ), decode_syntax)),
list(decode_prop))(body)
Locale(typargs, args, axioms)
})
/* locale dependencies */
sealed case class Locale_Dependency(
source: String,
target: String,
prefix: List[(String, Boolean)],
subst_types: List[((String, Term.Sort), Term.Typ)],
subst_terms: List[((String, Term.Typ), Term.Term)]) extends Content[Locale_Dependency]
{
override def cache(cache: Term.Cache): Locale_Dependency =
Locale_Dependency(
cache.string(source),
cache.string(target),
prefix.map({ case (name, mandatory) => (cache.string(name), mandatory) }),
subst_types.map({ case ((a, s), ty) => ((cache.string(a), cache.sort(s)), cache.typ(ty)) }),
subst_terms.map({ case ((x, ty), t) => ((cache.string(x), cache.typ(ty)), cache.term(t)) }))
def is_inclusion: Boolean =
subst_types.isEmpty && subst_terms.isEmpty
}
def read_locale_dependencies(provider: Export.Provider): List[Entity[Locale_Dependency]] =
read_entities(provider, Export.THEORY_PREFIX + "locale_dependencies", Kind.LOCALE_DEPENDENCY,
body =>
{
import XML.Decode._
import Term_XML.Decode._
val (source, (target, (prefix, (subst_types, subst_terms)))) =
pair(string, pair(string, pair(list(pair(string, bool)),
pair(list(pair(pair(string, sort), typ)), list(pair(pair(string, typ), term))))))(body)
Locale_Dependency(source, target, prefix, subst_types, subst_terms)
})
/* sort algebra */
sealed case class Classrel(class1: String, class2: String, prop: Prop)
{
def cache(cache: Term.Cache): Classrel =
Classrel(cache.string(class1), cache.string(class2), prop.cache(cache))
}
def read_classrel(provider: Export.Provider): List[Classrel] =
{
val body = provider.uncompressed_yxml(Export.THEORY_PREFIX + "classrel")
val classrel =
{
import XML.Decode._
list(pair(decode_prop, pair(string, string)))(body)
}
for ((prop, (c1, c2)) <- classrel) yield Classrel(c1, c2, prop)
}
sealed case class Arity(type_name: String, domain: List[Term.Sort], codomain: String, prop: Prop)
{
def cache(cache: Term.Cache): Arity =
Arity(cache.string(type_name), domain.map(cache.sort), cache.string(codomain),
prop.cache(cache))
}
def read_arities(provider: Export.Provider): List[Arity] =
{
val body = provider.uncompressed_yxml(Export.THEORY_PREFIX + "arities")
val arities =
{
import XML.Decode._
import Term_XML.Decode._
list(pair(decode_prop, triple(string, list(sort), string)))(body)
}
for ((prop, (a, b, c)) <- arities) yield Arity(a, b, c, prop)
}
/* Pure constdefs */
sealed case class Constdef(name: String, axiom_name: String)
{
def cache(cache: Term.Cache): Constdef =
Constdef(cache.string(name), cache.string(axiom_name))
}
def read_constdefs(provider: Export.Provider): List[Constdef] =
{
val body = provider.uncompressed_yxml(Export.THEORY_PREFIX + "constdefs")
val constdefs =
{
import XML.Decode._
list(pair(string, string))(body)
}
for ((name, axiom_name) <- constdefs) yield Constdef(name, axiom_name)
}
/* HOL typedefs */
sealed case class Typedef(name: String,
rep_type: Term.Typ, abs_type: Term.Typ, rep_name: String, abs_name: String, axiom_name: String)
{
def cache(cache: Term.Cache): Typedef =
Typedef(cache.string(name),
cache.typ(rep_type),
cache.typ(abs_type),
cache.string(rep_name),
cache.string(abs_name),
cache.string(axiom_name))
}
def read_typedefs(provider: Export.Provider): List[Typedef] =
{
val body = provider.uncompressed_yxml(Export.THEORY_PREFIX + "typedefs")
val typedefs =
{
import XML.Decode._
import Term_XML.Decode._
list(pair(string, pair(typ, pair(typ, pair(string, pair(string, string))))))(body)
}
for { (name, (rep_type, (abs_type, (rep_name, (abs_name, axiom_name))))) <- typedefs }
yield Typedef(name, rep_type, abs_type, rep_name, abs_name, axiom_name)
}
/* HOL datatypes */
sealed case class Datatype(
pos: Position.T,
name: String,
co: Boolean,
typargs: List[(String, Term.Sort)],
typ: Term.Typ,
constructors: List[(Term.Term, Term.Typ)])
{
def id: Option[Long] = Position.Id.unapply(pos)
def cache(cache: Term.Cache): Datatype =
Datatype(
cache.position(pos),
cache.string(name),
co,
typargs.map({ case (name, sort) => (cache.string(name), cache.sort(sort)) }),
cache.typ(typ),
constructors.map({ case (term, typ) => (cache.term(term), cache.typ(typ)) }))
}
def read_datatypes(provider: Export.Provider): List[Datatype] =
{
val body = provider.uncompressed_yxml(Export.THEORY_PREFIX + "datatypes")
val datatypes =
{
import XML.Decode._
import Term_XML.Decode._
list(pair(properties, pair(string, pair(bool, pair(list(pair(string, sort)),
pair(typ, list(pair(term, typ))))))))(body)
}
for ((pos, (name, (co, (typargs, (typ, constructors))))) <- datatypes)
yield Datatype(pos, name, co, typargs, typ, constructors)
}
/* Pure spec rules */
sealed abstract class Recursion
{
def cache(cache: Term.Cache): Recursion =
this match {
case Primrec(types) => Primrec(types.map(cache.string))
case Primcorec(types) => Primcorec(types.map(cache.string))
case _ => this
}
}
case class Primrec(types: List[String]) extends Recursion
case object Recdef extends Recursion
case class Primcorec(types: List[String]) extends Recursion
case object Corec extends Recursion
case object Unknown_Recursion extends Recursion
val decode_recursion: XML.Decode.T[Recursion] =
{
import XML.Decode._
variant(List(
{ case (Nil, a) => Primrec(list(string)(a)) },
{ case (Nil, Nil) => Recdef },
{ case (Nil, a) => Primcorec(list(string)(a)) },
{ case (Nil, Nil) => Corec },
{ case (Nil, Nil) => Unknown_Recursion }))
}
sealed abstract class Rough_Classification
{
def is_equational: Boolean = this.isInstanceOf[Equational]
def is_inductive: Boolean = this == Inductive
def is_co_inductive: Boolean = this == Co_Inductive
def is_relational: Boolean = is_inductive || is_co_inductive
def is_unknown: Boolean = this == Unknown
def cache(cache: Term.Cache): Rough_Classification =
this match {
case Equational(recursion) => Equational(recursion.cache(cache))
case _ => this
}
}
case class Equational(recursion: Recursion) extends Rough_Classification
case object Inductive extends Rough_Classification
case object Co_Inductive extends Rough_Classification
case object Unknown extends Rough_Classification
val decode_rough_classification: XML.Decode.T[Rough_Classification] =
{
import XML.Decode._
variant(List(
{ case (Nil, a) => Equational(decode_recursion(a)) },
{ case (Nil, Nil) => Inductive },
{ case (Nil, Nil) => Co_Inductive },
{ case (Nil, Nil) => Unknown }))
}
sealed case class Spec_Rule(
pos: Position.T,
name: String,
rough_classification: Rough_Classification,
typargs: List[(String, Term.Sort)],
args: List[(String, Term.Typ)],
terms: List[(Term.Term, Term.Typ)],
rules: List[Term.Term])
{
def id: Option[Long] = Position.Id.unapply(pos)
def cache(cache: Term.Cache): Spec_Rule =
Spec_Rule(
cache.position(pos),
cache.string(name),
rough_classification.cache(cache),
typargs.map({ case (name, sort) => (cache.string(name), cache.sort(sort)) }),
args.map({ case (name, typ) => (cache.string(name), cache.typ(typ)) }),
terms.map({ case (term, typ) => (cache.term(term), cache.typ(typ)) }),
rules.map(cache.term))
}
def read_spec_rules(provider: Export.Provider): List[Spec_Rule] =
{
val body = provider.uncompressed_yxml(Export.THEORY_PREFIX + "spec_rules")
val spec_rules =
{
import XML.Decode._
import Term_XML.Decode._
list(
pair(properties, pair(string, pair(decode_rough_classification,
pair(list(pair(string, sort)), pair(list(pair(string, typ)),
pair(list(pair(term, typ)), list(term))))))))(body)
}
for ((pos, (name, (rough_classification, (typargs, (args, (terms, rules)))))) <- spec_rules)
yield Spec_Rule(pos, name, rough_classification, typargs, args, terms, rules)
}
/* other entities */
sealed case class Other() extends Content[Other]
{
override def cache(cache: Term.Cache): Other = this
}
def read_others(provider: Export.Provider): Map[String, List[Entity[Other]]] =
{
val kinds =
provider(Export.THEORY_PREFIX + "other_kinds") match {
case Some(entry) => split_lines(entry.uncompressed.text)
case None => Nil
}
val other = Other()
def read_other(kind: String): List[Entity[Other]] =
read_entities(provider, Export.THEORY_PREFIX + "other/" + kind, kind, _ => other)
kinds.map(kind => kind -> read_other(kind)).toMap
}
}
diff --git a/src/Pure/Thy/presentation.scala b/src/Pure/Thy/presentation.scala
--- a/src/Pure/Thy/presentation.scala
+++ b/src/Pure/Thy/presentation.scala
@@ -1,612 +1,621 @@
/* Title: Pure/Thy/presentation.scala
Author: Makarius
HTML presentation of PIDE document content.
*/
package isabelle
import scala.annotation.tailrec
import scala.collection.immutable.SortedMap
import scala.collection.mutable
object Presentation
{
/** HTML documents **/
/* HTML context */
val fonts_path = Path.explode("fonts")
sealed case class HTML_Document(title: String, content: String)
def html_context(fonts_url: String => String = HTML.fonts_url()): HTML_Context =
new HTML_Context(fonts_url)
final class HTML_Context private[Presentation](fonts_url: String => String)
{
def init_fonts(dir: Path): Unit =
{
val fonts_dir = Isabelle_System.make_directory(dir + fonts_path)
for (entry <- Isabelle_Fonts.fonts(hidden = true))
Isabelle_System.copy_file(entry.path, fonts_dir)
}
def head(title: String, rest: XML.Body = Nil): XML.Tree =
HTML.div("head", HTML.chapter(title) :: rest)
def source(body: XML.Body): XML.Tree = HTML.pre("source", body)
def offset_ref(offset: Text.Range): String =
"offset/" + offset.start + ".." + offset.stop
def export_ref(entity: Entity): String =
Export_Theory.export_kind(entity.kind) + "/" + entity.name
def contents(heading: String, items: List[XML.Body], css_class: String = "contents")
: List[XML.Elem] =
{
if (items.isEmpty) Nil
else List(HTML.div(css_class, List(HTML.section(heading), HTML.itemize(items))))
}
def output_document(title: String, body: XML.Body): String =
HTML.output_document(
List(
HTML.style(HTML.fonts_css(fonts_url) + "\n\n" + File.read(HTML.isabelle_css)),
HTML.title(title)),
List(HTML.source(body)), css = "", structural = false)
def html_document(title: String, body: XML.Body): HTML_Document =
HTML_Document(title, output_document(title, body))
}
/* presentation elements */
sealed case class Elements(
html: Markup.Elements = Markup.Elements.empty,
entity: Markup.Elements = Markup.Elements.empty,
language: Markup.Elements = Markup.Elements.empty)
val elements1: Elements =
Elements(
html = Rendering.foreground_elements ++ Rendering.text_color_elements +
Markup.NUMERAL + Markup.COMMENT + Markup.ENTITY + Markup.LANGUAGE,
entity = Markup.Elements(Markup.THEORY, Markup.TYPE_NAME, Markup.CONSTANT, Markup.FACT,
Markup.CLASS, Markup.LOCALE, Markup.FREE))
val elements2: Elements =
Elements(
html = elements1.html ++ Rendering.markdown_elements,
language = Markup.Elements(Markup.Language.DOCUMENT))
/* formal entities */
type Entity = Export_Theory.Entity[Export_Theory.No_Content]
case class Entity_Context(node: Document.Node.Name)
{
val seen_ranges: mutable.Set[Symbol.Range] = mutable.Set.empty
}
/* HTML output */
private val div_elements =
Set(HTML.div.name, HTML.pre.name, HTML.par.name, HTML.list.name, HTML.enum.name,
HTML.descr.name)
def make_html(
name: Document.Node.Name,
elements: Elements,
entity_anchor: (Entity_Context, Symbol.Range, XML.Body) => Option[XML.Tree],
entity_link: (Entity_Context, Properties.T, XML.Body) => Option[XML.Tree],
xml: XML.Body): XML.Body =
{
val context = Entity_Context(name)
def html_div(html: XML.Body): Boolean =
html exists {
case XML.Elem(markup, body) => div_elements.contains(markup.name) || html_div(body)
case XML.Text(_) => false
}
def html_class(c: String, html: XML.Body): XML.Body =
if (c == "") html
else if (html_div(html)) List(HTML.div(c, html))
else List(HTML.span(c, html))
def html_body(xml_body: XML.Body, end_offset: Symbol.Offset): (XML.Body, Symbol.Offset) =
xml_body.foldRight((List.empty[XML.Tree], end_offset)) { case (tree, (res, end_offset1)) =>
val (res1, offset) = html_body_single(tree, end_offset1)
(res1 ++ res, offset)
}
@tailrec
def html_body_single(xml_tree: XML.Tree, end_offset: Symbol.Offset): (XML.Body, Symbol.Offset) =
xml_tree match {
case XML.Wrapped_Elem(markup, _, body) => html_body_single(XML.Elem(markup, body), end_offset)
case XML.Elem(Markup(Markup.ENTITY, props @ Markup.Kind(kind)), body) =>
val (body1, offset) = html_body(body, end_offset)
if (elements.entity(kind)) {
entity_link(context, props, body1) match {
case Some(link) => (List(link), offset)
case None => (body1, offset)
}
}
else (body1, offset)
case XML.Elem(Markup(Markup.LANGUAGE, Markup.Name(name)), body) =>
val (body1, offset) = html_body(body, end_offset)
(html_class(if (elements.language(name)) name else "", body1), offset)
case XML.Elem(Markup(Markup.MARKDOWN_PARAGRAPH, _), body) =>
val (body1, offset) = html_body(body, end_offset)
(List(HTML.par(body1)), offset)
case XML.Elem(Markup(Markup.MARKDOWN_ITEM, _), body) =>
val (body1, offset) = html_body(body, end_offset)
(List(HTML.item(body1)), offset)
case XML.Elem(Markup(Markup.Markdown_Bullet.name, _), text) =>
(Nil, end_offset - XML.symbol_length(text))
case XML.Elem(Markup.Markdown_List(kind), body) =>
val (body1, offset) = html_body(body, end_offset)
if (kind == Markup.ENUMERATE) (List(HTML.enum(body1)), offset)
else (List(HTML.list(body1)), offset)
case XML.Elem(markup, body) =>
val name = markup.name
val (body1, offset) = html_body(body, end_offset)
val html =
markup.properties match {
case Markup.Kind(kind) if kind == Markup.COMMAND || kind == Markup.KEYWORD =>
html_class(kind, body1)
case _ =>
body1
}
Rendering.foreground.get(name) orElse Rendering.text_color.get(name) match {
case Some(c) => (html_class(c.toString, html), offset)
case None => (html_class(name, html), offset)
}
case XML.Text(text) =>
val offset = end_offset - Symbol.length(text)
val body = HTML.text(Symbol.decode(text))
entity_anchor(context, Text.Range(offset, end_offset), body) match {
case Some(body1) => (List(body1), offset)
case None => (body, offset)
}
}
html_body(xml, XML.symbol_length(xml) + 1)._1
}
/* PIDE HTML document */
def html_document(
resources: Resources,
snapshot: Document.Snapshot,
html_context: HTML_Context,
elements: Elements,
plain_text: Boolean = false): HTML_Document =
{
require(!snapshot.is_outdated, "document snapshot outdated")
val name = snapshot.node_name
if (plain_text) {
val title = "File " + Symbol.cartouche_decoded(name.path.file_name)
val body = HTML.text(snapshot.node.source)
html_context.html_document(title, body)
}
else {
resources.html_document(snapshot) getOrElse {
val title =
if (name.is_theory) "Theory " + quote(name.theory_base_name)
else "File " + Symbol.cartouche_decoded(name.path.file_name)
val xml = snapshot.xml_markup(elements = elements.html)
val body = make_html(name, elements, (_, _, _) => None, (_, _, _) => None, xml)
html_context.html_document(title, body)
}
}
}
/** HTML presentation **/
/* presentation context */
object Context
{
val none: Context = new Context { def enabled: Boolean = false }
val standard: Context = new Context { def enabled: Boolean = true }
def dir(path: Path): Context =
new Context {
def enabled: Boolean = true
override def dir(store: Sessions.Store): Path = path
}
def make(s: String): Context =
if (s == ":") standard else dir(Path.explode(s))
}
abstract class Context private
{
def enabled: Boolean
def enabled(info: Sessions.Info): Boolean = enabled || info.browser_info
def dir(store: Sessions.Store): Path = store.presentation_dir
def dir(store: Sessions.Store, info: Sessions.Info): Path =
dir(store) + Path.explode(info.chapter_session)
}
/* maintain chapter index */
private val sessions_path = Path.basic(".sessions")
private def read_sessions(dir: Path): List[(String, String)] =
{
val path = dir + sessions_path
if (path.is_file) {
import XML.Decode._
list(pair(string, string))(Symbol.decode_yxml(File.read(path)))
}
else Nil
}
private def write_sessions(dir: Path, sessions: List[(String, String)]): Unit =
{
import XML.Encode._
File.write(dir + sessions_path, YXML.string_of_body(list(pair(string, string))(sessions)))
}
def update_chapter_index(
browser_info: Path, chapter: String, new_sessions: List[(String, String)]): Unit =
{
val dir = Isabelle_System.make_directory(browser_info + Path.basic(chapter))
val sessions0 =
try { read_sessions(dir) }
catch { case _: XML.Error => Nil }
val sessions = (SortedMap.empty[String, String] ++ sessions0 ++ new_sessions).toList
write_sessions(dir, sessions)
val title = "Isabelle/" + chapter + " sessions"
HTML.write_document(dir, "index.html",
List(HTML.title(title + Isabelle_System.isabelle_heading())),
HTML.chapter(title) ::
(if (sessions.isEmpty) Nil
else
List(HTML.div("sessions",
List(HTML.description(
sessions.map({ case (name, description) =>
val descr = Symbol.trim_blank_lines(description)
(List(HTML.link(name + "/index.html", HTML.text(name))),
if (descr == "") Nil
else HTML.break ::: List(HTML.pre(HTML.text(descr)))) })))))))
}
def make_global_index(browser_info: Path): Unit =
{
if (!(browser_info + Path.explode("index.html")).is_file) {
Isabelle_System.make_directory(browser_info)
Isabelle_System.copy_file(Path.explode("~~/lib/logo/isabelle.gif"),
browser_info + Path.explode("isabelle.gif"))
val title = "The " + XML.text(Isabelle_System.isabelle_name()) + " Library"
File.write(browser_info + Path.explode("index.html"),
HTML.header +
"""
""" + HTML.head_meta + """
""" + title + """
""" + File.read(Path.explode("~~/lib/html/library_index_content.template")) +
"""
""" + HTML.footer)
}
}
- /* cache */
+ /* theory cache */
- class Entity_Cache private(cache: mutable.Map[Document.Node.Name, Seq[Entity]])
+ object Theory_Cache
{
- def get_or_update(node: Document.Node.Name, e: => Seq[Entity]): Seq[Entity] =
- cache.getOrElseUpdate(node, e)
+ def apply(): Theory_Cache = new Theory_Cache()
}
- object Entity_Cache
+
+ class Theory_Cache private()
{
- def empty: Entity_Cache = new Entity_Cache(mutable.Map.empty)
+ private val cache = Synchronized(Map.empty[String, Export_Theory.Theory])
+
+ def apply(thy_name: String, make_thy: => Export_Theory.Theory): Export_Theory.Theory =
+ {
+ cache.change_result(thys =>
+ {
+ thys.get(thy_name) match {
+ case Some(thy) => (thy, thys)
+ case None =>
+ val thy = make_thy
+ (thy, thys + (thy_name -> thy))
+ }
+ })
+ }
}
+
/* present session */
val session_graph_path = Path.explode("session_graph.pdf")
val readme_path = Path.explode("README.html")
val files_path = Path.explode("files")
def html_name(name: Document.Node.Name): String = Path.explode(name.theory_base_name).html.implode
def html_path(path: Path): String = (files_path + path.squash.html).implode
def session_relative(deps: Sessions.Deps, session0: String, session1: String): Option[String] =
{
for {
info0 <- deps.sessions_structure.get(session0)
info1 <- deps.sessions_structure.get(session1)
} yield info0.relative_path(info1)
}
def node_relative(
deps: Sessions.Deps,
session0: String,
node_name: Document.Node.Name): Option[String] =
{
val session1 = deps(session0).theory_qualifier(node_name)
session_relative(deps, session0, session1)
}
def theory_link(deps: Sessions.Deps, session0: String,
name: Document.Node.Name, body: XML.Body, anchor: Option[String] = None): Option[XML.Tree] =
{
val session1 = deps(session0).theory_qualifier(name)
val info0 = deps.sessions_structure.get(session0)
val info1 = deps.sessions_structure.get(session1)
val fragment = if (anchor.isDefined) "#" + anchor.get else ""
if (info0.isDefined && info1.isDefined) {
Some(HTML.link(info0.get.relative_path(info1.get) + html_name(name) + fragment, body))
}
else None
}
def session_html(
resources: Resources,
session: String,
deps: Sessions.Deps,
db_context: Sessions.Database_Context,
progress: Progress = new Progress,
verbose: Boolean = false,
html_context: HTML_Context,
elements: Elements,
presentation: Context,
- seen_nodes_cache: Entity_Cache = Entity_Cache.empty): Unit =
+ theory_cache: Theory_Cache = Theory_Cache()): Unit =
{
val info = deps.sessions_structure(session)
val options = info.options
val base = deps(session)
val session_dir = presentation.dir(db_context.store, info)
html_context.init_fonts(session_dir)
Bytes.write(session_dir + session_graph_path,
graphview.Graph_File.make_pdf(options, base.session_graph_display))
val documents =
for {
doc <- info.document_variants
document <- db_context.input_database(session)(Document_Build.read_document(_, _, doc.name))
} yield {
if (verbose) progress.echo("Presenting document " + session + "/" + doc.name)
Bytes.write(session_dir + doc.path.pdf, document.pdf)
doc
}
val view_links =
{
val deps_link =
HTML.link(session_graph_path, HTML.text("theory dependencies"))
val readme_links =
if ((info.dir + readme_path).is_file) {
Isabelle_System.copy_file(info.dir + readme_path, session_dir + readme_path)
List(HTML.link(readme_path, HTML.text("README")))
}
else Nil
val document_links =
documents.map(doc => HTML.link(doc.path.pdf, HTML.text(doc.name)))
Library.separate(HTML.break ::: HTML.nl,
(deps_link :: readme_links ::: document_links).
map(link => HTML.text("View ") ::: List(link))).flatten
}
- def read_exports(name: Document.Node.Name): Seq[Entity] =
- {
- seen_nodes_cache.get_or_update(name, {
- if (Sessions.is_pure(name.theory_base_name)) Vector.empty
- else {
- val session1 = deps(session).theory_qualifier(name)
- val provider = Export.Provider.database_context(db_context, List(session1), name.theory)
- if (Export_Theory.read_theory_parents(provider, name.theory).isDefined) {
- val theory = Export_Theory.read_theory(provider, session1, name.theory)
- theory.entity_iterator.toVector
- }
- else {
- progress.echo_warning("No theory exports for " + name)
- Vector.empty
- }
- }
- })
- }
+ def read_theory(thy_name: String): Export_Theory.Theory =
+ if (thy_name == Thy_Header.PURE) Export_Theory.no_theory
+ else {
+ theory_cache(thy_name,
+ {
+ val qualifier = deps(session).theory_qualifier(thy_name)
+ val provider = Export.Provider.database_context(db_context, List(qualifier), thy_name)
+ if (Export_Theory.read_theory_parents(provider, thy_name).isDefined) {
+ Export_Theory.read_theory(provider, qualifier, thy_name)
+ }
+ else {
+ progress.echo_warning("No theory exports for " + quote(thy_name))
+ Export_Theory.no_theory
+ }
+ })
+ }
- val exports = base.known_theories.map(_._2.name).map(node => node -> read_exports(node)).toMap
- val export_ranges =
- exports.view.mapValues(_.groupBy(entity =>
- Text.Range(Position.Offset.get(entity.pos), Position.End_Offset.get(entity.pos)))).toMap
- val export_names =
- exports.map {
- case (node, entities) => node.theory -> entities.map(entity => entity.name -> entity).toMap
- }
+ val theory_by_name: Map[String, Export_Theory.Theory] =
+ base.known_theories.map(_._2.name.theory).map(name => name -> read_theory(name)).toMap
val theories: List[XML.Body] =
{
var seen_files = List.empty[(Path, String, Document.Node.Name)]
def node_file(node: Document.Node.Name): String =
if (node.node.endsWith(".thy")) html_name(node) else html_path(Path.explode(node.node))
def entity_anchor(
context: Entity_Context, range: Symbol.Range, body: XML.Body): Option[XML.Elem] =
{
body match {
case List(XML.Elem(Markup("span", List("id" -> _)), _)) => None
case _ =>
Some {
val body1 =
if (context.seen_ranges.contains(range)) {
HTML.entity_def(HTML.span(HTML.id(html_context.offset_ref(range)), body))
}
else HTML.span(body)
- export_ranges.get(context.node).flatMap(_.get(range)) match {
+ theory_by_name.get(context.node.theory).flatMap(_.entity_by_range.get(range)) match {
case Some(entities) =>
entities.map(html_context.export_ref).foldLeft(body1) {
- case (elem, id) =>
- HTML.entity_def(HTML.span(HTML.id(id), List(elem)))
+ case (elem, id) => HTML.entity_def(HTML.span(HTML.id(id), List(elem)))
}
case None => body1
}
}
}
}
- def entity_ref(theory: String, name: String): Option[String] =
- export_names.get(theory).flatMap(_.get(name)).map(html_context.export_ref)
+ def entity_ref(thy_name: String, kind: String, name: String): Option[String] =
+ for {
+ thy <- theory_by_name.get(thy_name)
+ entity <- thy.entity_by_kname.get(Export_Theory.export_kind_name(kind, name))
+ } yield html_context.export_ref(entity)
- def offset_ref(context: Entity_Context, theory: String, props: Properties.T): Option[String] =
+ def offset_ref(context: Entity_Context, thy_name: String, props: Properties.T): Option[String] =
{
- if (theory == context.node.theory) {
+ if (thy_name == context.node.theory) {
for {
offset <- Position.Def_Offset.unapply(props)
end_offset <- Position.Def_End_Offset.unapply(props)
range = Text.Range(offset, end_offset)
} yield {
context.seen_ranges += range
html_context.offset_ref(range)
}
- } else None
+ }
+ else None
}
def entity_link(
context: Entity_Context, props: Properties.T, body: XML.Body): Option[XML.Elem] =
{
- (props, props, props, props) match {
- case (Markup.Kind(Markup.THEORY), Markup.Name(theory), Position.Def_File(thy_file), _) =>
+ (props, props, props, props, props) match {
+ case (Markup.Kind(Markup.THEORY), Markup.Name(theory), Position.Def_File(thy_file), _, _) =>
val node_name = resources.file_node(Path.explode(thy_file), theory = theory)
node_relative(deps, session, node_name).map(html_dir =>
HTML.link(html_dir + html_name(node_name), body))
case (Markup.Ref(_), Position.Def_File(thy_file), Position.Def_Theory(def_theory),
- Markup.Name(name)) =>
- val theory = if (def_theory.nonEmpty) def_theory else context.node.theory
- val node_name = resources.file_node(Path.explode(thy_file), theory = theory)
+ Markup.Kind(kind), Markup.Name(name)) =>
+ val thy_name = if (def_theory.nonEmpty) def_theory else context.node.theory
+ val node_name = resources.file_node(Path.explode(thy_file), theory = thy_name)
for {
html_dir <- node_relative(deps, session, node_name)
html_file = node_file(node_name)
- html_ref <- entity_ref(theory, name).orElse(offset_ref(context, theory, props))
+ html_ref <- entity_ref(thy_name, kind, name).orElse(offset_ref(context, thy_name, props))
} yield {
HTML.entity_ref(HTML.link(html_dir + html_file + "#" + html_ref, body))
}
case _ => None
}
}
sealed case class Theory(
name: Document.Node.Name,
command: Command,
files_html: List[(Path, XML.Tree)],
html: XML.Tree)
def read_theory(name: Document.Node.Name): Option[Theory] =
{
progress.expose_interrupt()
if (verbose) progress.echo("Presenting theory " + name)
for (command <- Build_Job.read_theory(db_context, resources, session, name.theory))
yield {
val snapshot = Document.State.init.snippet(command)
val files_html =
for {
(src_path, xml) <- snapshot.xml_markup_blobs(elements = elements.html)
if xml.nonEmpty
}
yield {
progress.expose_interrupt()
if (verbose) progress.echo("Presenting file " + src_path)
(src_path, html_context.source(
make_html(name, elements, entity_anchor, entity_link, xml)))
}
val html =
html_context.source(
make_html(name, elements, entity_anchor, entity_link,
snapshot.xml_markup(elements = elements.html)))
Theory(name, command, files_html, html)
}
}
for (thy <- Par_List.map(read_theory, base.session_theories).flatten)
yield {
val files =
for { (src_path, file_html) <- thy.files_html }
yield {
val file_name = html_path(src_path)
seen_files.find(p => p._1 == src_path || p._2 == file_name) match {
case None => seen_files ::= (src_path, file_name, thy.name)
case Some((_, _, thy_name1)) =>
error("Incoherent use of file name " + src_path + " as " + quote(file_name) +
" in theory " + thy_name1 + " vs. " + thy.name)
}
val file_path = session_dir + Path.explode(file_name)
html_context.init_fonts(file_path.dir)
val file_title = "File " + Symbol.cartouche_decoded(src_path.implode_short)
HTML.write_document(file_path.dir, file_path.file_name,
List(HTML.title(file_title)), List(html_context.head(file_title), file_html))
List(HTML.link(file_name, HTML.text(file_title)))
}
val thy_title = "Theory " + thy.name.theory_base_name
HTML.write_document(session_dir, html_name(thy.name),
List(HTML.title(thy_title)), List(html_context.head(thy_title), thy.html))
List(HTML.link(html_name(thy.name),
HTML.text(thy.name.theory_base_name) :::
(if (files.isEmpty) Nil else List(HTML.itemize(files)))))
}
}
val title = "Session " + session
HTML.write_document(session_dir, "index.html",
List(HTML.title(title + Isabelle_System.isabelle_heading())),
html_context.head(title, List(HTML.par(view_links))) ::
html_context.contents("Theories", theories))
}
}
diff --git a/src/Pure/Tools/build.scala b/src/Pure/Tools/build.scala
--- a/src/Pure/Tools/build.scala
+++ b/src/Pure/Tools/build.scala
@@ -1,689 +1,689 @@
/* Title: Pure/Tools/build.scala
Author: Makarius
Options: :folding=explicit:
Build and manage Isabelle sessions.
*/
package isabelle
import scala.collection.immutable.SortedSet
import scala.annotation.tailrec
object Build
{
/** auxiliary **/
/* persistent build info */
sealed case class Session_Info(
sources: String,
input_heaps: List[String],
output_heap: Option[String],
return_code: Int)
{
def ok: Boolean = return_code == 0
}
/* queue with scheduling information */
private object Queue
{
type Timings = (List[Properties.T], Double)
def load_timings(progress: Progress, store: Sessions.Store, session_name: String): Timings =
{
val no_timings: Timings = (Nil, 0.0)
store.try_open_database(session_name) match {
case None => no_timings
case Some(db) =>
def ignore_error(msg: String) =
{
progress.echo_warning("Ignoring bad database " + db + (if (msg == "") "" else "\n" + msg))
no_timings
}
try {
val command_timings = store.read_command_timings(db, session_name)
val session_timing =
store.read_session_timing(db, session_name) match {
case Markup.Elapsed(t) => t
case _ => 0.0
}
(command_timings, session_timing)
}
catch {
case ERROR(msg) => ignore_error(msg)
case exn: java.lang.Error => ignore_error(Exn.message(exn))
case _: XML.Error => ignore_error("")
}
finally { db.close() }
}
}
def make_session_timing(sessions_structure: Sessions.Structure, timing: Map[String, Double])
: Map[String, Double] =
{
val maximals = sessions_structure.build_graph.maximals.toSet
def desc_timing(session_name: String): Double =
{
if (maximals.contains(session_name)) timing(session_name)
else {
val descendants = sessions_structure.build_descendants(List(session_name)).toSet
val g = sessions_structure.build_graph.restrict(descendants)
(0.0 :: g.maximals.flatMap(desc => {
val ps = g.all_preds(List(desc))
if (ps.exists(p => !timing.isDefinedAt(p))) None
else Some(ps.map(timing(_)).sum)
})).max
}
}
timing.keySet.iterator.map(name => (name -> desc_timing(name))).toMap.withDefaultValue(0.0)
}
def apply(progress: Progress, sessions_structure: Sessions.Structure, store: Sessions.Store)
: Queue =
{
val graph = sessions_structure.build_graph
val names = graph.keys
val timings = names.map(name => (name, load_timings(progress, store, name)))
val command_timings =
timings.map({ case (name, (ts, _)) => (name, ts) }).toMap.withDefaultValue(Nil)
val session_timing =
make_session_timing(sessions_structure,
timings.map({ case (name, (_, t)) => (name, t) }).toMap)
object Ordering extends scala.math.Ordering[String]
{
def compare(name1: String, name2: String): Int =
session_timing(name2) compare session_timing(name1) match {
case 0 =>
sessions_structure(name2).timeout compare sessions_structure(name1).timeout match {
case 0 => name1 compare name2
case ord => ord
}
case ord => ord
}
}
new Queue(graph, SortedSet(names: _*)(Ordering), command_timings)
}
}
private class Queue(
graph: Graph[String, Sessions.Info],
order: SortedSet[String],
val command_timings: String => List[Properties.T])
{
def is_inner(name: String): Boolean = !graph.is_maximal(name)
def is_empty: Boolean = graph.is_empty
def - (name: String): Queue =
new Queue(graph.del_node(name), order - name, command_timings)
def dequeue(skip: String => Boolean): Option[(String, Sessions.Info)] =
{
val it = order.iterator.dropWhile(name => skip(name) || !graph.is_minimal(name))
if (it.hasNext) { val name = it.next(); Some((name, graph.get_node(name))) }
else None
}
}
/** build with results **/
class Results private[Build](results: Map[String, (Option[Process_Result], Sessions.Info)])
{
def sessions: Set[String] = results.keySet
def infos: List[Sessions.Info] = results.values.map(_._2).toList
def cancelled(name: String): Boolean = results(name)._1.isEmpty
def apply(name: String): Process_Result = results(name)._1.getOrElse(Process_Result(1))
def info(name: String): Sessions.Info = results(name)._2
val rc: Int =
results.iterator.map({ case (_, (Some(r), _)) => r.rc case (_, (None, _)) => 1 }).
foldLeft(Process_Result.RC.ok)(_ max _)
def ok: Boolean = rc == Process_Result.RC.ok
override def toString: String = rc.toString
}
def session_finished(session_name: String, process_result: Process_Result): String =
"Finished " + session_name + " (" + process_result.timing.message_resources + ")"
def session_timing(session_name: String, build_log: Build_Log.Session_Info): String =
{
val props = build_log.session_timing
val threads = Markup.Session_Timing.Threads.unapply(props) getOrElse 1
val timing = Markup.Timing_Properties.parse(props)
"Timing " + session_name + " (" + threads + " threads, " + timing.message_factor + ")"
}
def build(
options: Options,
selection: Sessions.Selection = Sessions.Selection.empty,
presentation: Presentation.Context = Presentation.Context.none,
progress: Progress = new Progress,
check_unknown_files: Boolean = false,
build_heap: Boolean = false,
clean_build: Boolean = false,
dirs: List[Path] = Nil,
select_dirs: List[Path] = Nil,
infos: List[Sessions.Info] = Nil,
numa_shuffling: Boolean = false,
max_jobs: Int = 1,
list_files: Boolean = false,
check_keywords: Set[String] = Set.empty,
fresh_build: Boolean = false,
no_build: Boolean = false,
soft_build: Boolean = false,
verbose: Boolean = false,
export_files: Boolean = false,
session_setup: (String, Session) => Unit = (_, _) => ()): Results =
{
val build_options =
options +
"completion_limit=0" +
"editor_tracing_messages=0" +
"kodkod_scala=false" +
("pide_reports=" + options.bool("build_pide_reports"))
val store = Sessions.store(build_options)
Isabelle_Fonts.init()
/* session selection and dependencies */
val full_sessions =
Sessions.load_structure(build_options, dirs = dirs, select_dirs = select_dirs, infos = infos)
val full_sessions_selection = full_sessions.imports_selection(selection)
def sources_stamp(deps: Sessions.Deps, session_name: String): String =
{
val digests =
full_sessions(session_name).meta_digest ::
deps.sources(session_name) :::
deps.imported_sources(session_name)
SHA1.digest_set(digests).toString
}
val deps =
{
val deps0 =
Sessions.deps(full_sessions.selection(selection),
progress = progress, inlined_files = true, verbose = verbose,
list_files = list_files, check_keywords = check_keywords).check_errors
if (soft_build && !fresh_build) {
val outdated =
deps0.sessions_structure.build_topological_order.flatMap(name =>
store.try_open_database(name) match {
case Some(db) =>
using(db)(store.read_build(_, name)) match {
case Some(build)
if build.ok && build.sources == sources_stamp(deps0, name) => None
case _ => Some(name)
}
case None => Some(name)
})
Sessions.deps(full_sessions.selection(Sessions.Selection(sessions = outdated)),
progress = progress, inlined_files = true).check_errors
}
else deps0
}
/* check unknown files */
if (check_unknown_files) {
val source_files =
(for {
(_, base) <- deps.session_bases.iterator
(path, _) <- base.sources.iterator
} yield path).toList
val exclude_files = List(Path.explode("$POLYML_EXE")).map(_.canonical_file)
val unknown_files =
Mercurial.check_files(source_files)._2.
filterNot(path => exclude_files.contains(path.canonical_file))
if (unknown_files.nonEmpty) {
progress.echo_warning("Unknown files (not part of the underlying Mercurial repository):" +
unknown_files.map(path => path.expand.implode).sorted.mkString("\n ", "\n ", ""))
}
}
/* main build process */
val queue = Queue(progress, deps.sessions_structure, store)
store.prepare_output_dir()
if (clean_build) {
for (name <- full_sessions.imports_descendants(full_sessions_selection)) {
val (relevant, ok) = store.clean_output(name)
if (relevant) {
if (ok) progress.echo("Cleaned " + name)
else progress.echo(name + " FAILED to clean")
}
}
}
// scheduler loop
case class Result(
current: Boolean,
heap_digest: Option[String],
process: Option[Process_Result],
info: Sessions.Info)
{
def ok: Boolean =
process match {
case None => false
case Some(res) => res.ok
}
}
def sleep(): Unit =
Isabelle_Thread.interrupt_handler(_ => progress.stop()) { Time.seconds(0.5).sleep() }
val log =
build_options.string("system_log") match {
case "" => No_Logger
case "true" => Logger.make(progress)
case log_file => Logger.make(Some(Path.explode(log_file)))
}
val numa_nodes = new NUMA.Nodes(numa_shuffling)
@tailrec def loop(
pending: Queue,
running: Map[String, (List[String], Build_Job)],
results: Map[String, Result]): Map[String, Result] =
{
def used_node(i: Int): Boolean =
running.iterator.exists(
{ case (_, (_, job)) => job.numa_node.isDefined && job.numa_node.get == i })
if (pending.is_empty) results
else {
if (progress.stopped) {
for ((_, (_, job)) <- running) job.terminate()
}
running.find({ case (_, (_, job)) => job.is_finished }) match {
case Some((session_name, (input_heaps, job))) =>
//{{{ finish job
val (process_result, heap_digest) = job.join
val log_lines = process_result.out_lines.filterNot(Protocol_Message.Marker.test)
val process_result_tail =
{
val tail = job.info.options.int("process_output_tail")
process_result.copy(
out_lines =
"(see also " + store.output_log(session_name).file.toString + ")" ::
(if (tail == 0) log_lines else log_lines.drop(log_lines.length - tail max 0)))
}
val build_log =
Build_Log.Log_File(session_name, process_result.out_lines).
parse_session_info(
command_timings = true,
theory_timings = true,
ml_statistics = true,
task_statistics = true)
// write log file
if (process_result.ok) {
File.write_gzip(store.output_log_gz(session_name), terminate_lines(log_lines))
}
else File.write(store.output_log(session_name), terminate_lines(log_lines))
// write database
using(store.open_database(session_name, output = true))(db =>
store.write_session_info(db, session_name,
build_log =
if (process_result.timeout) build_log.error("Timeout") else build_log,
build =
Session_Info(sources_stamp(deps, session_name), input_heaps, heap_digest,
process_result.rc)))
// messages
process_result.err_lines.foreach(progress.echo)
if (process_result.ok) {
if (verbose) progress.echo(session_timing(session_name, build_log))
progress.echo(session_finished(session_name, process_result))
}
else {
progress.echo(session_name + " FAILED")
if (!process_result.interrupted) progress.echo(process_result_tail.out)
}
loop(pending - session_name, running - session_name,
results +
(session_name -> Result(false, heap_digest, Some(process_result_tail), job.info)))
//}}}
case None if running.size < (max_jobs max 1) =>
//{{{ check/start next job
pending.dequeue(running.isDefinedAt) match {
case Some((session_name, info)) =>
val ancestor_results =
deps.sessions_structure.build_requirements(List(session_name)).
filterNot(_ == session_name).map(results(_))
val ancestor_heaps = ancestor_results.flatMap(_.heap_digest)
val do_store =
build_heap || Sessions.is_pure(session_name) || queue.is_inner(session_name)
val (current, heap_digest) =
{
store.try_open_database(session_name) match {
case Some(db) =>
using(db)(store.read_build(_, session_name)) match {
case Some(build) =>
val heap_digest = store.find_heap_digest(session_name)
val current =
!fresh_build &&
build.ok &&
build.sources == sources_stamp(deps, session_name) &&
build.input_heaps == ancestor_heaps &&
build.output_heap == heap_digest &&
!(do_store && heap_digest.isEmpty)
(current, heap_digest)
case None => (false, None)
}
case None => (false, None)
}
}
val all_current = current && ancestor_results.forall(_.current)
if (all_current)
loop(pending - session_name, running,
results +
(session_name -> Result(true, heap_digest, Some(Process_Result(0)), info)))
else if (no_build) {
progress.echo_if(verbose, "Skipping " + session_name + " ...")
loop(pending - session_name, running,
results +
(session_name -> Result(false, heap_digest, Some(Process_Result(1)), info)))
}
else if (ancestor_results.forall(_.ok) && !progress.stopped) {
progress.echo((if (do_store) "Building " else "Running ") + session_name + " ...")
store.clean_output(session_name)
using(store.open_database(session_name, output = true))(
store.init_session_info(_, session_name))
val numa_node = numa_nodes.next(used_node)
val job =
new Build_Job(progress, session_name, info, deps, store, do_store,
log, session_setup, numa_node, queue.command_timings(session_name))
loop(pending, running + (session_name -> (ancestor_heaps, job)), results)
}
else {
progress.echo(session_name + " CANCELLED")
loop(pending - session_name, running,
results + (session_name -> Result(false, heap_digest, None, info)))
}
case None => sleep(); loop(pending, running, results)
}
///}}}
case None => sleep(); loop(pending, running, results)
}
}
}
/* build results */
val results =
{
val results0 =
if (deps.is_empty) {
progress.echo_warning("Nothing to build")
Map.empty[String, Result]
}
else Isabelle_Thread.uninterruptible { loop(queue, Map.empty, Map.empty) }
new Results(
(for ((name, result) <- results0.iterator)
yield (name, (result.process, result.info))).toMap)
}
if (export_files) {
for (name <- full_sessions.imports_selection(selection).iterator if results(name).ok) {
val info = results.info(name)
if (info.export_files.nonEmpty) {
progress.echo("Exporting " + info.name + " ...")
for ((dir, prune, pats) <- info.export_files) {
Export.export_files(store, name, info.dir + dir,
progress = if (verbose) progress else new Progress,
export_prune = prune,
export_patterns = pats)
}
}
}
}
if (results.rc != Process_Result.RC.ok && (verbose || !no_build)) {
val unfinished =
(for {
name <- results.sessions.iterator
if !results(name).ok
} yield name).toList.sorted
progress.echo("Unfinished session(s): " + commas(unfinished))
}
/* PDF/HTML presentation */
if (!no_build && !progress.stopped && results.ok) {
val selected = full_sessions_selection.toSet
val presentation_chapters =
(for {
session_name <- deps.sessions_structure.build_topological_order.iterator
info = results.info(session_name)
if selected(session_name) && presentation.enabled(info) && results(session_name).ok }
yield (info.chapter, (session_name, info.description))).toList
if (presentation_chapters.nonEmpty) {
val presentation_dir = presentation.dir(store)
progress.echo("Presentation in " + presentation_dir.absolute)
val resources = Resources.empty
val html_context = Presentation.html_context()
- val seen_nodes_cache = Presentation.Entity_Cache.empty
+ val theory_cache = Presentation.Theory_Cache()
using(store.open_database_context())(db_context =>
for ((_, (session_name, _)) <- presentation_chapters) {
progress.expose_interrupt()
progress.echo("Presenting " + session_name + " ...")
Presentation.session_html(
resources, session_name, deps, db_context, progress = progress,
verbose = verbose, html_context = html_context,
elements = Presentation.elements1, presentation = presentation,
- seen_nodes_cache = seen_nodes_cache)
+ theory_cache = theory_cache)
})
val browser_chapters =
presentation_chapters.groupBy(_._1).
map({ case (chapter, es) => (chapter, es.map(_._2)) }).filterNot(_._2.isEmpty)
for ((chapter, entries) <- browser_chapters)
Presentation.update_chapter_index(presentation_dir, chapter, entries)
if (browser_chapters.nonEmpty) Presentation.make_global_index(presentation_dir)
}
}
results
}
/* Isabelle tool wrapper */
val isabelle_tool = Isabelle_Tool("build", "build and manage Isabelle sessions",
Scala_Project.here, args =>
{
val build_options = Word.explode(Isabelle_System.getenv("ISABELLE_BUILD_OPTIONS"))
var base_sessions: List[String] = Nil
var select_dirs: List[Path] = Nil
var numa_shuffling = false
var presentation = Presentation.Context.none
var requirements = false
var soft_build = false
var exclude_session_groups: List[String] = Nil
var all_sessions = false
var build_heap = false
var clean_build = false
var dirs: List[Path] = Nil
var export_files = false
var fresh_build = false
var session_groups: List[String] = Nil
var max_jobs = 1
var check_keywords: Set[String] = Set.empty
var list_files = false
var no_build = false
var options = Options.init(opts = build_options)
var verbose = false
var exclude_sessions: List[String] = Nil
val getopts = Getopts("""
Usage: isabelle build [OPTIONS] [SESSIONS ...]
Options are:
-B NAME include session NAME and all descendants
-D DIR include session directory and select its sessions
-N cyclic shuffling of NUMA CPU nodes (performance tuning)
-P DIR enable HTML/PDF presentation in directory (":" for default)
-R refer to requirements of selected sessions
-S soft build: only observe changes of sources, not heap images
-X NAME exclude sessions from group NAME and all descendants
-a select all sessions
-b build heap images
-c clean build
-d DIR include session directory
-e export files from session specification into file-system
-f fresh build
-g NAME select session group NAME
-j INT maximum number of parallel jobs (default 1)
-k KEYWORD check theory sources for conflicts with proposed keywords
-l list session source files
-n no build -- test dependencies only
-o OPTION override Isabelle system OPTION (via NAME=VAL or NAME)
-v verbose
-x NAME exclude session NAME and all descendants
Build and manage Isabelle sessions, depending on implicit settings:
""" + Library.indent_lines(2, Build_Log.Settings.show()) + "\n",
"B:" -> (arg => base_sessions = base_sessions ::: List(arg)),
"D:" -> (arg => select_dirs = select_dirs ::: List(Path.explode(arg))),
"N" -> (_ => numa_shuffling = true),
"P:" -> (arg => presentation = Presentation.Context.make(arg)),
"R" -> (_ => requirements = true),
"S" -> (_ => soft_build = true),
"X:" -> (arg => exclude_session_groups = exclude_session_groups ::: List(arg)),
"a" -> (_ => all_sessions = true),
"b" -> (_ => build_heap = true),
"c" -> (_ => clean_build = true),
"d:" -> (arg => dirs = dirs ::: List(Path.explode(arg))),
"e" -> (_ => export_files = true),
"f" -> (_ => fresh_build = true),
"g:" -> (arg => session_groups = session_groups ::: List(arg)),
"j:" -> (arg => max_jobs = Value.Int.parse(arg)),
"k:" -> (arg => check_keywords = check_keywords + arg),
"l" -> (_ => list_files = true),
"n" -> (_ => no_build = true),
"o:" -> (arg => options = options + arg),
"v" -> (_ => verbose = true),
"x:" -> (arg => exclude_sessions = exclude_sessions ::: List(arg)))
val sessions = getopts(args)
val progress = new Console_Progress(verbose = verbose)
val start_date = Date.now()
if (verbose) {
progress.echo(
"Started at " + Build_Log.print_date(start_date) +
" (" + Isabelle_System.getenv("ML_IDENTIFIER") + " on " + Isabelle_System.hostname() +")")
progress.echo(Build_Log.Settings.show() + "\n")
}
val results =
progress.interrupt_handler {
build(options,
selection = Sessions.Selection(
requirements = requirements,
all_sessions = all_sessions,
base_sessions = base_sessions,
exclude_session_groups = exclude_session_groups,
exclude_sessions = exclude_sessions,
session_groups = session_groups,
sessions = sessions),
presentation = presentation,
progress = progress,
check_unknown_files = Mercurial.is_repository(Path.ISABELLE_HOME),
build_heap = build_heap,
clean_build = clean_build,
dirs = dirs,
select_dirs = select_dirs,
numa_shuffling = NUMA.enabled_warning(progress, numa_shuffling),
max_jobs = max_jobs,
list_files = list_files,
check_keywords = check_keywords,
fresh_build = fresh_build,
no_build = no_build,
soft_build = soft_build,
verbose = verbose,
export_files = export_files)
}
val end_date = Date.now()
val elapsed_time = end_date.time - start_date.time
if (verbose) {
progress.echo("\nFinished at " + Build_Log.print_date(end_date))
}
val total_timing =
results.sessions.iterator.map(a => results(a).timing).foldLeft(Timing.zero)(_ + _).
copy(elapsed = elapsed_time)
progress.echo(total_timing.message_resources)
sys.exit(results.rc)
})
/* build logic image */
def build_logic(options: Options, logic: String,
progress: Progress = new Progress,
build_heap: Boolean = false,
dirs: List[Path] = Nil,
fresh: Boolean = false,
strict: Boolean = false): Int =
{
val selection = Sessions.Selection.session(logic)
val rc =
if (!fresh && build(options, selection = selection,
build_heap = build_heap, no_build = true, dirs = dirs).ok) Process_Result.RC.ok
else {
progress.echo("Build started for Isabelle/" + logic + " ...")
Build.build(options, selection = selection, progress = progress,
build_heap = build_heap, fresh_build = fresh, dirs = dirs).rc
}
if (strict && rc != Process_Result.RC.ok) error("Failed to build Isabelle/" + logic) else rc
}
}