src/Pure/System/build.scala
author wenzelm
Thu Jul 19 16:09:48 2012 +0200 (2012-07-19)
changeset 48351 a0b95a762abb
parent 48350 09bf3b73e446
child 48352 7fbf98ee265f
permissions -rw-r--r--
less redundant data structures;
     1 /*  Title:      Pure/System/build.scala
     2     Author:     Makarius
     3 
     4 Build and manage Isabelle sessions.
     5 */
     6 
     7 package isabelle
     8 
     9 
    10 import java.io.File
    11 
    12 import scala.collection.mutable
    13 import scala.annotation.tailrec
    14 
    15 
    16 object Build
    17 {
    18   /** session information **/
    19 
    20   type Options = List[(String, Option[String])]
    21 
    22   object Session
    23   {
    24     object Key
    25     {
    26       object Ordering extends scala.math.Ordering[Key]
    27       {
    28         def compare(key1: Key, key2: Key): Int =
    29           key1.order compare key2.order match {
    30             case 0 => key1.name compare key2.name
    31             case ord => ord
    32           }
    33       }
    34     }
    35 
    36     sealed case class Key(name: String, order: Int)
    37     {
    38       override def toString: String = name
    39     }
    40 
    41     sealed case class Info(
    42       dir: Path,
    43       description: String,
    44       options: Options,
    45       theories: List[(Options, String)],
    46       files: List[String])
    47 
    48     object Queue
    49     {
    50       val empty: Queue = new Queue()
    51     }
    52 
    53     final class Queue private(
    54       keys: Map[String, Key] = Map.empty,
    55       graph: Graph[Key, Info] = Graph.empty(Key.Ordering))
    56     {
    57       def defined(name: String): Boolean = keys.isDefinedAt(name)
    58 
    59       def + (key: Key, info: Info, parent: Option[String]): Queue =
    60       {
    61         val keys1 =
    62           if (defined(key.name)) error("Duplicate session: " + quote(key.name))
    63           else keys + (key.name -> key)
    64 
    65         val graph1 =
    66           try {
    67             graph.new_node(key, info).add_deps_acyclic(key, parent.toList.map(keys(_)))
    68           }
    69           catch {
    70             case exn: Graph.Cycles[_] =>
    71               error(cat_lines(exn.cycles.map(cycle =>
    72                 "Cyclic session dependency of " +
    73                   cycle.map(key => quote(key.toString)).mkString(" via "))))
    74           }
    75         new Queue(keys1, graph1)
    76       }
    77 
    78       def topological_order: List[(Key, Info)] =
    79         graph.topological_order.map(key => (key, graph.get_node(key)))
    80     }
    81   }
    82 
    83 
    84   /* parsing */
    85 
    86   val ROOT_NAME = "ROOT"
    87 
    88   private case class Session_Entry(
    89     name: String,
    90     reset: Boolean,
    91     order: Int,
    92     path: Option[String],
    93     parent: Option[String],
    94     description: String,
    95     options: Options,
    96     theories: List[(Options, List[String])],
    97     files: List[String])
    98 
    99   private object Parser extends Parse.Parser
   100   {
   101     val SESSION = "session"
   102     val IN = "in"
   103     val DESCRIPTION = "description"
   104     val OPTIONS = "options"
   105     val THEORIES = "theories"
   106     val FILES = "files"
   107 
   108     val syntax =
   109       Outer_Syntax.empty + "!" + "(" + ")" + "+" + "," + "=" + "[" + "]" +
   110         SESSION + IN + DESCRIPTION + OPTIONS + THEORIES + FILES
   111 
   112     val session_entry: Parser[Session_Entry] =
   113     {
   114       val session_name = atom("session name", _.is_name)
   115       val theory_name = atom("theory name", _.is_name)
   116 
   117       val option =
   118         name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) }
   119       val options = keyword("[") ~> repsep(option, keyword(",")) <~ keyword("]")
   120 
   121       val theories =
   122         keyword(THEORIES) ~! ((options | success(Nil)) ~ rep1(theory_name)) ^^
   123           { case _ ~ (x ~ y) => (x, y) }
   124 
   125       ((keyword(SESSION) ~! session_name) ^^ { case _ ~ x => x }) ~
   126         (keyword("!") ^^^ true | success(false)) ~
   127         (keyword("(") ~! (nat <~ keyword(")")) ^^ { case _ ~ x => x } | success(Integer.MAX_VALUE)) ~
   128         (opt(keyword(IN) ~! string ^^ { case _ ~ x => x })) ~
   129         (keyword("=") ~> opt(session_name <~ keyword("+"))) ~
   130         (keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x } | success("")) ~
   131         (keyword(OPTIONS) ~! options ^^ { case _ ~ x => x } | success(Nil)) ~
   132         rep(theories) ~
   133         (keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x } | success(Nil)) ^^
   134           { case a ~ b ~ c ~ d ~ e ~ f ~ g ~ h ~ i => Session_Entry(a, b, c, d, e, f, g, h, i) }
   135     }
   136 
   137     def parse_entries(root: File): List[Session_Entry] =
   138     {
   139       val toks = syntax.scan(Standard_System.read_file(root))
   140       parse_all(rep(session_entry), Token.reader(toks, root.toString)) match {
   141         case Success(result, _) => result
   142         case bad => error(bad.toString)
   143       }
   144     }
   145   }
   146 
   147 
   148   /* find sessions */
   149 
   150   def find_sessions(more_dirs: List[Path]): Session.Queue =
   151   {
   152     var sessions = Session.Queue.empty
   153 
   154     for {
   155       (dir, strict) <- Isabelle_System.components().map((_, false)) ++ more_dirs.map((_, true))
   156       root = Isabelle_System.platform_file(dir + Path.basic(ROOT_NAME))
   157       _ = (strict && !root.isFile && error("Bad session root file: " + quote(root.toString)))
   158       if root.isFile
   159       entry <- Parser.parse_entries(root)
   160     }
   161     {
   162       try {
   163         if (entry.name == "") error("Bad session name")
   164 
   165         val full_name =
   166           if (entry.name == "RAW" || entry.name == "Pure") {
   167             if (entry.parent.isDefined) error("Illegal parent session")
   168             else entry.name
   169           }
   170           else
   171             entry.parent match {
   172               case Some(parent_name) if sessions.defined(parent_name) =>
   173                 if (entry.reset) entry.name
   174                 else parent_name + "-" + entry.name
   175               case _ => error("Bad parent session")
   176             }
   177 
   178         val path =
   179           entry.path match {
   180             case Some(p) => Path.explode(p)
   181             case None => Path.basic(entry.name)
   182           }
   183 
   184         val key = Session.Key(full_name, entry.order)
   185         val info = Session.Info(dir + path, entry.description, entry.options,
   186           entry.theories.map({ case (x, ys) => ys.map(y => (x, y)) }).flatten, entry.files)
   187 
   188         sessions += (key, info, entry.parent)
   189       }
   190       catch {
   191         case ERROR(msg) =>
   192           error(msg + "\nThe error(s) above occurred in session entry " +
   193             quote(entry.name) + " (file " + quote(root.toString) + ")")
   194       }
   195     }
   196     sessions
   197   }
   198 
   199 
   200 
   201   /** build **/
   202 
   203   def build(all_sessions: Boolean, build_images: Boolean, list_only: Boolean,
   204     more_dirs: List[Path], options: List[String], sessions: List[String]): Int =
   205   {
   206     println("more_dirs = " + more_dirs.toString)
   207     println("options = " + options.toString)
   208     println("sessions = " + sessions.toString)
   209 
   210     for ((key, info) <- find_sessions(more_dirs).topological_order)
   211       println(key.name + " in " + info.dir)
   212 
   213     0
   214   }
   215 
   216 
   217   /* command line entry point */
   218 
   219   def main(args: Array[String])
   220   {
   221     Command_Line.tool {
   222       args.toList match {
   223         case
   224           Properties.Value.Boolean(all_sessions) ::
   225           Properties.Value.Boolean(build_images) ::
   226           Properties.Value.Boolean(list_only) ::
   227           Command_Line.Chunks(more_dirs, options, sessions) =>
   228             build(all_sessions, build_images, list_only,
   229               more_dirs.map(Path.explode), options, sessions)
   230         case _ => error("Bad arguments:\n" + cat_lines(args))
   231       }
   232     }
   233   }
   234 }
   235