| author | nipkow | 
| Fri, 10 Apr 2015 12:16:58 +0200 | |
| changeset 59999 | 3fa68bacfa2b | 
| parent 59735 | 24bee1b11fce | 
| child 60215 | 5fb4990dfc73 | 
| permissions | -rw-r--r-- | 
| 56385 | 1 | /* Title: Pure/PIDE/prover.scala | 
| 2 | Author: Makarius | |
| 57923 | 3 | Options: :folding=explicit: | 
| 56385 | 4 | |
| 57923 | 5 | Prover process wrapping. | 
| 56385 | 6 | */ | 
| 7 | ||
| 8 | package isabelle | |
| 9 | ||
| 10 | ||
| 57916 | 11 | import java.io.{InputStream, OutputStream, BufferedReader, BufferedOutputStream, IOException}
 | 
| 57915 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 12 | |
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 13 | |
| 56385 | 14 | object Prover | 
| 15 | {
 | |
| 56393 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 16 | /* syntax */ | 
| 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 17 | |
| 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 18 | trait Syntax | 
| 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 19 |   {
 | 
| 59077 
7e0d3da6e6d8
node-specific syntax, with base_syntax as default;
 wenzelm parents: 
57923diff
changeset | 20 | def ++ (other: Syntax): Syntax | 
| 56393 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 21 | def add_keywords(keywords: Thy_Header.Keywords): Syntax | 
| 57906 | 22 | def parse_spans(input: CharSequence): List[Command_Span.Span] | 
| 57901 
e1abca2527da
more explicit type Span in Scala, according to ML version;
 wenzelm parents: 
56394diff
changeset | 23 | def load_command(name: String): Option[List[String]] | 
| 56393 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 24 | def load_commands_in(text: String): Boolean | 
| 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 25 | } | 
| 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 26 | |
| 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 27 | |
| 57915 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 28 | /* underlying system process */ | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 29 | |
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 30 | trait System_Process | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 31 |   {
 | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 32 | def stdout: BufferedReader | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 33 | def stderr: BufferedReader | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 34 | def terminate: Unit | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 35 | def join: Int | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 36 | } | 
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 37 | |
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 38 | |
| 56385 | 39 | /* messages */ | 
| 40 | ||
| 41 | sealed abstract class Message | |
| 42 | ||
| 56386 | 43 | class Input(val name: String, val args: List[String]) extends Message | 
| 56385 | 44 |   {
 | 
| 45 | override def toString: String = | |
| 46 | XML.Elem(Markup(Markup.PROVER_COMMAND, List((Markup.NAME, name))), | |
| 47 | args.map(s => | |
| 48 |           List(XML.Text("\n"), XML.elem(Markup.PROVER_ARG, YXML.parse_body(s)))).flatten).toString
 | |
| 49 | } | |
| 50 | ||
| 51 | class Output(val message: XML.Elem) extends Message | |
| 52 |   {
 | |
| 53 | def kind: String = message.markup.name | |
| 54 | def properties: Properties.T = message.markup.properties | |
| 55 | def body: XML.Body = message.body | |
| 56 | ||
| 57 | def is_init = kind == Markup.INIT | |
| 58 | def is_exit = kind == Markup.EXIT | |
| 59 | def is_stdout = kind == Markup.STDOUT | |
| 60 | def is_stderr = kind == Markup.STDERR | |
| 61 | def is_system = kind == Markup.SYSTEM | |
| 62 | def is_status = kind == Markup.STATUS | |
| 63 | def is_report = kind == Markup.REPORT | |
| 64 | def is_syslog = is_init || is_exit || is_system || is_stderr | |
| 65 | ||
| 66 | override def toString: String = | |
| 67 |     {
 | |
| 68 | val res = | |
| 69 | if (is_status || is_report) message.body.map(_.toString).mkString | |
| 70 | else Pretty.string_of(message.body) | |
| 71 | if (properties.isEmpty) | |
| 72 | kind.toString + " [[" + res + "]]" | |
| 73 | else | |
| 74 | kind.toString + " " + | |
| 75 |           (for ((x, y) <- properties) yield x + "=" + y).mkString("{", ",", "}") + " [[" + res + "]]"
 | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 79 | class Protocol_Output(props: Properties.T, val bytes: Bytes) | |
| 80 | extends Output(XML.Elem(Markup(Markup.PROTOCOL, props), Nil)) | |
| 81 |   {
 | |
| 82 | lazy val text: String = bytes.toString | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 56393 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 86 | |
| 57916 | 87 | abstract class Prover( | 
| 88 | receiver: Prover.Message => Unit, | |
| 57917 | 89 | system_channel: System_Channel, | 
| 57916 | 90 | system_process: Prover.System_Process) extends Protocol | 
| 56387 | 91 | {
 | 
| 57923 | 92 | /** receiver output **/ | 
| 57916 | 93 | |
| 94 | val xml_cache: XML.Cache = new XML.Cache() | |
| 95 | ||
| 96 | private def system_output(text: String) | |
| 97 |   {
 | |
| 98 | receiver(new Prover.Output(XML.Elem(Markup(Markup.SYSTEM, Nil), List(XML.Text(text))))) | |
| 99 | } | |
| 100 | ||
| 101 | private def protocol_output(props: Properties.T, bytes: Bytes) | |
| 102 |   {
 | |
| 103 | receiver(new Prover.Protocol_Output(props, bytes)) | |
| 104 | } | |
| 105 | ||
| 106 | private def output(kind: String, props: Properties.T, body: XML.Body) | |
| 107 |   {
 | |
| 57917 | 108 | if (kind == Markup.INIT) system_channel.accepted() | 
| 56387 | 109 | |
| 59713 | 110 | val main = XML.Elem(Markup(kind, props), Protocol_Message.clean_reports(body)) | 
| 111 | val reports = Protocol_Message.reports(props, body) | |
| 57916 | 112 | for (msg <- main :: reports) receiver(new Prover.Output(xml_cache.elem(msg))) | 
| 113 | } | |
| 114 | ||
| 115 | private def exit_message(rc: Int) | |
| 116 |   {
 | |
| 117 |     output(Markup.EXIT, Markup.Return_Code(rc), List(XML.Text("Return code: " + rc.toString)))
 | |
| 118 | } | |
| 119 | ||
| 120 | ||
| 56387 | 121 | |
| 57916 | 122 | /** process manager **/ | 
| 123 | ||
| 124 | private val (_, process_result) = | |
| 125 |     Simple_Thread.future("process_result") { system_process.join }
 | |
| 126 | ||
| 127 | private def terminate_process() | |
| 56387 | 128 |   {
 | 
| 57916 | 129 |     try { system_process.terminate }
 | 
| 130 |     catch {
 | |
| 131 |       case exn @ ERROR(_) => system_output("Failed to terminate prover process: " + exn.getMessage)
 | |
| 132 | } | |
| 56387 | 133 | } | 
| 134 | ||
| 57916 | 135 |   private val process_manager = Simple_Thread.fork("process_manager")
 | 
| 136 |   {
 | |
| 137 | val (startup_failed, startup_errors) = | |
| 138 |     {
 | |
| 139 | var finished: Option[Boolean] = None | |
| 140 | val result = new StringBuilder(100) | |
| 141 |       while (finished.isEmpty && (system_process.stderr.ready || !process_result.is_finished)) {
 | |
| 142 |         while (finished.isEmpty && system_process.stderr.ready) {
 | |
| 143 |           try {
 | |
| 144 | val c = system_process.stderr.read | |
| 145 | if (c == 2) finished = Some(true) | |
| 146 | else result += c.toChar | |
| 147 | } | |
| 148 |           catch { case _: IOException => finished = Some(false) }
 | |
| 149 | } | |
| 150 | Thread.sleep(10) | |
| 151 | } | |
| 152 | (finished.isEmpty || !finished.get, result.toString.trim) | |
| 153 | } | |
| 154 | if (startup_errors != "") system_output(startup_errors) | |
| 155 | ||
| 156 |     if (startup_failed) {
 | |
| 157 | terminate_process() | |
| 158 | process_result.join | |
| 159 | exit_message(127) | |
| 160 | } | |
| 161 |     else {
 | |
| 57917 | 162 | val (command_stream, message_stream) = system_channel.rendezvous() | 
| 57916 | 163 | |
| 164 | command_input_init(command_stream) | |
| 165 | val stdout = physical_output(false) | |
| 166 | val stderr = physical_output(true) | |
| 167 | val message = message_output(message_stream) | |
| 168 | ||
| 169 | val rc = process_result.join | |
| 170 |       system_output("process terminated")
 | |
| 171 | command_input_close() | |
| 172 | for (thread <- List(stdout, stderr, message)) thread.join | |
| 173 |       system_output("process_manager terminated")
 | |
| 174 | exit_message(rc) | |
| 175 | } | |
| 57917 | 176 | system_channel.accepted() | 
| 57916 | 177 | } | 
| 178 | ||
| 179 | ||
| 180 | /* management methods */ | |
| 181 | ||
| 182 |   def join() { process_manager.join() }
 | |
| 183 | ||
| 184 | def terminate() | |
| 185 |   {
 | |
| 186 | command_input_close() | |
| 187 |     system_output("Terminating prover process")
 | |
| 188 | terminate_process() | |
| 189 | } | |
| 190 | ||
| 191 | ||
| 192 | ||
| 193 | /** process streams **/ | |
| 194 | ||
| 195 | /* command input */ | |
| 196 | ||
| 197 | private var command_input: Option[Consumer_Thread[List[Bytes]]] = None | |
| 198 | ||
| 199 | private def command_input_close(): Unit = command_input.foreach(_.shutdown) | |
| 200 | ||
| 201 | private def command_input_init(raw_stream: OutputStream) | |
| 202 |   {
 | |
| 203 | val name = "command_input" | |
| 204 | val stream = new BufferedOutputStream(raw_stream) | |
| 205 | command_input = | |
| 206 | Some( | |
| 207 | Consumer_Thread.fork(name)( | |
| 208 | consume = | |
| 209 |             {
 | |
| 210 | case chunks => | |
| 211 |                 try {
 | |
| 212 |                   Bytes(chunks.map(_.length).mkString("", ",", "\n")).write(stream)
 | |
| 213 | chunks.foreach(_.write(stream)) | |
| 214 | stream.flush | |
| 215 | true | |
| 216 | } | |
| 217 |                 catch { case e: IOException => system_output(name + ": " + e.getMessage); false }
 | |
| 218 | }, | |
| 219 |           finish = { case () => stream.close; system_output(name + " terminated") }
 | |
| 220 | ) | |
| 221 | ) | |
| 222 | } | |
| 56387 | 223 | |
| 224 | ||
| 57916 | 225 | /* physical output */ | 
| 226 | ||
| 227 | private def physical_output(err: Boolean): Thread = | |
| 228 |   {
 | |
| 229 | val (name, reader, markup) = | |
| 230 |       if (err) ("standard_error", system_process.stderr, Markup.STDERR)
 | |
| 231 |       else ("standard_output", system_process.stdout, Markup.STDOUT)
 | |
| 56387 | 232 | |
| 57916 | 233 |     Simple_Thread.fork(name) {
 | 
| 234 |       try {
 | |
| 235 | var result = new StringBuilder(100) | |
| 236 | var finished = false | |
| 237 |         while (!finished) {
 | |
| 238 |           //{{{
 | |
| 239 | var c = -1 | |
| 240 | var done = false | |
| 241 |           while (!done && (result.length == 0 || reader.ready)) {
 | |
| 242 | c = reader.read | |
| 243 | if (c >= 0) result.append(c.asInstanceOf[Char]) | |
| 244 | else done = true | |
| 245 | } | |
| 246 |           if (result.length > 0) {
 | |
| 247 | output(markup, Nil, List(XML.Text(decode(result.toString)))) | |
| 248 | result.length = 0 | |
| 249 | } | |
| 250 |           else {
 | |
| 251 | reader.close | |
| 252 | finished = true | |
| 253 | } | |
| 254 | //}}} | |
| 255 | } | |
| 256 | } | |
| 257 |       catch { case e: IOException => system_output(name + ": " + e.getMessage) }
 | |
| 258 | system_output(name + " terminated") | |
| 259 | } | |
| 260 | } | |
| 56387 | 261 | |
| 262 | ||
| 57916 | 263 | /* message output */ | 
| 264 | ||
| 265 | private def message_output(stream: InputStream): Thread = | |
| 266 |   {
 | |
| 267 | class EOF extends Exception | |
| 268 | class Protocol_Error(msg: String) extends Exception(msg) | |
| 269 | ||
| 270 | val name = "message_output" | |
| 271 |     Simple_Thread.fork(name) {
 | |
| 272 | val default_buffer = new Array[Byte](65536) | |
| 273 | var c = -1 | |
| 56387 | 274 | |
| 57916 | 275 | def read_int(): Int = | 
| 276 |       //{{{
 | |
| 277 |       {
 | |
| 278 | var n = 0 | |
| 279 | c = stream.read | |
| 280 | if (c == -1) throw new EOF | |
| 281 |         while (48 <= c && c <= 57) {
 | |
| 282 | n = 10 * n + (c - 48) | |
| 283 | c = stream.read | |
| 284 | } | |
| 285 | if (c != 10) | |
| 286 |           throw new Protocol_Error("malformed header: expected integer followed by newline")
 | |
| 287 | else n | |
| 288 | } | |
| 289 | //}}} | |
| 56387 | 290 | |
| 57916 | 291 | def read_chunk_bytes(): (Array[Byte], Int) = | 
| 292 |       //{{{
 | |
| 293 |       {
 | |
| 294 | val n = read_int() | |
| 295 | val buf = | |
| 296 | if (n <= default_buffer.size) default_buffer | |
| 297 | else new Array[Byte](n) | |
| 298 | ||
| 299 | var i = 0 | |
| 300 | var m = 0 | |
| 301 |         do {
 | |
| 302 | m = stream.read(buf, i, n - i) | |
| 303 | if (m != -1) i += m | |
| 304 | } | |
| 305 | while (m != -1 && n > i) | |
| 306 | ||
| 307 | if (i != n) | |
| 308 |           throw new Protocol_Error("bad chunk (unexpected EOF after " + i + " of " + n + " bytes)")
 | |
| 309 | ||
| 310 | (buf, n) | |
| 311 | } | |
| 312 | //}}} | |
| 56387 | 313 | |
| 57916 | 314 | def read_chunk(): XML.Body = | 
| 315 |       {
 | |
| 316 | val (buf, n) = read_chunk_bytes() | |
| 317 | YXML.parse_body_failsafe(UTF8.decode_chars(decode, buf, 0, n)) | |
| 318 | } | |
| 56387 | 319 | |
| 57916 | 320 |       try {
 | 
| 321 |         do {
 | |
| 322 |           try {
 | |
| 323 | val header = read_chunk() | |
| 324 |             header match {
 | |
| 325 | case List(XML.Elem(Markup(name, props), Nil)) => | |
| 326 | val kind = name.intern | |
| 327 |                 if (kind == Markup.PROTOCOL) {
 | |
| 328 | val (buf, n) = read_chunk_bytes() | |
| 329 | protocol_output(props, Bytes(buf, 0, n)) | |
| 330 | } | |
| 331 |                 else {
 | |
| 332 | val body = read_chunk() | |
| 333 | output(kind, props, body) | |
| 334 | } | |
| 335 | case _ => | |
| 336 | read_chunk() | |
| 337 |                 throw new Protocol_Error("bad header: " + header.toString)
 | |
| 338 | } | |
| 339 | } | |
| 340 |           catch { case _: EOF => }
 | |
| 341 | } | |
| 342 | while (c != -1) | |
| 343 | } | |
| 344 |       catch {
 | |
| 345 |         case e: IOException => system_output("Cannot read message:\n" + e.getMessage)
 | |
| 346 |         case e: Protocol_Error => system_output("Malformed message:\n" + e.getMessage)
 | |
| 347 | } | |
| 348 | stream.close | |
| 56387 | 349 | |
| 57916 | 350 | system_output(name + " terminated") | 
| 351 | } | |
| 352 | } | |
| 353 | ||
| 354 | ||
| 355 | ||
| 356 | /** protocol commands **/ | |
| 357 | ||
| 358 | def protocol_command_bytes(name: String, args: Bytes*): Unit = | |
| 359 |     command_input match {
 | |
| 360 | case Some(thread) => thread.send(Bytes(name) :: args.toList) | |
| 361 |       case None => error("Uninitialized command input thread")
 | |
| 362 | } | |
| 363 | ||
| 364 | def protocol_command(name: String, args: String*) | |
| 365 |   {
 | |
| 366 | receiver(new Prover.Input(name, args.toList)) | |
| 367 | protocol_command_bytes(name, args.map(Bytes(_)): _*) | |
| 368 | } | |
| 56387 | 369 | } | 
| 370 |