src/Pure/Thy/sessions.scala
changeset 75393 87ebf5a50283
parent 75382 81673c441ce3
child 75394 42267c650205
equal deleted inserted replaced
75388:b3ca4a6ed74b 75393:87ebf5a50283
    13 
    13 
    14 import scala.collection.immutable.{SortedSet, SortedMap}
    14 import scala.collection.immutable.{SortedSet, SortedMap}
    15 import scala.collection.mutable
    15 import scala.collection.mutable
    16 
    16 
    17 
    17 
    18 object Sessions
    18 object Sessions {
    19 {
       
    20   /* session and theory names */
    19   /* session and theory names */
    21 
    20 
    22   val ROOTS: Path = Path.explode("ROOTS")
    21   val ROOTS: Path = Path.explode("ROOTS")
    23   val ROOT: Path = Path.explode("ROOT")
    22   val ROOT: Path = Path.explode("ROOT")
    24 
    23 
    38     name == root_name || name == "README" || name == "index" || name == "bib"
    37     name == root_name || name == "README" || name == "index" || name == "bib"
    39 
    38 
    40 
    39 
    41   /* ROOTS file format */
    40   /* ROOTS file format */
    42 
    41 
    43   class File_Format extends isabelle.File_Format
    42   class File_Format extends isabelle.File_Format {
    44   {
       
    45     val format_name: String = roots_name
    43     val format_name: String = roots_name
    46     val file_ext = ""
    44     val file_ext = ""
    47 
    45 
    48     override def detect(name: String): Boolean =
    46     override def detect(name: String): Boolean =
    49       Thy_Header.split_file_name(name) match {
    47       Thy_Header.split_file_name(name) match {
    73     known_loaded_files: Map[String, List[Path]] = Map.empty,
    71     known_loaded_files: Map[String, List[Path]] = Map.empty,
    74     overall_syntax: Outer_Syntax = Outer_Syntax.empty,
    72     overall_syntax: Outer_Syntax = Outer_Syntax.empty,
    75     imported_sources: List[(Path, SHA1.Digest)] = Nil,
    73     imported_sources: List[(Path, SHA1.Digest)] = Nil,
    76     sources: List[(Path, SHA1.Digest)] = Nil,
    74     sources: List[(Path, SHA1.Digest)] = Nil,
    77     session_graph_display: Graph_Display.Graph = Graph_Display.empty_graph,
    75     session_graph_display: Graph_Display.Graph = Graph_Display.empty_graph,
    78     errors: List[String] = Nil)
    76     errors: List[String] = Nil
    79   {
    77   ) {
    80     override def toString: String =
    78     override def toString: String =
    81       "Sessions.Base(loaded_theories = " + loaded_theories.size +
    79       "Sessions.Base(loaded_theories = " + loaded_theories.size +
    82         ", used_theories = " + used_theories.length + ")"
    80         ", used_theories = " + used_theories.length + ")"
    83 
    81 
    84     def theory_qualifier(name: String): String =
    82     def theory_qualifier(name: String): String =
    98 
    96 
    99     def node_syntax(nodes: Document.Nodes, name: Document.Node.Name): Outer_Syntax =
    97     def node_syntax(nodes: Document.Nodes, name: Document.Node.Name): Outer_Syntax =
   100       nodes(name).syntax orElse loaded_theory_syntax(name) getOrElse overall_syntax
    98       nodes(name).syntax orElse loaded_theory_syntax(name) getOrElse overall_syntax
   101   }
    99   }
   102 
   100 
   103   sealed case class Deps(sessions_structure: Structure, session_bases: Map[String, Base])
   101   sealed case class Deps(sessions_structure: Structure, session_bases: Map[String, Base]) {
   104   {
       
   105     override def toString: String = "Sessions.Deps(" + sessions_structure + ")"
   102     override def toString: String = "Sessions.Deps(" + sessions_structure + ")"
   106 
   103 
   107     def is_empty: Boolean = session_bases.isEmpty
   104     def is_empty: Boolean = session_bases.isEmpty
   108     def apply(name: String): Base = session_bases(name)
   105     def apply(name: String): Base = session_bases(name)
   109     def get(name: String): Option[Base] = session_bases.get(name)
   106     def get(name: String): Option[Base] = session_bases.get(name)
   128         case errs => error(cat_lines(errs))
   125         case errs => error(cat_lines(errs))
   129       }
   126       }
   130   }
   127   }
   131 
   128 
   132   def deps(sessions_structure: Structure,
   129   def deps(sessions_structure: Structure,
   133       progress: Progress = new Progress,
   130     progress: Progress = new Progress,
   134       inlined_files: Boolean = false,
   131     inlined_files: Boolean = false,
   135       verbose: Boolean = false,
   132     verbose: Boolean = false,
   136       list_files: Boolean = false,
   133     list_files: Boolean = false,
   137       check_keywords: Set[String] = Set.empty): Deps =
   134     check_keywords: Set[String] = Set.empty
   138   {
   135   ): Deps = {
   139     var cache_sources = Map.empty[JFile, SHA1.Digest]
   136     var cache_sources = Map.empty[JFile, SHA1.Digest]
   140     def check_sources(paths: List[Path]): List[(Path, SHA1.Digest)] =
   137     def check_sources(paths: List[Path]): List[(Path, SHA1.Digest)] = {
   141     {
       
   142       for {
   138       for {
   143         path <- paths
   139         path <- paths
   144         file = path.file
   140         file = path.file
   145         if cache_sources.isDefinedAt(file) || file.isFile
   141         if cache_sources.isDefinedAt(file) || file.isFile
   146       }
   142       }
   202             if (check_keywords.nonEmpty) {
   198             if (check_keywords.nonEmpty) {
   203               Check_Keywords.check_keywords(
   199               Check_Keywords.check_keywords(
   204                 progress, overall_syntax.keywords, check_keywords, theory_files)
   200                 progress, overall_syntax.keywords, check_keywords, theory_files)
   205             }
   201             }
   206 
   202 
   207             val session_graph_display: Graph_Display.Graph =
   203             val session_graph_display: Graph_Display.Graph = {
   208             {
       
   209               def session_node(name: String): Graph_Display.Node =
   204               def session_node(name: String): Graph_Display.Node =
   210                 Graph_Display.Node("[" + name + "]", "session." + name)
   205                 Graph_Display.Node("[" + name + "]", "session." + name)
   211 
   206 
   212               def node(name: Document.Node.Name): Graph_Display.Node =
   207               def node(name: Document.Node.Name): Graph_Display.Node = {
   213               {
       
   214                 val qualifier = deps_base.theory_qualifier(name)
   208                 val qualifier = deps_base.theory_qualifier(name)
   215                 if (qualifier == info.name)
   209                 if (qualifier == info.name)
   216                   Graph_Display.Node(name.theory_base_name, "theory." + name.theory)
   210                   Graph_Display.Node(name.theory_base_name, "theory." + name.theory)
   217                 else session_node(qualifier)
   211                 else session_node(qualifier)
   218               }
   212               }
   249               dependencies.entries.iterator.map(entry => entry.name.theory -> entry).
   243               dependencies.entries.iterator.map(entry => entry.name.theory -> entry).
   250                 foldLeft(deps_base.known_theories)(_ + _)
   244                 foldLeft(deps_base.known_theories)(_ + _)
   251 
   245 
   252             val known_loaded_files = deps_base.known_loaded_files ++ loaded_files
   246             val known_loaded_files = deps_base.known_loaded_files ++ loaded_files
   253 
   247 
   254             val import_errors =
   248             val import_errors = {
   255             {
       
   256               val known_sessions =
   249               val known_sessions =
   257                 sessions_structure.imports_requirements(List(session_name)).toSet
   250                 sessions_structure.imports_requirements(List(session_name)).toSet
   258               for {
   251               for {
   259                 name <- dependencies.theories
   252                 name <- dependencies.theories
   260                 qualifier = deps_base.theory_qualifier(name)
   253                 qualifier = deps_base.theory_qualifier(name)
   289                   }
   282                   }
   290               })
   283               })
   291             val document_theories =
   284             val document_theories =
   292               info.document_theories.map({ case (thy, _) => known_theories(thy).name })
   285               info.document_theories.map({ case (thy, _) => known_theories(thy).name })
   293 
   286 
   294             val dir_errors =
   287             val dir_errors = {
   295             {
       
   296               val ok = info.dirs.map(_.canonical_file).toSet
   288               val ok = info.dirs.map(_.canonical_file).toSet
   297               val bad =
   289               val bad =
   298                 (for {
   290                 (for {
   299                   name <- session_theories.iterator
   291                   name <- session_theories.iterator
   300                   path = name.master_dir_path
   292                   path = name.master_dir_path
   370   sealed case class Base_Info(
   362   sealed case class Base_Info(
   371     session: String,
   363     session: String,
   372     sessions_structure: Structure,
   364     sessions_structure: Structure,
   373     errors: List[String],
   365     errors: List[String],
   374     base: Base,
   366     base: Base,
   375     infos: List[Info])
   367     infos: List[Info]
   376   {
   368   ) {
   377     def check: Base_Info = if (errors.isEmpty) this else error(cat_lines(errors))
   369     def check: Base_Info = if (errors.isEmpty) this else error(cat_lines(errors))
   378   }
   370   }
   379 
   371 
   380   def base_info(options: Options,
   372   def base_info(options: Options,
   381     session: String,
   373     session: String,
   382     progress: Progress = new Progress,
   374     progress: Progress = new Progress,
   383     dirs: List[Path] = Nil,
   375     dirs: List[Path] = Nil,
   384     include_sessions: List[String] = Nil,
   376     include_sessions: List[String] = Nil,
   385     session_ancestor: Option[String] = None,
   377     session_ancestor: Option[String] = None,
   386     session_requirements: Boolean = false): Base_Info =
   378     session_requirements: Boolean = false
   387   {
   379   ): Base_Info = {
   388     val full_sessions = load_structure(options, dirs = dirs)
   380     val full_sessions = load_structure(options, dirs = dirs)
   389 
   381 
   390     val selected_sessions =
   382     val selected_sessions =
   391       full_sessions.selection(Selection(sessions = session :: session_ancestor.toList))
   383       full_sessions.selection(Selection(sessions = session :: session_ancestor.toList))
   392     val info = selected_sessions(session)
   384     val info = selected_sessions(session)
   472     theories: List[(Options, List[(String, Position.T)])],
   464     theories: List[(Options, List[(String, Position.T)])],
   473     global_theories: List[String],
   465     global_theories: List[String],
   474     document_theories: List[(String, Position.T)],
   466     document_theories: List[(String, Position.T)],
   475     document_files: List[(Path, Path)],
   467     document_files: List[(Path, Path)],
   476     export_files: List[(Path, Int, List[String])],
   468     export_files: List[(Path, Int, List[String])],
   477     meta_digest: SHA1.Digest)
   469     meta_digest: SHA1.Digest
   478   {
   470   ) {
   479     def chapter_session: String = chapter + "/" + name
   471     def chapter_session: String = chapter + "/" + name
   480 
   472 
   481     def relative_path(info1: Info): String =
   473     def relative_path(info1: Info): String =
   482       if (name == info1.name) ""
   474       if (name == info1.name) ""
   483       else if (chapter == info1.chapter) "../" + info1.name + "/"
   475       else if (chapter == info1.chapter) "../" + info1.name + "/"
   484       else "../../" + info1.chapter_session + "/"
   476       else "../../" + info1.chapter_session + "/"
   485 
   477 
   486     def deps: List[String] = parent.toList ::: imports
   478     def deps: List[String] = parent.toList ::: imports
   487 
   479 
   488     def deps_base(session_bases: String => Base): Base =
   480     def deps_base(session_bases: String => Base): Base = {
   489     {
       
   490       val parent_base = session_bases(parent.getOrElse(""))
   481       val parent_base = session_bases(parent.getOrElse(""))
   491       val imports_bases = imports.map(session_bases)
   482       val imports_bases = imports.map(session_bases)
   492       parent_base.copy(
   483       parent_base.copy(
   493         known_theories =
   484         known_theories =
   494           (for {
   485           (for {
   512         case "" | "false" => false
   503         case "" | "false" => false
   513         case "pdf" | "true" => true
   504         case "pdf" | "true" => true
   514         case doc => error("Bad document specification " + quote(doc))
   505         case doc => error("Bad document specification " + quote(doc))
   515       }
   506       }
   516 
   507 
   517     def document_variants: List[Document_Build.Document_Variant] =
   508     def document_variants: List[Document_Build.Document_Variant] = {
   518     {
       
   519       val variants =
   509       val variants =
   520         Library.space_explode(':', options.string("document_variants")).
   510         Library.space_explode(':', options.string("document_variants")).
   521           map(Document_Build.Document_Variant.parse)
   511           map(Document_Build.Document_Variant.parse)
   522 
   512 
   523       val dups = Library.duplicates(variants.map(_.name))
   513       val dups = Library.duplicates(variants.map(_.name))
   524       if (dups.nonEmpty) error("Duplicate document variants: " + commas_quote(dups))
   514       if (dups.nonEmpty) error("Duplicate document variants: " + commas_quote(dups))
   525 
   515 
   526       variants
   516       variants
   527     }
   517     }
   528 
   518 
   529     def documents: List[Document_Build.Document_Variant] =
   519     def documents: List[Document_Build.Document_Variant] = {
   530     {
       
   531       val variants = document_variants
   520       val variants = document_variants
   532       if (!document_enabled || document_files.isEmpty) Nil else variants
   521       if (!document_enabled || document_files.isEmpty) Nil else variants
   533     }
   522     }
   534 
   523 
   535     def document_output: Option[Path] =
   524     def document_output: Option[Path] =
   551 
   540 
   552     def is_afp: Boolean = chapter == AFP.chapter
   541     def is_afp: Boolean = chapter == AFP.chapter
   553     def is_afp_bulky: Boolean = is_afp && groups.exists(AFP.groups_bulky.contains)
   542     def is_afp_bulky: Boolean = is_afp && groups.exists(AFP.groups_bulky.contains)
   554   }
   543   }
   555 
   544 
   556   def make_info(options: Options, dir_selected: Boolean, dir: Path, chapter: String,
   545   def make_info(
   557     entry: Session_Entry): Info =
   546     options: Options,
   558   {
   547     dir_selected: Boolean,
       
   548     dir: Path,
       
   549     chapter: String,
       
   550     entry: Session_Entry
       
   551   ): Info = {
   559     try {
   552     try {
   560       val name = entry.name
   553       val name = entry.name
   561 
   554 
   562       if (exclude_session(name)) error("Bad session name")
   555       if (exclude_session(name)) error("Bad session name")
   563       if (is_pure(name) && entry.parent.isDefined) error("Illegal parent session")
   556       if (is_pure(name) && entry.parent.isDefined) error("Illegal parent session")
   613         error(msg + "\nThe error(s) above occurred in session entry " +
   606         error(msg + "\nThe error(s) above occurred in session entry " +
   614           quote(entry.name) + Position.here(entry.pos))
   607           quote(entry.name) + Position.here(entry.pos))
   615     }
   608     }
   616   }
   609   }
   617 
   610 
   618   object Selection
   611   object Selection {
   619   {
       
   620     val empty: Selection = Selection()
   612     val empty: Selection = Selection()
   621     val all: Selection = Selection(all_sessions = true)
   613     val all: Selection = Selection(all_sessions = true)
   622     def session(session: String): Selection = Selection(sessions = List(session))
   614     def session(session: String): Selection = Selection(sessions = List(session))
   623   }
   615   }
   624 
   616 
   627     all_sessions: Boolean = false,
   619     all_sessions: Boolean = false,
   628     base_sessions: List[String] = Nil,
   620     base_sessions: List[String] = Nil,
   629     exclude_session_groups: List[String] = Nil,
   621     exclude_session_groups: List[String] = Nil,
   630     exclude_sessions: List[String] = Nil,
   622     exclude_sessions: List[String] = Nil,
   631     session_groups: List[String] = Nil,
   623     session_groups: List[String] = Nil,
   632     sessions: List[String] = Nil)
   624     sessions: List[String] = Nil
   633   {
   625   ) {
   634     def ++ (other: Selection): Selection =
   626     def ++ (other: Selection): Selection =
   635       Selection(
   627       Selection(
   636         requirements = requirements || other.requirements,
   628         requirements = requirements || other.requirements,
   637         all_sessions = all_sessions || other.all_sessions,
   629         all_sessions = all_sessions || other.all_sessions,
   638         base_sessions = Library.merge(base_sessions, other.base_sessions),
   630         base_sessions = Library.merge(base_sessions, other.base_sessions),
   640         exclude_sessions = Library.merge(exclude_sessions, other.exclude_sessions),
   632         exclude_sessions = Library.merge(exclude_sessions, other.exclude_sessions),
   641         session_groups = Library.merge(session_groups, other.session_groups),
   633         session_groups = Library.merge(session_groups, other.session_groups),
   642         sessions = Library.merge(sessions, other.sessions))
   634         sessions = Library.merge(sessions, other.sessions))
   643   }
   635   }
   644 
   636 
   645   object Structure
   637   object Structure {
   646   {
       
   647     val empty: Structure = make(Nil)
   638     val empty: Structure = make(Nil)
   648 
   639 
   649     def make(infos: List[Info]): Structure =
   640     def make(infos: List[Info]): Structure = {
   650     {
   641       def add_edges(
   651       def add_edges(graph: Graph[String, Info], kind: String, edges: Info => Iterable[String])
   642         graph: Graph[String, Info],
   652         : Graph[String, Info] =
   643         kind: String,
   653       {
   644         edges: Info => Iterable[String]
   654         def add_edge(pos: Position.T, name: String, g: Graph[String, Info], parent: String) =
   645       ) : Graph[String, Info] = {
   655         {
   646         def add_edge(pos: Position.T, name: String, g: Graph[String, Info], parent: String) = {
   656           if (!g.defined(parent))
   647           if (!g.defined(parent))
   657             error("Bad " + kind + " session " + quote(parent) + " for " +
   648             error("Bad " + kind + " session " + quote(parent) + " for " +
   658               quote(name) + Position.here(pos))
   649               quote(name) + Position.here(pos))
   659 
   650 
   660           try { g.add_edge_acyclic(parent, name) }
   651           try { g.add_edge_acyclic(parent, name) }
   723         session_positions, session_directories, global_theories, build_graph, imports_graph)
   714         session_positions, session_directories, global_theories, build_graph, imports_graph)
   724     }
   715     }
   725   }
   716   }
   726 
   717 
   727   final class Structure private[Sessions](
   718   final class Structure private[Sessions](
   728       val session_positions: List[(String, Position.T)],
   719     val session_positions: List[(String, Position.T)],
   729       val session_directories: Map[JFile, String],
   720     val session_directories: Map[JFile, String],
   730       val global_theories: Map[String, String],
   721     val global_theories: Map[String, String],
   731       val build_graph: Graph[String, Info],
   722     val build_graph: Graph[String, Info],
   732       val imports_graph: Graph[String, Info])
   723     val imports_graph: Graph[String, Info]
   733   {
   724   ) {
   734     sessions_structure =>
   725     sessions_structure =>
   735 
   726 
   736     def bootstrap: Base =
   727     def bootstrap: Base =
   737       Base(
   728       Base(
   738         session_directories = session_directories,
   729         session_directories = session_directories,
   757     def get(name: String): Option[Info] = if (defined(name)) Some(apply(name)) else None
   748     def get(name: String): Option[Info] = if (defined(name)) Some(apply(name)) else None
   758 
   749 
   759     def theory_qualifier(name: String): String =
   750     def theory_qualifier(name: String): String =
   760       global_theories.getOrElse(name, Long_Name.qualifier(name))
   751       global_theories.getOrElse(name, Long_Name.qualifier(name))
   761 
   752 
   762     def check_sessions(names: List[String]): Unit =
   753     def check_sessions(names: List[String]): Unit = {
   763     {
       
   764       val bad_sessions = SortedSet(names.filterNot(defined): _*).toList
   754       val bad_sessions = SortedSet(names.filterNot(defined): _*).toList
   765       if (bad_sessions.nonEmpty)
   755       if (bad_sessions.nonEmpty)
   766         error("Undefined session(s): " + commas_quote(bad_sessions))
   756         error("Undefined session(s): " + commas_quote(bad_sessions))
   767     }
   757     }
   768 
   758 
   769     def check_sessions(sel: Selection): Unit =
   759     def check_sessions(sel: Selection): Unit =
   770       check_sessions(sel.base_sessions ::: sel.exclude_sessions ::: sel.sessions)
   760       check_sessions(sel.base_sessions ::: sel.exclude_sessions ::: sel.sessions)
   771 
   761 
   772     private def selected(graph: Graph[String, Info], sel: Selection): List[String] =
   762     private def selected(graph: Graph[String, Info], sel: Selection): List[String] = {
   773     {
       
   774       check_sessions(sel)
   763       check_sessions(sel)
   775 
   764 
   776       val select_group = sel.session_groups.toSet
   765       val select_group = sel.session_groups.toSet
   777       val select_session = sel.sessions.toSet ++ imports_graph.all_succs(sel.base_sessions)
   766       val select_session = sel.sessions.toSet ++ imports_graph.all_succs(sel.base_sessions)
   778 
   767 
   787 
   776 
   788       if (sel.requirements) (graph.all_preds(selected0).toSet -- selected0).toList
   777       if (sel.requirements) (graph.all_preds(selected0).toSet -- selected0).toList
   789       else selected0
   778       else selected0
   790     }
   779     }
   791 
   780 
   792     def selection(sel: Selection): Structure =
   781     def selection(sel: Selection): Structure = {
   793     {
       
   794       check_sessions(sel)
   782       check_sessions(sel)
   795 
   783 
   796       val excluded =
   784       val excluded = {
   797       {
       
   798         val exclude_group = sel.exclude_session_groups.toSet
   785         val exclude_group = sel.exclude_session_groups.toSet
   799         val exclude_group_sessions =
   786         val exclude_group_sessions =
   800           (for {
   787           (for {
   801             (name, (info, _)) <- imports_graph.iterator
   788             (name, (info, _)) <- imports_graph.iterator
   802             if imports_graph.get_node(name).groups.exists(exclude_group)
   789             if imports_graph.get_node(name).groups.exists(exclude_group)
   803           } yield name).toList
   790           } yield name).toList
   804         imports_graph.all_succs(exclude_group_sessions ::: sel.exclude_sessions).toSet
   791         imports_graph.all_succs(exclude_group_sessions ::: sel.exclude_sessions).toSet
   805       }
   792       }
   806 
   793 
   807       def restrict(graph: Graph[String, Info]): Graph[String, Info] =
   794       def restrict(graph: Graph[String, Info]): Graph[String, Info] = {
   808       {
       
   809         val sessions = graph.all_preds(selected(graph, sel)).filterNot(excluded)
   795         val sessions = graph.all_preds(selected(graph, sel)).filterNot(excluded)
   810         graph.restrict(graph.all_preds(sessions).toSet)
   796         graph.restrict(graph.all_preds(sessions).toSet)
   811       }
   797       }
   812 
   798 
   813       new Structure(
   799       new Structure(
   820     def selection_deps(
   806     def selection_deps(
   821       selection: Selection,
   807       selection: Selection,
   822       progress: Progress = new Progress,
   808       progress: Progress = new Progress,
   823       loading_sessions: Boolean = false,
   809       loading_sessions: Boolean = false,
   824       inlined_files: Boolean = false,
   810       inlined_files: Boolean = false,
   825       verbose: Boolean = false): Deps =
   811       verbose: Boolean = false
   826     {
   812     ): Deps = {
   827       val deps =
   813       val deps =
   828         Sessions.deps(sessions_structure.selection(selection),
   814         Sessions.deps(sessions_structure.selection(selection),
   829           progress = progress, inlined_files = inlined_files, verbose = verbose)
   815           progress = progress, inlined_files = inlined_files, verbose = verbose)
   830 
   816 
   831       if (loading_sessions) {
   817       if (loading_sessions) {
   902     imports: List[String],
   888     imports: List[String],
   903     directories: List[String],
   889     directories: List[String],
   904     theories: List[(List[Options.Spec], List[((String, Position.T), Boolean)])],
   890     theories: List[(List[Options.Spec], List[((String, Position.T), Boolean)])],
   905     document_theories: List[(String, Position.T)],
   891     document_theories: List[(String, Position.T)],
   906     document_files: List[(String, String)],
   892     document_files: List[(String, String)],
   907     export_files: List[(String, Int, List[String])]) extends Entry
   893     export_files: List[(String, Int, List[String])]
   908   {
   894   ) extends Entry {
   909     def theories_no_position: List[(List[Options.Spec], List[(String, Boolean)])] =
   895     def theories_no_position: List[(List[Options.Spec], List[(String, Boolean)])] =
   910       theories.map({ case (a, b) => (a, b.map({ case ((c, _), d) => (c, d) })) })
   896       theories.map({ case (a, b) => (a, b.map({ case ((c, _), d) => (c, d) })) })
   911     def document_theories_no_position: List[String] =
   897     def document_theories_no_position: List[String] =
   912       document_theories.map(_._1)
   898       document_theories.map(_._1)
   913   }
   899   }
   914 
   900 
   915   private object Parser extends Options.Parser
   901   private object Parser extends Options.Parser {
   916   {
   902     private val chapter: Parser[Chapter] = {
   917     private val chapter: Parser[Chapter] =
       
   918     {
       
   919       val chapter_name = atom("chapter name", _.is_name)
   903       val chapter_name = atom("chapter name", _.is_name)
   920 
   904 
   921       command(CHAPTER) ~! chapter_name ^^ { case _ ~ a => Chapter(a) }
   905       command(CHAPTER) ~! chapter_name ^^ { case _ ~ a => Chapter(a) }
   922     }
   906     }
   923 
   907 
   924     private val session_entry: Parser[Session_Entry] =
   908     private val session_entry: Parser[Session_Entry] = {
   925     {
       
   926       val option =
   909       val option =
   927         option_name ~ opt($$$("=") ~! option_value ^^ { case _ ~ x => x }) ^^
   910         option_name ~ opt($$$("=") ~! option_value ^^ { case _ ~ x => x }) ^^
   928           { case x ~ y => (x, y) }
   911           { case x ~ y => (x, y) }
   929       val options = $$$("[") ~> rep1sep(option, $$$(",")) <~ $$$("]")
   912       val options = $$$("[") ~> rep1sep(option, $$$(",")) <~ $$$("]")
   930 
   913 
   967               rep(export_files)))) ^^
   950               rep(export_files)))) ^^
   968         { case _ ~ ((a, pos) ~ b ~ c ~ (_ ~ (d ~ e ~ f ~ g ~ h ~ i ~ j ~ k ~ l))) =>
   951         { case _ ~ ((a, pos) ~ b ~ c ~ (_ ~ (d ~ e ~ f ~ g ~ h ~ i ~ j ~ k ~ l))) =>
   969             Session_Entry(pos, a, b, c, d, e, f, g, h, i, j, k, l) }
   952             Session_Entry(pos, a, b, c, d, e, f, g, h, i, j, k, l) }
   970     }
   953     }
   971 
   954 
   972     def parse_root(path: Path): List[Entry] =
   955     def parse_root(path: Path): List[Entry] = {
   973     {
       
   974       val toks = Token.explode(root_syntax.keywords, File.read(path))
   956       val toks = Token.explode(root_syntax.keywords, File.read(path))
   975       val start = Token.Pos.file(path.implode)
   957       val start = Token.Pos.file(path.implode)
   976 
   958 
   977       parse_all(rep(chapter | session_entry), Token.reader(toks, start)) match {
   959       parse_all(rep(chapter | session_entry), Token.reader(toks, start)) match {
   978         case Success(result, _) => result
   960         case Success(result, _) => result
   985 
   967 
   986   def parse_root_entries(path: Path): List[Session_Entry] =
   968   def parse_root_entries(path: Path): List[Session_Entry] =
   987     for (entry <- Parser.parse_root(path) if entry.isInstanceOf[Session_Entry])
   969     for (entry <- Parser.parse_root(path) if entry.isInstanceOf[Session_Entry])
   988     yield entry.asInstanceOf[Session_Entry]
   970     yield entry.asInstanceOf[Session_Entry]
   989 
   971 
   990   def read_root(options: Options, select: Boolean, path: Path): List[Info] =
   972   def read_root(options: Options, select: Boolean, path: Path): List[Info] = {
   991   {
       
   992     var entry_chapter = UNSORTED
   973     var entry_chapter = UNSORTED
   993     val infos = new mutable.ListBuffer[Info]
   974     val infos = new mutable.ListBuffer[Info]
   994     parse_root(path).foreach {
   975     parse_root(path).foreach {
   995       case Chapter(name) => entry_chapter = name
   976       case Chapter(name) => entry_chapter = name
   996       case entry: Session_Entry =>
   977       case entry: Session_Entry =>
   997         infos += make_info(options, select, path.dir, entry_chapter, entry)
   978         infos += make_info(options, select, path.dir, entry_chapter, entry)
   998     }
   979     }
   999     infos.toList
   980     infos.toList
  1000   }
   981   }
  1001 
   982 
  1002   def parse_roots(roots: Path): List[String] =
   983   def parse_roots(roots: Path): List[String] = {
  1003   {
       
  1004     for {
   984     for {
  1005       line <- split_lines(File.read(roots))
   985       line <- split_lines(File.read(roots))
  1006       if !(line == "" || line.startsWith("#"))
   986       if !(line == "" || line.startsWith("#"))
  1007     } yield line
   987     } yield line
  1008   }
   988   }
  1015 
   995 
  1016   def check_session_dir(dir: Path): Path =
   996   def check_session_dir(dir: Path): Path =
  1017     if (is_session_dir(dir)) File.pwd() + dir.expand
   997     if (is_session_dir(dir)) File.pwd() + dir.expand
  1018     else error("Bad session root directory (missing ROOT or ROOTS): " + dir.expand.toString)
   998     else error("Bad session root directory (missing ROOT or ROOTS): " + dir.expand.toString)
  1019 
   999 
  1020   def directories(dirs: List[Path], select_dirs: List[Path]): List[(Boolean, Path)] =
  1000   def directories(dirs: List[Path], select_dirs: List[Path]): List[(Boolean, Path)] = {
  1021   {
       
  1022     val default_dirs = Components.directories().filter(is_session_dir)
  1001     val default_dirs = Components.directories().filter(is_session_dir)
  1023     for { (select, dir) <- (default_dirs ::: dirs).map((false, _)) ::: select_dirs.map((true, _)) }
  1002     for { (select, dir) <- (default_dirs ::: dirs).map((false, _)) ::: select_dirs.map((true, _)) }
  1024     yield (select, dir.canonical)
  1003     yield (select, dir.canonical)
  1025   }
  1004   }
  1026 
  1005 
  1027   def load_structure(options: Options,
  1006   def load_structure(
       
  1007     options: Options,
  1028     dirs: List[Path] = Nil,
  1008     dirs: List[Path] = Nil,
  1029     select_dirs: List[Path] = Nil,
  1009     select_dirs: List[Path] = Nil,
  1030     infos: List[Info] = Nil): Structure =
  1010     infos: List[Info] = Nil
  1031   {
  1011   ): Structure = {
  1032     def load_dir(select: Boolean, dir: Path): List[(Boolean, Path)] =
  1012     def load_dir(select: Boolean, dir: Path): List[(Boolean, Path)] =
  1033       load_root(select, dir) ::: load_roots(select, dir)
  1013       load_root(select, dir) ::: load_roots(select, dir)
  1034 
  1014 
  1035     def load_root(select: Boolean, dir: Path): List[(Boolean, Path)] =
  1015     def load_root(select: Boolean, dir: Path): List[(Boolean, Path)] = {
  1036     {
       
  1037       val root = dir + ROOT
  1016       val root = dir + ROOT
  1038       if (root.is_file) List((select, root)) else Nil
  1017       if (root.is_file) List((select, root)) else Nil
  1039     }
  1018     }
  1040 
  1019 
  1041     def load_roots(select: Boolean, dir: Path): List[(Boolean, Path)] =
  1020     def load_roots(select: Boolean, dir: Path): List[(Boolean, Path)] = {
  1042     {
       
  1043       val roots = dir + ROOTS
  1021       val roots = dir + ROOTS
  1044       if (roots.is_file) {
  1022       if (roots.is_file) {
  1045         for {
  1023         for {
  1046           entry <- parse_roots(roots)
  1024           entry <- parse_roots(roots)
  1047           dir1 =
  1025           dir1 =
  1077 
  1055 
  1078 
  1056 
  1079   /* Isabelle tool wrapper */
  1057   /* Isabelle tool wrapper */
  1080 
  1058 
  1081   val isabelle_tool = Isabelle_Tool("sessions", "explore structure of Isabelle sessions",
  1059   val isabelle_tool = Isabelle_Tool("sessions", "explore structure of Isabelle sessions",
  1082     Scala_Project.here, args =>
  1060     Scala_Project.here, args => {
  1083   {
       
  1084     var base_sessions: List[String] = Nil
  1061     var base_sessions: List[String] = Nil
  1085     var select_dirs: List[Path] = Nil
  1062     var select_dirs: List[Path] = Nil
  1086     var requirements = false
  1063     var requirements = false
  1087     var exclude_session_groups: List[String] = Nil
  1064     var exclude_session_groups: List[String] = Nil
  1088     var all_sessions = false
  1065     var all_sessions = false
  1135 
  1112 
  1136   /** heap file with SHA1 digest **/
  1113   /** heap file with SHA1 digest **/
  1137 
  1114 
  1138   private val sha1_prefix = "SHA1:"
  1115   private val sha1_prefix = "SHA1:"
  1139 
  1116 
  1140   def read_heap_digest(heap: Path): Option[String] =
  1117   def read_heap_digest(heap: Path): Option[String] = {
  1141   {
       
  1142     if (heap.is_file) {
  1118     if (heap.is_file) {
  1143       using(FileChannel.open(heap.java_path, StandardOpenOption.READ))(file =>
  1119       using(FileChannel.open(heap.java_path, StandardOpenOption.READ))(file => {
  1144       {
       
  1145         val len = file.size
  1120         val len = file.size
  1146         val n = sha1_prefix.length + SHA1.digest_length
  1121         val n = sha1_prefix.length + SHA1.digest_length
  1147         if (len >= n) {
  1122         if (len >= n) {
  1148           file.position(len - n)
  1123           file.position(len - n)
  1149 
  1124 
  1181 
  1156 
  1182 
  1157 
  1183 
  1158 
  1184   /** persistent store **/
  1159   /** persistent store **/
  1185 
  1160 
  1186   object Session_Info
  1161   object Session_Info {
  1187   {
       
  1188     val session_name = SQL.Column.string("session_name").make_primary_key
  1162     val session_name = SQL.Column.string("session_name").make_primary_key
  1189 
  1163 
  1190     // Build_Log.Session_Info
  1164     // Build_Log.Session_Info
  1191     val session_timing = SQL.Column.bytes("session_timing")
  1165     val session_timing = SQL.Column.bytes("session_timing")
  1192     val command_timings = SQL.Column.bytes("command_timings")
  1166     val command_timings = SQL.Column.bytes("command_timings")
  1208     val table = SQL.Table("isabelle_session_info", build_log_columns ::: build_columns)
  1182     val table = SQL.Table("isabelle_session_info", build_log_columns ::: build_columns)
  1209   }
  1183   }
  1210 
  1184 
  1211   class Database_Context private[Sessions](
  1185   class Database_Context private[Sessions](
  1212     val store: Sessions.Store,
  1186     val store: Sessions.Store,
  1213     database_server: Option[SQL.Database]) extends AutoCloseable
  1187     database_server: Option[SQL.Database]
  1214   {
  1188   ) extends AutoCloseable {
  1215     def cache: Term.Cache = store.cache
  1189     def cache: Term.Cache = store.cache
  1216 
  1190 
  1217     def close(): Unit = database_server.foreach(_.close())
  1191     def close(): Unit = database_server.foreach(_.close())
  1218 
  1192 
  1219     def output_database[A](session: String)(f: SQL.Database => A): A =
  1193     def output_database[A](session: String)(f: SQL.Database => A): A =
  1231             case None => None
  1205             case None => None
  1232           }
  1206           }
  1233       }
  1207       }
  1234 
  1208 
  1235     def read_export(
  1209     def read_export(
  1236       sessions: List[String], theory_name: String, name: String): Option[Export.Entry] =
  1210       sessions: List[String],
  1237     {
  1211       theory_name: String,
       
  1212       name: String
       
  1213     ): Option[Export.Entry] = {
  1238       val attempts =
  1214       val attempts =
  1239         database_server match {
  1215         database_server match {
  1240           case Some(db) =>
  1216           case Some(db) =>
  1241             sessions.view.map(session_name =>
  1217             sessions.view.map(session_name =>
  1242               Export.read_entry(db, store.cache, session_name, theory_name, name))
  1218               Export.read_entry(db, store.cache, session_name, theory_name, name))
  1254     def get_export(
  1230     def get_export(
  1255         session_hierarchy: List[String], theory_name: String, name: String): Export.Entry =
  1231         session_hierarchy: List[String], theory_name: String, name: String): Export.Entry =
  1256       read_export(session_hierarchy, theory_name, name) getOrElse
  1232       read_export(session_hierarchy, theory_name, name) getOrElse
  1257         Export.empty_entry(theory_name, name)
  1233         Export.empty_entry(theory_name, name)
  1258 
  1234 
  1259     override def toString: String =
  1235     override def toString: String = {
  1260     {
       
  1261       val s =
  1236       val s =
  1262         database_server match {
  1237         database_server match {
  1263           case Some(db) => db.toString
  1238           case Some(db) => db.toString
  1264           case None => "input_dirs = " + store.input_dirs.map(_.absolute).mkString(", ")
  1239           case None => "input_dirs = " + store.input_dirs.map(_.absolute).mkString(", ")
  1265         }
  1240         }
  1268   }
  1243   }
  1269 
  1244 
  1270   def store(options: Options, cache: Term.Cache = Term.Cache.make()): Store =
  1245   def store(options: Options, cache: Term.Cache = Term.Cache.make()): Store =
  1271     new Store(options, cache)
  1246     new Store(options, cache)
  1272 
  1247 
  1273   class Store private[Sessions](val options: Options, val cache: Term.Cache)
  1248   class Store private[Sessions](val options: Options, val cache: Term.Cache) {
  1274   {
       
  1275     store =>
  1249     store =>
  1276 
  1250 
  1277     override def toString: String = "Store(output_dir = " + output_dir.absolute + ")"
  1251     override def toString: String = "Store(output_dir = " + output_dir.absolute + ")"
  1278 
  1252 
  1279 
  1253 
  1345         ssh_close = true)
  1319         ssh_close = true)
  1346 
  1320 
  1347     def open_database_context(): Database_Context =
  1321     def open_database_context(): Database_Context =
  1348       new Database_Context(store, if (database_server) Some(open_database_server()) else None)
  1322       new Database_Context(store, if (database_server) Some(open_database_server()) else None)
  1349 
  1323 
  1350     def try_open_database(name: String, output: Boolean = false): Option[SQL.Database] =
  1324     def try_open_database(name: String, output: Boolean = false): Option[SQL.Database] = {
  1351     {
       
  1352       def check(db: SQL.Database): Option[SQL.Database] =
  1325       def check(db: SQL.Database): Option[SQL.Database] =
  1353         if (output || session_info_exists(db)) Some(db) else { db.close(); None }
  1326         if (output || session_info_exists(db)) Some(db) else { db.close(); None }
  1354 
  1327 
  1355       if (database_server) check(open_database_server())
  1328       if (database_server) check(open_database_server())
  1356       else if (output) Some(SQLite.open_database(output_database(name)))
  1329       else if (output) Some(SQLite.open_database(output_database(name)))
  1365 
  1338 
  1366     def open_database(name: String, output: Boolean = false): SQL.Database =
  1339     def open_database(name: String, output: Boolean = false): SQL.Database =
  1367       try_open_database(name, output = output) getOrElse
  1340       try_open_database(name, output = output) getOrElse
  1368         error("Missing build database for session " + quote(name))
  1341         error("Missing build database for session " + quote(name))
  1369 
  1342 
  1370     def clean_output(name: String): (Boolean, Boolean) =
  1343     def clean_output(name: String): (Boolean, Boolean) = {
  1371     {
       
  1372       val relevant_db =
  1344       val relevant_db =
  1373         database_server &&
  1345         database_server && {
  1374         {
       
  1375           try_open_database(name) match {
  1346           try_open_database(name) match {
  1376             case Some(db) =>
  1347             case Some(db) =>
  1377               try {
  1348               try {
  1378                 db.transaction {
  1349                 db.transaction {
  1379                   val relevant_db = session_info_defined(db, name)
  1350                   val relevant_db = session_info_defined(db, name)
  1401 
  1372 
  1402     /* SQL database content */
  1373     /* SQL database content */
  1403 
  1374 
  1404     def read_bytes(db: SQL.Database, name: String, column: SQL.Column): Bytes =
  1375     def read_bytes(db: SQL.Database, name: String, column: SQL.Column): Bytes =
  1405       db.using_statement(Session_Info.table.select(List(column),
  1376       db.using_statement(Session_Info.table.select(List(column),
  1406         Session_Info.session_name.where_equal(name)))(stmt =>
  1377         Session_Info.session_name.where_equal(name)))(stmt => {
  1407       {
       
  1408         val res = stmt.execute_query()
  1378         val res = stmt.execute_query()
  1409         if (!res.next()) Bytes.empty else res.bytes(column)
  1379         if (!res.next()) Bytes.empty else res.bytes(column)
  1410       })
  1380       })
  1411 
  1381 
  1412     def read_properties(db: SQL.Database, name: String, column: SQL.Column): List[Properties.T] =
  1382     def read_properties(db: SQL.Database, name: String, column: SQL.Column): List[Properties.T] =
  1413       Properties.uncompress(read_bytes(db, name, column), cache = cache)
  1383       Properties.uncompress(read_bytes(db, name, column), cache = cache)
  1414 
  1384 
  1415 
  1385 
  1416     /* session info */
  1386     /* session info */
  1417 
  1387 
  1418     def init_session_info(db: SQL.Database, name: String): Unit =
  1388     def init_session_info(db: SQL.Database, name: String): Unit = {
  1419     {
       
  1420       db.transaction {
  1389       db.transaction {
  1421         db.create_table(Session_Info.table)
  1390         db.create_table(Session_Info.table)
  1422         db.using_statement(
  1391         db.using_statement(
  1423           Session_Info.table.delete(Session_Info.session_name.where_equal(name)))(_.execute())
  1392           Session_Info.table.delete(Session_Info.session_name.where_equal(name)))(_.execute())
  1424 
  1393 
  1431           Document_Build.Data.table.delete(
  1400           Document_Build.Data.table.delete(
  1432             Document_Build.Data.session_name.where_equal(name)))(_.execute())
  1401             Document_Build.Data.session_name.where_equal(name)))(_.execute())
  1433       }
  1402       }
  1434     }
  1403     }
  1435 
  1404 
  1436     def session_info_exists(db: SQL.Database): Boolean =
  1405     def session_info_exists(db: SQL.Database): Boolean = {
  1437     {
       
  1438       val tables = db.tables
  1406       val tables = db.tables
  1439       tables.contains(Session_Info.table.name) &&
  1407       tables.contains(Session_Info.table.name) &&
  1440       tables.contains(Export.Data.table.name)
  1408       tables.contains(Export.Data.table.name)
  1441     }
  1409     }
  1442 
  1410 
  1443     def session_info_defined(db: SQL.Database, name: String): Boolean =
  1411     def session_info_defined(db: SQL.Database, name: String): Boolean =
  1444       db.transaction {
  1412       db.transaction {
  1445         session_info_exists(db) &&
  1413         session_info_exists(db) && {
  1446         {
       
  1447           db.using_statement(
  1414           db.using_statement(
  1448             Session_Info.table.select(List(Session_Info.session_name),
  1415             Session_Info.table.select(List(Session_Info.session_name),
  1449               Session_Info.session_name.where_equal(name)))(stmt => stmt.execute_query().next())
  1416               Session_Info.session_name.where_equal(name)))(stmt => stmt.execute_query().next())
  1450         }
  1417         }
  1451       }
  1418       }
  1452 
  1419 
  1453     def write_session_info(
  1420     def write_session_info(
  1454       db: SQL.Database,
  1421       db: SQL.Database,
  1455       name: String,
  1422       name: String,
  1456       build_log: Build_Log.Session_Info,
  1423       build_log: Build_Log.Session_Info,
  1457       build: Build.Session_Info): Unit =
  1424       build: Build.Session_Info
  1458     {
  1425     ): Unit = {
  1459       db.transaction {
  1426       db.transaction {
  1460         db.using_statement(Session_Info.table.insert())(stmt =>
  1427         db.using_statement(Session_Info.table.insert())(stmt => {
  1461         {
       
  1462           stmt.string(1) = name
  1428           stmt.string(1) = name
  1463           stmt.bytes(2) = Properties.encode(build_log.session_timing)
  1429           stmt.bytes(2) = Properties.encode(build_log.session_timing)
  1464           stmt.bytes(3) = Properties.compress(build_log.command_timings, cache = cache.xz)
  1430           stmt.bytes(3) = Properties.compress(build_log.command_timings, cache = cache.xz)
  1465           stmt.bytes(4) = Properties.compress(build_log.theory_timings, cache = cache.xz)
  1431           stmt.bytes(4) = Properties.compress(build_log.theory_timings, cache = cache.xz)
  1466           stmt.bytes(5) = Properties.compress(build_log.ml_statistics, cache = cache.xz)
  1432           stmt.bytes(5) = Properties.compress(build_log.ml_statistics, cache = cache.xz)
  1494       read_theory_timings(db, name).flatMap(Markup.Name.unapply)
  1460       read_theory_timings(db, name).flatMap(Markup.Name.unapply)
  1495 
  1461 
  1496     def read_errors(db: SQL.Database, name: String): List[String] =
  1462     def read_errors(db: SQL.Database, name: String): List[String] =
  1497       Build_Log.uncompress_errors(read_bytes(db, name, Session_Info.errors), cache = cache)
  1463       Build_Log.uncompress_errors(read_bytes(db, name, Session_Info.errors), cache = cache)
  1498 
  1464 
  1499     def read_build(db: SQL.Database, name: String): Option[Build.Session_Info] =
  1465     def read_build(db: SQL.Database, name: String): Option[Build.Session_Info] = {
  1500     {
       
  1501       if (db.tables.contains(Session_Info.table.name)) {
  1466       if (db.tables.contains(Session_Info.table.name)) {
  1502         db.using_statement(Session_Info.table.select(Session_Info.build_columns,
  1467         db.using_statement(Session_Info.table.select(Session_Info.build_columns,
  1503           Session_Info.session_name.where_equal(name)))(stmt =>
  1468           Session_Info.session_name.where_equal(name)))(stmt => {
  1504         {
       
  1505           val res = stmt.execute_query()
  1469           val res = stmt.execute_query()
  1506           if (!res.next()) None
  1470           if (!res.next()) None
  1507           else {
  1471           else {
  1508             Some(
  1472             Some(
  1509               Build.Session_Info(
  1473               Build.Session_Info(