18 |
18 |
19 object Build |
19 object Build |
20 { |
20 { |
21 /** session information **/ |
21 /** session information **/ |
22 |
22 |
|
23 // external version |
|
24 sealed case class Session_Entry( |
|
25 pos: Position.T, |
|
26 base_name: String, |
|
27 this_name: Boolean, |
|
28 groups: List[String], |
|
29 path: Option[String], |
|
30 parent: Option[String], |
|
31 description: String, |
|
32 options: List[Options.Spec], |
|
33 theories: List[(List[Options.Spec], List[String])], |
|
34 files: List[String]) |
|
35 |
|
36 // internal version |
|
37 sealed case class Session_Info( |
|
38 pos: Position.T, |
|
39 groups: List[String], |
|
40 dir: Path, |
|
41 parent: Option[String], |
|
42 description: String, |
|
43 options: Options, |
|
44 theories: List[(Options, List[Path])], |
|
45 files: List[Path], |
|
46 entry_digest: SHA1.Digest) |
|
47 |
|
48 def is_pure(name: String): Boolean = name == "RAW" || name == "Pure" |
|
49 |
|
50 def session_info(options: Options, dir: Path, entry: Session_Entry): (String, Session_Info) = |
|
51 try { |
|
52 if (entry.base_name == "") error("Bad session name") |
|
53 |
|
54 val full_name = |
|
55 if (is_pure(entry.base_name)) { |
|
56 if (entry.parent.isDefined) error("Illegal parent session") |
|
57 else entry.base_name |
|
58 } |
|
59 else |
|
60 entry.parent match { |
|
61 case None => error("Missing parent session") |
|
62 case Some(parent_name) => |
|
63 if (entry.this_name) entry.base_name |
|
64 else parent_name + "-" + entry.base_name |
|
65 } |
|
66 |
|
67 val path = |
|
68 entry.path match { |
|
69 case Some(p) => Path.explode(p) |
|
70 case None => Path.basic(entry.base_name) |
|
71 } |
|
72 |
|
73 val session_options = options ++ entry.options |
|
74 |
|
75 val theories = |
|
76 entry.theories.map({ case (opts, thys) => |
|
77 (session_options ++ opts, thys.map(Path.explode(_))) }) |
|
78 val files = entry.files.map(Path.explode(_)) |
|
79 val entry_digest = |
|
80 SHA1.digest((full_name, entry.parent, entry.options, entry.theories).toString) |
|
81 |
|
82 val info = |
|
83 Session_Info(entry.pos, entry.groups, dir + path, entry.parent, entry.description, |
|
84 session_options, theories, files, entry_digest) |
|
85 |
|
86 (full_name, info) |
|
87 } |
|
88 catch { |
|
89 case ERROR(msg) => |
|
90 error(msg + "\nThe error(s) above occurred in session entry " + |
|
91 quote(entry.base_name) + Position.str_of(entry.pos)) |
|
92 } |
|
93 |
|
94 |
|
95 /* parser */ |
|
96 |
|
97 private object Parser extends Parse.Parser |
|
98 { |
|
99 val SESSION = "session" |
|
100 val IN = "in" |
|
101 val DESCRIPTION = "description" |
|
102 val OPTIONS = "options" |
|
103 val THEORIES = "theories" |
|
104 val FILES = "files" |
|
105 |
|
106 val syntax = |
|
107 Outer_Syntax.empty + "!" + "(" + ")" + "+" + "," + "=" + "[" + "]" + |
|
108 SESSION + IN + DESCRIPTION + OPTIONS + THEORIES + FILES |
|
109 |
|
110 def session_entry(pos: Position.T): Parser[Session_Entry] = |
|
111 { |
|
112 val session_name = atom("session name", _.is_name) |
|
113 |
|
114 val option = |
|
115 name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) } |
|
116 val options = keyword("[") ~> repsep(option, keyword(",")) <~ keyword("]") |
|
117 |
|
118 val theories = |
|
119 keyword(THEORIES) ~! ((options | success(Nil)) ~ rep1(theory_name)) ^^ |
|
120 { case _ ~ (x ~ y) => (x, y) } |
|
121 |
|
122 ((keyword(SESSION) ~! session_name) ^^ { case _ ~ x => x }) ~ |
|
123 (keyword("!") ^^^ true | success(false)) ~ |
|
124 (keyword("(") ~! (rep1(name) <~ keyword(")")) ^^ { case _ ~ x => x } | success(Nil)) ~ |
|
125 (opt(keyword(IN) ~! path ^^ { case _ ~ x => x })) ~ |
|
126 (keyword("=") ~> opt(session_name <~ keyword("+"))) ~ |
|
127 (keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x } | success("")) ~ |
|
128 (keyword(OPTIONS) ~! options ^^ { case _ ~ x => x } | success(Nil)) ~ |
|
129 rep(theories) ~ |
|
130 (keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x } | success(Nil)) ^^ |
|
131 { case a ~ b ~ c ~ d ~ e ~ f ~ g ~ h ~ i => Session_Entry(pos, a, b, c, d, e, f, g, h, i) } |
|
132 } |
|
133 |
|
134 def parse_entries(root: Path): List[Session_Entry] = |
|
135 { |
|
136 val toks = syntax.scan(File.read(root)) |
|
137 parse_all(rep(session_entry(root.position)), Token.reader(toks, root.implode)) match { |
|
138 case Success(result, _) => result |
|
139 case bad => error(bad.toString) |
|
140 } |
|
141 } |
|
142 } |
|
143 |
|
144 |
|
145 /* find sessions within certain directories */ |
|
146 |
|
147 private val ROOT = Path.explode("ROOT") |
|
148 |
|
149 private def is_session_dir(dir: Path): Boolean = (dir + ROOT).is_file |
|
150 |
|
151 private def sessions_root(options: Options, dir: Path): List[(String, Session_Info)] = |
|
152 { |
|
153 val root = dir + ROOT |
|
154 if (root.is_file) Parser.parse_entries(dir + ROOT).map(session_info(options, dir, _)) |
|
155 else error("Bad session root file: " + root.toString) |
|
156 } |
|
157 |
|
158 def find_sessions(options: Options, more_dirs: List[Path]): List[(String, Session_Info)] = |
|
159 { |
|
160 val dirs = Isabelle_System.components().filter(is_session_dir(_)) ::: more_dirs |
|
161 dirs.map(sessions_root(options, _)).flatten |
|
162 } |
|
163 |
|
164 |
23 object Session |
165 object Session |
24 { |
166 { |
25 /* Info */ |
|
26 |
|
27 sealed case class Info( |
|
28 groups: List[String], |
|
29 dir: Path, |
|
30 parent: Option[String], |
|
31 description: String, |
|
32 options: Options, |
|
33 theories: List[(Options, List[Path])], |
|
34 files: List[Path], |
|
35 entry_digest: SHA1.Digest) |
|
36 |
|
37 |
|
38 /* Queue */ |
|
39 |
|
40 object Queue |
167 object Queue |
41 { |
168 { |
42 val empty: Queue = new Queue() |
169 val empty: Queue = new Queue() |
43 } |
170 def apply(args: Seq[(String, Session_Info)]): Queue = |
44 |
171 (empty /: args) { case (queue, (name, info)) => queue + (name, info) } |
45 final class Queue private(graph: Graph[String, Option[Info]] = Graph.string) |
172 } |
46 extends PartialFunction[String, Info] |
173 |
|
174 final class Queue private(graph: Graph[String, Option[Session_Info]] = Graph.string) |
|
175 extends PartialFunction[String, Session_Info] |
47 { |
176 { |
48 def apply(name: String): Info = graph.get_node(name).get |
177 def apply(name: String): Session_Info = graph.get_node(name).get |
49 def isDefinedAt(name: String): Boolean = |
178 def isDefinedAt(name: String): Boolean = |
50 graph.defined(name) && graph.get_node(name).isDefined |
179 graph.defined(name) && graph.get_node(name).isDefined |
51 |
180 |
52 def is_inner(name: String): Boolean = !graph.is_maximal(name) |
181 def is_inner(name: String): Boolean = !graph.is_maximal(name) |
53 |
182 |
54 def is_empty: Boolean = graph.is_empty |
183 def is_empty: Boolean = graph.is_empty |
55 |
184 |
56 def + (name: String, info: Info): Queue = |
185 def + (name: String, info: Session_Info): Queue = |
57 { |
186 { |
58 val parents = info.parent.toList |
187 val parents = info.parent.toList |
59 |
188 |
60 val graph1 = (graph /: (name :: parents))(_.default_node(_, None)) |
189 val graph1 = (graph /: (name :: parents))(_.default_node(_, None)) |
61 if (graph1.get_node(name).isDefined) |
190 if (graph1.get_node(name).isDefined) |
62 error("Duplicate session: " + quote(name)) |
191 error("Duplicate session: " + quote(name) + Position.str_of(info.pos)) |
63 |
192 |
64 new Queue( |
193 new Queue( |
65 try { graph1.map_node(name, _ => Some(info)).add_deps_acyclic(name, parents) } |
194 try { graph1.map_node(name, _ => Some(info)).add_deps_acyclic(name, parents) } |
66 catch { |
195 catch { |
67 case exn: Graph.Cycles[_] => |
196 case exn: Graph.Cycles[_] => |
104 val descendants = graph.all_succs(selected) |
233 val descendants = graph.all_succs(selected) |
105 val queue1 = new Queue(graph.restrict(graph.all_preds(selected).toSet)) |
234 val queue1 = new Queue(graph.restrict(graph.all_preds(selected).toSet)) |
106 (descendants, queue1) |
235 (descendants, queue1) |
107 } |
236 } |
108 |
237 |
109 def dequeue(skip: String => Boolean): Option[(String, Info)] = |
238 def dequeue(skip: String => Boolean): Option[(String, Session_Info)] = |
110 { |
239 { |
111 val it = graph.entries.dropWhile( |
240 val it = graph.entries.dropWhile( |
112 { case (name, (info, (deps, _))) => !deps.isEmpty || info.isEmpty || skip(name) }) |
241 { case (name, (info, (deps, _))) => !deps.isEmpty || info.isEmpty || skip(name) }) |
113 if (it.hasNext) { val (name, (info, _)) = it.next; Some((name, info.get)) } |
242 if (it.hasNext) { val (name, (info, _)) = it.next; Some((name, info.get)) } |
114 else None |
243 else None |
115 } |
244 } |
116 |
245 |
117 def topological_order: List[(String, Info)] = |
246 def topological_order: List[(String, Session_Info)] = |
118 graph.topological_order.map(name => (name, apply(name))) |
247 graph.topological_order.map(name => (name, apply(name))) |
119 } |
248 } |
120 } |
|
121 |
|
122 |
|
123 /* parsing */ |
|
124 |
|
125 private case class Session_Entry( |
|
126 base_name: String, |
|
127 this_name: Boolean, |
|
128 groups: List[String], |
|
129 path: Option[String], |
|
130 parent: Option[String], |
|
131 description: String, |
|
132 options: List[Options.Spec], |
|
133 theories: List[(List[Options.Spec], List[String])], |
|
134 files: List[String]) |
|
135 |
|
136 private object Parser extends Parse.Parser |
|
137 { |
|
138 val SESSION = "session" |
|
139 val IN = "in" |
|
140 val DESCRIPTION = "description" |
|
141 val OPTIONS = "options" |
|
142 val THEORIES = "theories" |
|
143 val FILES = "files" |
|
144 |
|
145 val syntax = |
|
146 Outer_Syntax.empty + "!" + "(" + ")" + "+" + "," + "=" + "[" + "]" + |
|
147 SESSION + IN + DESCRIPTION + OPTIONS + THEORIES + FILES |
|
148 |
|
149 val session_entry: Parser[Session_Entry] = |
|
150 { |
|
151 val session_name = atom("session name", _.is_name) |
|
152 |
|
153 val option = |
|
154 name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) } |
|
155 val options = keyword("[") ~> repsep(option, keyword(",")) <~ keyword("]") |
|
156 |
|
157 val theories = |
|
158 keyword(THEORIES) ~! ((options | success(Nil)) ~ rep1(theory_name)) ^^ |
|
159 { case _ ~ (x ~ y) => (x, y) } |
|
160 |
|
161 ((keyword(SESSION) ~! session_name) ^^ { case _ ~ x => x }) ~ |
|
162 (keyword("!") ^^^ true | success(false)) ~ |
|
163 (keyword("(") ~! (rep1(name) <~ keyword(")")) ^^ { case _ ~ x => x } | success(Nil)) ~ |
|
164 (opt(keyword(IN) ~! path ^^ { case _ ~ x => x })) ~ |
|
165 (keyword("=") ~> opt(session_name <~ keyword("+"))) ~ |
|
166 (keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x } | success("")) ~ |
|
167 (keyword(OPTIONS) ~! options ^^ { case _ ~ x => x } | success(Nil)) ~ |
|
168 rep(theories) ~ |
|
169 (keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x } | success(Nil)) ^^ |
|
170 { case a ~ b ~ c ~ d ~ e ~ f ~ g ~ h ~ i => Session_Entry(a, b, c, d, e, f, g, h, i) } |
|
171 } |
|
172 |
|
173 def parse_entries(root: Path): List[Session_Entry] = |
|
174 { |
|
175 val toks = syntax.scan(File.read(root)) |
|
176 parse_all(rep(session_entry), Token.reader(toks, root.implode)) match { |
|
177 case Success(result, _) => result |
|
178 case bad => error(bad.toString) |
|
179 } |
|
180 } |
|
181 } |
|
182 |
|
183 |
|
184 /* find sessions */ |
|
185 |
|
186 private val ROOT = Path.explode("ROOT") |
|
187 private val SESSIONS = Path.explode("etc/sessions") |
|
188 |
|
189 private def is_pure(name: String): Boolean = name == "RAW" || name == "Pure" |
|
190 |
|
191 private def sessions_root(options: Options, dir: Path, queue: Session.Queue, root: Path) |
|
192 : Session.Queue = |
|
193 { |
|
194 (queue /: Parser.parse_entries(root))((queue1, entry) => |
|
195 try { |
|
196 if (entry.base_name == "") error("Bad session name") |
|
197 |
|
198 val full_name = |
|
199 if (is_pure(entry.base_name)) { |
|
200 if (entry.parent.isDefined) error("Illegal parent session") |
|
201 else entry.base_name |
|
202 } |
|
203 else |
|
204 entry.parent match { |
|
205 case None => error("Missing parent session") |
|
206 case Some(parent_name) => |
|
207 if (entry.this_name) entry.base_name |
|
208 else parent_name + "-" + entry.base_name |
|
209 } |
|
210 |
|
211 val path = |
|
212 entry.path match { |
|
213 case Some(p) => Path.explode(p) |
|
214 case None => Path.basic(entry.base_name) |
|
215 } |
|
216 |
|
217 val session_options = options ++ entry.options |
|
218 |
|
219 val theories = |
|
220 entry.theories.map({ case (opts, thys) => |
|
221 (session_options ++ opts, thys.map(Path.explode(_))) }) |
|
222 val files = entry.files.map(Path.explode(_)) |
|
223 val entry_digest = |
|
224 SHA1.digest((full_name, entry.parent, entry.options, entry.theories).toString) |
|
225 |
|
226 val info = |
|
227 Session.Info(entry.groups, dir + path, entry.parent, entry.description, |
|
228 session_options, theories, files, entry_digest) |
|
229 |
|
230 queue1 + (full_name, info) |
|
231 } |
|
232 catch { |
|
233 case ERROR(msg) => |
|
234 error(msg + "\nThe error(s) above occurred in session entry " + |
|
235 quote(entry.base_name) + Position.str_of(root.position)) |
|
236 }) |
|
237 } |
|
238 |
|
239 private def sessions_dir(options: Options, strict: Boolean, queue: Session.Queue, dir: Path) |
|
240 : Session.Queue = |
|
241 { |
|
242 val root = dir + ROOT |
|
243 if (root.is_file) sessions_root(options, dir, queue, root) |
|
244 else if (strict) error("Bad session root file: " + root.toString) |
|
245 else queue |
|
246 } |
|
247 |
|
248 def find_sessions(options: Options, more_dirs: List[Path]): Session.Queue = |
|
249 { |
|
250 val queue1 = |
|
251 (Session.Queue.empty /: Isabelle_System.components())(sessions_dir(options, false, _, _)) |
|
252 val queue2 = (queue1 /: more_dirs)(sessions_dir(options, true, _, _)) |
|
253 queue2 |
|
254 } |
249 } |
255 |
250 |
256 |
251 |
257 |
252 |
258 /** build **/ |
253 /** build **/ |