1(* 2 Title: Root function for the PolyML structure 3 Author: David Matthews 4 Copyright David Matthews 2009, 2015-17 5 6 This library is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License version 2.1 as published by the Free Software Foundation. 9 10 This library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18*) 19 20(* This contains the code for the IDE protocol as well as the normal 21 Poly/ML top-level loop. *) 22 23local 24 val parseTree = ref ("", []) (* Parsetree ID and parsetrees as a list. *) 25 26 fun runIDEProtocol () = 27 let 28 (* Save the last parsetree here. *) 29 val lastParsetree = 30 ref (case parseTree of ref(_, hd::_) => SOME hd | _ => NONE) 31 32 val parseLock = Thread.Mutex.mutex() 33 34 (* Access the parse tree and other information with the lock held. *) 35 fun withLock f = 36 let 37 open Thread.Thread Thread.Mutex 38 val originalState = getAttributes() 39 val () = setAttributes[InterruptState InterruptDefer] 40 val () = lock parseLock 41 val result = f () 42 val () = unlock parseLock 43 val () = setAttributes originalState 44 in 45 result 46 end 47 48 type basicLoc = (* Locations in request packets. *) { startOffset: FixedInt.int, endOffset: FixedInt.int } 49 type compileError = { hardError: bool, location: PolyML.location, message: PolyML.pretty } 50 51 datatype request = 52 (* Requests sent by the IDE to Poly/ML. *) 53 PropertyRequest (* O *) 54 of { requestId: string, parseTreeId: string, location: basicLoc } 55 | MoveRequest (* M *) 56 of { requestId: string, parseTreeId: string, location: basicLoc, direction: direction } 57 | TypeRequest (* T *) 58 of { requestId: string, parseTreeId: string, location: basicLoc } 59 | DecRequest (* I *) 60 of { requestId: string, parseTreeId: string, location: basicLoc, decType: dectype } 61 | RefRequest (* V *) 62 of { requestId: string, parseTreeId: string, location: basicLoc } 63 | CompileRequest (* R *) 64 of { requestId: string, fileName: string, startPosition: FixedInt.int, 65 preludeCode: string, sourceCode: string } 66 | KillRequest (* K *) 67 of { requestId: string } 68 | UnknownRequest (* Provided for upwards compatibility. *) 69 of { request: int, requestId: string} 70 71 and direction = DirUp | DirLeft | DirRight | DirDown 72 73 and dectype = DecLocal | DecOpen | DecParent 74 75 and response = 76 (* Replies sent from Poly/ML to the IDE. *) 77 PropertyResponse (* O *) 78 of { requestId: string, parseTreeId: string, location: basicLoc, commands: string list } 79 | MoveResponse (* M *) 80 of { requestId: string, parseTreeId: string, location: basicLoc } 81 | TypeResponse (* T *) 82 of { requestId: string, parseTreeId: string, location: basicLoc, typeRes: PolyML.pretty option } 83 | DecResponse (* I *) 84 of { requestId: string, parseTreeId: string, location: basicLoc, 85 decLocation: PolyML.location option } 86 | RefResponse (* V *) 87 of { requestId: string, parseTreeId: string, location: basicLoc, references: basicLoc list } 88 | CompilerResponse (* R *) 89 of { requestId: string, parseTreeId: string, finalOffset: FixedInt.int, result: compileResult } 90 | UnknownResponse (* Provided for upwards compatibility. *) 91 of { request: int, requestId: string } 92 93 and compileResult = 94 Succeeded of compileError list 95 | RuntimeException of PolyML.pretty * compileError list 96 | PreludeFail of string 97 | CompileFail of compileError list 98 | CompileCancelled of compileError list 99 100 101 val outputLock = Thread.Mutex.mutex() 102 103 val (readRequest, sendStartedMessage, sendResponse) = 104 case OS.Process.getEnv "POLYIDESOCKET" of 105 NONE => (* Version 1 protocol - backwards compatibility - use stdIn/stdOut *) 106 let 107 (* Separate out the output stream. We need to interlock access to stdOut 108 to avoid user code outputing within a packet. *) 109 open TextIO TextIO.StreamIO 110 val outStream = getOutstream stdOut 111 val (writer, buffMode) = getWriter outStream 112 val TextPrimIO.WR 113 { name, chunkSize, writeVec, writeArr, block, canOutput, ioDesc, ... } = writer 114 (* Create a version of the stream that locks before actually sending output. *) 115 val lockedWriteVec = 116 case writeVec of 117 NONE => NONE 118 | SOME writeVec => 119 SOME(fn a => ThreadLib.protect outputLock writeVec a) 120 val lockedWriteArray = 121 case writeArr of 122 NONE => NONE 123 | SOME writeArr => 124 SOME(fn a => ThreadLib.protect outputLock writeArr a) 125 val lockedWriter = 126 TextPrimIO.WR { name = name, chunkSize = chunkSize, 127 writeVec = lockedWriteVec, writeArr = lockedWriteArray, 128 writeVecNB = NONE, writeArrNB = NONE, block = block, canOutput = canOutput, 129 getPos = NONE, setPos = NONE, endPos = NONE, verifyPos = NONE, 130 close = fn () => raise Fail "stdOut must not be closed", ioDesc = ioDesc } 131 (* Use this locked version for normal stdOut. *) 132 val () = setOutstream(stdOut, 133 StreamIO.mkOutstream(TextPrimIO.augmentWriter lockedWriter, buffMode)) 134 (* Create an unlocked version for use within the IDE code. When writing to this 135 stream the IDE code will first get a lock, then output the whole packet before 136 releasing the lock. Because mutexes are not recursive we can't use the locking 137 version. *) 138 val unLockedWriter = 139 TextPrimIO.WR { name = name, chunkSize = chunkSize, writeVec = writeVec, writeArr = writeArr, 140 writeVecNB = NONE, writeArrNB = NONE, block = block, canOutput = canOutput, 141 getPos = NONE, setPos = NONE, endPos = NONE, verifyPos = NONE, 142 close = fn () => raise Fail "stdOut must not be closed", ioDesc = ioDesc } 143 144 val inStream = stdIn 145 146 val outStream = StreamIO.mkOutstream(TextPrimIO.augmentWriter unLockedWriter, buffMode) 147 148 fun protocolError error = 149 let 150 open OS.Process 151 in 152 TextIO.print ("Protocol error: " ^ error) handle _ => (); 153 exit failure; 154 raise Fail "bad" (* Never called but sets return type as 'a *) 155 end 156 157 (* Reads a request. Calls OS.Process.exit at end-of-file or on a protocol error. *) 158 fun readRequest (): request = 159 let 160 open TextIO 161 162 (* Returns the string as far as the next ESC and the terminator. *) 163 fun readToEscape (soFar: string, terminator) : string = 164 case input1 inStream of 165 SOME #"\u001b" => 166 ( 167 case input1 inStream of 168 NONE => protocolError "End of file" 169 | SOME ch => 170 if ch = terminator 171 then soFar 172 else if ch = #"\u001b" (* Escaped ESC. *) 173 then readToEscape(soFar ^ str #"\u001b", terminator) 174 else protocolError(str ch ^ " not " ^ str terminator) 175 ) 176 | SOME ch => readToEscape(soFar ^ str ch, terminator) 177 | NONE => protocolError "End of file" 178 179 (* Parse an integer. Returns zero if it isn't a valid int. *) 180 fun getInt termCh : FixedInt.int = 181 case FixedInt.fromString (readToEscape("", termCh)) of 182 NONE => 0 183 | SOME i => i 184 185 val () = 186 case input1 inStream of 187 NONE => OS.Process.exit OS.Process.success (* Close down. *) 188 | SOME #"\u001b" => () (* Escape- start of packet. *) 189 | SOME ch => protocolError(str ch ^ " not ESCAPE at start of packet") 190 val startCh = (* Request code *) 191 case input1 inStream of 192 NONE => protocolError "End of file" 193 | SOME ch => ch 194 in 195 case startCh of 196 #"R" => 197 let (* Compile request. *) 198 (* Begin a new compilation. *) 199 val requestId = readToEscape("", #",") 200 val fileName = readToEscape("", #",") 201 val startPosition = getInt #"," 202 (* The next two are the lengths *) 203 val preludeLength = getInt #"," 204 val sourceLength = getInt #"," 205 (* *) 206 val preludeCode = TextIO.inputN(inStream, FixedInt.toInt preludeLength) 207 val _ = readToEscape("", #",") (* Should be empty - check? *) 208 val sourceText = TextIO.inputN(inStream, FixedInt.toInt sourceLength) 209 val _ = readToEscape("", #"r") (* Should be empty - check? *) 210 in 211 CompileRequest { requestId = requestId, fileName = fileName, startPosition = startPosition, 212 preludeCode = preludeCode, sourceCode = sourceText } 213 end 214 215 (* Navigation functions. *) 216 217 | #"M" => 218 let 219 val requestId = readToEscape("", #",") 220 val parseTreeId = readToEscape("", #",") 221 val startOffset = getInt #"," 222 val endOffset = getInt #"," 223 val requestType = 224 case readToEscape("", #"m") of 225 "N" => DirRight 226 | "P" => DirLeft 227 | "U" => DirUp 228 | _(*"C"*) => DirDown 229 in 230 MoveRequest{ 231 requestId = requestId, parseTreeId = parseTreeId, direction= requestType, 232 location = { startOffset = startOffset, endOffset = endOffset } 233 } 234 end 235 236 (* Print the type of the selected node. *) 237 | #"T" => 238 let 239 val requestId = readToEscape("", #",") 240 val parseTreeId = readToEscape("", #",") 241 val startOffset = getInt #"," 242 val endOffset = getInt #"t" 243 in 244 TypeRequest{ 245 requestId = requestId, parseTreeId = parseTreeId, 246 location = { startOffset = startOffset, endOffset = endOffset } 247 } 248 end 249 250 (* Print the declaration location of the selected node. *) 251 | #"I" => 252 let 253 val requestId = readToEscape("", #",") 254 val parseTreeId = readToEscape("", #",") 255 val startOffset = getInt #"," 256 val endOffset = getInt #"," 257 val decType = 258 case readToEscape("", #"i") of 259 "J" => DecOpen 260 | "S" => DecParent 261 | _ (*"I"*) => DecLocal 262 in 263 DecRequest{ 264 requestId = requestId, parseTreeId = parseTreeId, decType = decType, 265 location = { startOffset = startOffset, endOffset = endOffset } 266 } 267 end 268 269 (* Return the local references to the given identifier. *) 270 | #"V" => 271 let 272 val requestId = readToEscape("", #",") 273 val parseTreeId = readToEscape("", #",") 274 val startOffset = getInt #"," 275 val endOffset = getInt #"v" 276 in 277 RefRequest{ 278 requestId = requestId, parseTreeId = parseTreeId, 279 location = { startOffset = startOffset, endOffset = endOffset } 280 } 281 end 282 283 | #"O" => (* Print list of valid commands. *) 284 let 285 val requestId = readToEscape("", #",") 286 val parseTreeId = readToEscape("", #",") 287 val startOffset = getInt #"," 288 val endOffset = getInt #"o" 289 in 290 PropertyRequest{ 291 requestId = requestId, parseTreeId = parseTreeId, 292 location = { startOffset = startOffset, endOffset = endOffset } 293 } 294 end 295 296 | #"K" => (* Cancel request. *) 297 KillRequest { requestId = readToEscape ("", #"k") } 298 299 | ch => (* Something else. Reply with empty response. *) 300 let 301 (* Unlike the other cases we don't know what may follow ESCAPE. *) 302 val terminator = Char.toLower ch 303 fun skipToTerminator () = 304 case input1 inStream of 305 SOME #"\u001b" => 306 ( 307 case input1 inStream of 308 NONE => protocolError "End of file" 309 | SOME ch => 310 if ch = terminator 311 then () (* Found the end. *) 312 else (* Some internal escape code. *) skipToTerminator() 313 ) 314 | SOME _ => skipToTerminator () 315 | NONE => protocolError "End of file" 316 in 317 skipToTerminator (); 318 UnknownRequest { request = Char.ord ch, requestId = "" } 319 end 320 end (* readRequest *) 321 322 fun sendStartedMessage () = 323 let 324 fun print s = TextIO.StreamIO.output(outStream, s) 325 fun printEsc ch = print (String.concat["\u001b", String.str ch]) 326 fun sendResponse () = 327 ( (* send the version number of the protocol *) 328 printEsc #"H"; print "1.0.0"; printEsc #"h"; 329 TextIO.StreamIO.flushOut outStream 330 ) 331 in 332 ThreadLib.protect outputLock sendResponse () 333 end 334 335 (* Send a reply packet. *) 336 fun sendResponse response = 337 let 338 fun print s = TextIO.StreamIO.output(outStream, s) 339 fun printEsc ch = print (String.concat["\u001b", String.str ch]) 340 341 fun printLocation {startOffset, endOffset } = 342 print (String.concat[FixedInt.toString startOffset, "\u001b,", FixedInt.toString endOffset]) 343 344 and printFullLocation { file, startLine, startPosition, endPosition, ...} = 345 ( 346 print file; (* TODO double any escapes. *) printEsc #","; 347 print (FixedInt.toString startLine); printEsc #","; 348 print (FixedInt.toString startPosition); printEsc #","; 349 print (FixedInt.toString endPosition) 350 ) 351 352 fun makeResponse (PropertyResponse { requestId, parseTreeId, location, commands }) = 353 let 354 fun printCommand comm = (printEsc #","; print comm) 355 in 356 printEsc #"O"; 357 print requestId; printEsc #","; 358 print parseTreeId; printEsc #","; 359 printLocation location; 360 List.app printCommand commands; 361 printEsc #"o" 362 end 363 364 | makeResponse (MoveResponse { requestId, parseTreeId, location }) = 365 ( 366 printEsc #"M"; 367 print requestId; printEsc #","; 368 print parseTreeId; printEsc #","; 369 printLocation location; 370 printEsc #"m" 371 ) 372 373 | makeResponse (TypeResponse { requestId, parseTreeId, location, typeRes }) = 374 let 375 fun prettyAsString message = 376 let 377 val result = ref [] 378 fun doPrint s = result := s :: ! result 379 val () = PolyML.prettyPrint(doPrint, !PolyML.Compiler.lineLength) message 380 in 381 String.concat(List.rev(! result)) 382 end 383 in 384 printEsc #"T"; 385 print requestId; printEsc #","; 386 print parseTreeId; printEsc #","; 387 printLocation location; 388 case typeRes of 389 NONE => () 390 | SOME typeRes => 391 ( 392 printEsc #","; 393 print(prettyAsString typeRes) 394 ); 395 printEsc #"t" 396 end 397 398 | makeResponse (DecResponse { requestId, parseTreeId, location, decLocation }) = 399 ( 400 printEsc #"I"; 401 print requestId; printEsc #","; 402 print parseTreeId; printEsc #","; 403 printLocation location; 404 case decLocation of 405 SOME location => (printEsc #","; printFullLocation location) 406 | NONE => (); 407 printEsc #"i" 408 ) 409 410 | makeResponse (RefResponse { requestId, parseTreeId, location, references }) = 411 ( 412 printEsc #"V"; 413 print requestId; printEsc #","; 414 print parseTreeId; printEsc #","; 415 printLocation location; 416 List.app (fn loc => (printEsc #","; printLocation loc)) references; 417 printEsc #"v" 418 ) 419 420 | makeResponse (CompilerResponse { requestId, parseTreeId, finalOffset, result }) = 421 let 422 (* Pretty print a message and return the output string. *) 423 fun prettyMarkupAsString message = 424 let 425 val result = ref [] 426 fun doPrint s = result := s :: ! result 427 val () = PolyML.prettyPrintWithIDEMarkup(doPrint, !PolyML.Compiler.lineLength) message 428 in 429 String.concat(List.rev(! result)) 430 end 431 432 fun printError { hardError, location, message } = 433 ( 434 printEsc #"E"; 435 if hardError then print "E" else print "W"; 436 printEsc #","; 437 printFullLocation location; 438 printEsc #";"; (* N.B. Semicolon here, not comma. *) 439 print (prettyMarkupAsString message); (* May include markup *) 440 printEsc #"e" 441 ) 442 fun printOffset() = (printEsc #","; print (FixedInt.toString finalOffset)) 443 fun printErrors errors = (List.app printError errors) 444 in 445 printEsc #"R"; 446 print requestId; printEsc #","; 447 print parseTreeId; printEsc #","; 448 case result of 449 Succeeded errors => (print "S"; printOffset(); printEsc #";"; printErrors errors) 450 | RuntimeException (s, errors) => 451 ( 452 print "X"; printOffset(); 453 printEsc #";"; 454 printEsc #"X"; print(prettyMarkupAsString s); (* May include markup *) 455 printEsc #"x"; 456 printErrors errors 457 ) 458 | PreludeFail s => 459 ( print "L"; printOffset(); printEsc #";"; print s (* May include markup *) ) 460 | CompileFail errors => 461 ( print "F"; printOffset(); printEsc #";"; printErrors errors ) 462 | CompileCancelled errors => 463 ( print "C"; printOffset(); printEsc #";"; printErrors errors ); 464 printEsc #"r" 465 end 466 467 | makeResponse (UnknownResponse { request, ... }) = 468 let 469 val startCh = Char.chr request 470 in 471 (* Response to unknown command - return empty result. *) 472 ( printEsc startCh; printEsc (Char.toLower startCh)) 473 end 474 475 fun sendResponse () = 476 ( 477 makeResponse response handle _ => protocolError "Exception"; 478 TextIO.StreamIO.flushOut outStream 479 ) 480 in 481 (* Sending the response packet must be atomic with respect to any other 482 output to stdOut. *) 483 ThreadLib.protect outputLock sendResponse () 484 end (* sendResponse *) 485 486 in 487 (readRequest, sendStartedMessage, sendResponse) 488 end 489 490 | SOME portNo => 491 (* Version 2 protocol - uses ASN1 binary over a socket.*) 492 let 493 val prefBuffSize = 4096 (* Get this from somewhere? *) 494 val socket = INetSock.TCP.socket(): Socket.active INetSock.stream_sock 495 (* We don't have a stream to produce error messages so simply fail if 496 we get an exception here. *) 497 val localhost = NetHostDB.addr (valOf(NetHostDB.getByName "localhost")) 498 val port = valOf(Int.fromString portNo) 499 val () = Socket.connect(socket, INetSock.toAddr(localhost, port)) 500 (* Construct the readers and writers. *) 501 502 fun sendASN1(v: Word8Vector.vector list) = 503 let 504 open Word8VectorSlice 505 (* Write the whole data, in chunks if necessary. *) 506 fun sendSlice slice = 507 if length slice = 0 508 then () 509 else sendSlice(subslice(slice, Socket.sendVec(socket, slice), NONE)) 510 in 511 sendSlice(Word8VectorSlice.full(Word8Vector.concat v)) 512 end 513 514 fun readVecFromSocket(n: int): Word8Vector.vector = Socket.recvVec(socket, n) 515 516 open TextIO Asn1 517 518 local (* Interlocked writer for TextIO.stdOut *) 519 (* Whenever we write plain text we package it as an ASN1 packet. *) 520 fun writeVecToSocket(v: CharVectorSlice.slice) = 521 ( 522 sendASN1(encodeItem(Application(1, Primitive), [encodeString(CharVectorSlice.vector v)])); 523 CharVectorSlice.length v (* It's written it all. *) 524 ) 525 val lockedWriteVec = ThreadLib.protect outputLock writeVecToSocket 526 val lockedWriter = 527 TextPrimIO.WR { 528 name = "TextIO.stdOut", chunkSize = prefBuffSize, 529 writeVec = SOME lockedWriteVec, writeArr = NONE, 530 writeVecNB = NONE, writeArrNB = NONE, block = NONE, canOutput = NONE, 531 getPos = NONE, setPos = NONE, endPos = NONE, verifyPos = NONE, 532 close = fn () => raise Fail "stdOut must not be closed", 533 ioDesc = SOME(Socket.ioDesc socket) } 534 in 535 (* Use this locked version for normal stdOut. *) 536 val () = setOutstream(stdOut, 537 StreamIO.mkOutstream(TextPrimIO.augmentWriter lockedWriter, IO.LINE_BUF)) 538 end 539 540 local 541 (* Create a functional binary stream *) 542 val reader = 543 BinPrimIO.RD { 544 name = "socket", chunkSize = prefBuffSize, 545 readVec = SOME readVecFromSocket, readArr = NONE, readVecNB = NONE, 546 readArrNB = NONE, block = NONE, 547 canInput = NONE, avail = fn _ => NONE, 548 getPos = NONE, setPos = NONE, endPos = NONE, verifyPos = NONE, 549 close = fn _ => (), ioDesc = SOME(Socket.ioDesc socket) 550 } 551 val binStream = 552 BinIO.StreamIO.mkInstream(BinPrimIO.augmentReader reader, Word8Vector.fromList []) 553 in 554 val inStream = ref binStream 555 end 556 557 fun protocolError error = 558 let 559 open OS.Process 560 in 561 TextIO.print ("Protocol error: " ^ error) handle _ => (); 562 exit failure 563 end 564 565 (* Reads a request. Calls OS.Process.exit at end-of-file or on a protocol error. *) 566 fun readRequest (): request = 567 let 568 open Asn1 569 570 (* Read the ASN1 header to get the tag and then read the data. 571 Position the stream ready to read the next request. *) 572 val (requestTag, data) = 573 case readHeader BinIO.StreamIO.input1 (!inStream) of 574 NONE => (* If we had EOF here it's probably because we've closed. *) 575 OS.Process.exit OS.Process.success (* Close down. *) 576 | SOME((tag, length), afterHdr) => 577 let 578 val (vector, afterBlock) = 579 BinIO.StreamIO.inputN(afterHdr, length) 580 in 581 if Word8Vector.length vector = length 582 then () 583 else protocolError "Stream closed"; 584 inStream := afterBlock; 585 (tag, vector) 586 end 587 588 fun splitSequence v = 589 case decodeItem v of 590 SOME{tag, data, remainder} => 591 (tag, data) :: splitSequence remainder 592 | NONE => [] 593 594 (* See if an item is present and return it if it is. *) 595 fun findData tag list = 596 Option.map #2 (List.find (fn (t, _) => t = tag) list) 597 598 fun findString tag list = 599 Option.map decodeString (findData tag list) 600 and findInt tag list = 601 Option.map (FixedInt.fromInt o decodeInt) (findData tag list) 602 in 603 case requestTag of 604 Application(3, _) => (* Compilation request. *) 605 let 606 val tdList = splitSequence(Word8VectorSlice.full data) 607 (* Request id *) 608 val reqId = findString (Application(1, Primitive)) tdList 609 (* File name - optional, default "" *) 610 val fileName = getOpt(findString (Context(1, Primitive)) tdList, "") 611 (* Start position - optional, default 0 *) 612 val startPosition = getOpt(findInt (Context(2, Primitive)) tdList, 0) 613 (* Prelude code - optional, default "" *) 614 val preludeCode = getOpt(findString (Context(3, Primitive)) tdList, "") 615 (* Source code *) 616 val source = findString (Context(4, Primitive)) tdList 617 in 618 case (reqId, source) of 619 (SOME requestId, SOME sourceText) => 620 CompileRequest { requestId = requestId, fileName = fileName, startPosition = startPosition, 621 preludeCode = preludeCode, sourceCode = sourceText } 622 | (SOME requestId, _) => UnknownRequest { request = 3, requestId = requestId } 623 | _ => UnknownRequest { request = 3, requestId = "" } (* Malformed *) 624 end 625 626 | Application(4, _) => (* Return the type of the selected node. *) 627 let 628 val tdList = splitSequence(Word8VectorSlice.full data) 629 (* Request id *) 630 val reqId = findString (Application(1, Primitive)) tdList 631 (* Parse id *) 632 val parseId = findString (Application(2, Primitive)) tdList 633 (* Start offset *) 634 val startOff = findInt (Context(1, Primitive)) tdList 635 (* End offset *) 636 val endOff = findInt (Context(2, Primitive)) tdList 637 in 638 case (reqId, parseId, startOff, endOff) of 639 (SOME requestId, SOME parseTreeId, SOME startOffset, SOME endOffset) => 640 TypeRequest{ 641 requestId = requestId, parseTreeId = parseTreeId, 642 location = { startOffset = startOffset, endOffset = endOffset } 643 } 644 | (SOME requestId, _, _, _) => UnknownRequest { request = 4, requestId = requestId } 645 | _ => UnknownRequest { request = 4, requestId = "" } (* Malformed *) 646 end 647 648 | Application(5, _) => (* Move request. *) 649 let 650 val tdList = splitSequence(Word8VectorSlice.full data) 651 (* Request id *) 652 val reqId = findString (Application(1, Primitive)) tdList 653 (* Parse id *) 654 val parseId = findString (Application(2, Primitive)) tdList 655 (* Start offset *) 656 val startOff = findInt (Context(1, Primitive)) tdList 657 (* End offset *) 658 val endOff = findInt (Context(2, Primitive)) tdList 659 (* Move direction *) 660 val dir = findInt (Context(3, Primitive)) tdList 661 in 662 case (reqId, parseId, startOff, endOff, dir) of 663 (SOME requestId, SOME parseTreeId, SOME startOffset, 664 SOME endOffset, SOME dir) => 665 let 666 val dirn = 667 case dir of 668 1 => DirUp 669 | 2 => DirLeft 670 | 3 => DirRight 671 | _ (*4*) => DirDown 672 in 673 MoveRequest{ 674 requestId = requestId, parseTreeId = parseTreeId, direction = dirn, 675 location = { startOffset = startOffset, endOffset = endOffset } 676 } 677 end 678 | (SOME requestId, _, _, _, _) => UnknownRequest { request = 5, requestId = requestId } 679 | _ => UnknownRequest { request = 5, requestId = "" } (* Malformed *) 680 681 end 682 683 | Application(6, _) => (* Declaration location for variables. *) 684 let 685 val tdList = splitSequence(Word8VectorSlice.full data) 686 (* Request id *) 687 val reqId = findString (Application(1, Primitive)) tdList 688 (* Parse id *) 689 val parseId = findString (Application(2, Primitive)) tdList 690 (* Start offset *) 691 val startOff = findInt (Context(1, Primitive)) tdList 692 (* End offset *) 693 val endOff = findInt (Context(2, Primitive)) tdList 694 (* Dec type *) 695 val dt = findInt (Context(3, Primitive)) tdList 696 in 697 case (reqId, parseId, startOff, endOff, dt) of 698 (SOME requestId, SOME parseTreeId, SOME startOffset, 699 SOME endOffset, SOME dect) => 700 let 701 val decType = 702 case dect of 703 2 => DecOpen 704 | 3 => DecParent 705 | _ (*1*) => DecLocal 706 in 707 DecRequest{ 708 requestId = requestId, parseTreeId = parseTreeId, decType = decType, 709 location = { startOffset = startOffset, endOffset = endOffset } 710 } 711 end 712 | (SOME requestId, _, _, _, _) => UnknownRequest { request = 6, requestId = requestId } 713 | _ => UnknownRequest { request = 6, requestId = "" } (* Malformed *) 714 end 715 716 | Application(7, _) => (* List the references to a variable. *) 717 let 718 val tdList = splitSequence(Word8VectorSlice.full data) 719 (* Request id *) 720 val reqId = findString (Application(1, Primitive)) tdList 721 (* Parse id *) 722 val parseId = findString (Application(2, Primitive)) tdList 723 (* Start offset *) 724 val startOff = findInt (Context(1, Primitive)) tdList 725 (* End offset *) 726 val endOff = findInt (Context(2, Primitive)) tdList 727 in 728 case (reqId, parseId, startOff, endOff) of 729 (SOME requestId, SOME parseTreeId, SOME startOffset, SOME endOffset) => 730 RefRequest{ 731 requestId = requestId, parseTreeId = parseTreeId, 732 location = { startOffset = startOffset, endOffset = endOffset } 733 } 734 | (SOME requestId, _, _, _) => UnknownRequest { request = 7, requestId = requestId } 735 | _ => UnknownRequest { request = 7, requestId = "" } (* Malformed *) 736 end 737 738 | Universal(tagNo, _) => UnknownRequest { request = tagNo, requestId = "" } 739 | Application(tagNo, _) => UnknownRequest { request = tagNo, requestId = "" } 740 | Context(tagNo, _) => UnknownRequest { request = tagNo, requestId = "" } 741 | Private(tagNo, _) => UnknownRequest { request = tagNo, requestId = "" } 742 (*case startCh of 743 | #"O" => (* Print list of valid commands. *) 744 let 745 val requestId = readToEscape("", #",") 746 val parseTreeId = readToEscape("", #",") 747 val startOffset = getInt #"," 748 val endOffset = getInt #"o" 749 in 750 PropertyRequest{ 751 requestId = requestId, parseTreeId = parseTreeId, 752 location = { startOffset = startOffset, endOffset = endOffset } 753 } 754 end 755 756 | #"K" => (* Cancel request. *) 757 KillRequest { requestId = readToEscape ("", #"k") } 758 *) 759 end (* readRequest *) 760 761 fun sendStartedMessage () = 762 let 763 fun sendResponse () = 764 sendASN1(encodeItem(Application(2, Primitive), [encodeString "1.0.0"])) 765 in 766 ThreadLib.protect outputLock sendResponse () 767 end 768 769 (* Send a reply packet. *) 770 fun sendResponse response = 771 let 772 fun encodeFullLocation { file, startLine, startPosition, endPosition, ...} = 773 let 774 val encFile = 775 if file = "" then [] else encodeItem(Context(1, Primitive), [encodeString file]) 776 val encLine = 777 if startLine = 0 then [] else encodeItem(Context(2, Primitive), [encodeInt(FixedInt.toInt startLine)]) 778 val encStart = 779 if startPosition = 0 then [] else encodeItem(Context(3, Primitive), [encodeInt(FixedInt.toInt startPosition)]) 780 val encEnd = 781 if endPosition = 0 then [] else encodeItem(Context(4, Primitive), [encodeInt(FixedInt.toInt endPosition)]) 782 in 783 encFile @ encLine @ encStart @ encEnd 784 end 785 786 and encodeLocation {startOffset, endOffset } = 787 encodeItem(Context(3, Primitive), [encodeInt(FixedInt.toInt startOffset)]) @ 788 encodeItem(Context(4, Primitive), [encodeInt(FixedInt.toInt endOffset)]) 789 790 and encodeRequestId requestId = 791 encodeItem(Application(20, Primitive), [encodeString requestId]) 792 793 and encodeParseId parseId = 794 encodeItem(Application(21, Primitive), [encodeString parseId]) 795 796 fun mapEnc _ [] = [] 797 | mapEnc f (hd :: tl) = f hd @ mapEnc f tl 798 799 (* Turn a pretty-print structure into text, stripping out mark-up. *) 800 (* TODO: We could return the "pretty" structure and have the IDE format it. *) 801 fun prettyAsString message = 802 let 803 val result = ref [] 804 fun doPrint s = result := s :: ! result 805 val () = PolyML.prettyPrint(doPrint, 120(*!PolyML.Compiler.lineLength*)) message 806 in 807 String.concat(List.rev(! result)) 808 end 809 810 fun makeResponse (CompilerResponse { requestId, parseTreeId, finalOffset, result }) = 811 let 812 fun encodeError { hardError, location, message } = 813 encodeItem(Context(4, Constructed), 814 encodeItem(Context(1, Primitive), [encodeBool hardError]) @ 815 encodeItem(Context(3, Constructed), encodeFullLocation location) @ 816 encodeItem(Context(2, Primitive), [encodeString(prettyAsString message)]) 817 ) 818 819 val (resultCode, resultData) = 820 case result of 821 Succeeded errors => 822 (0, mapEnc encodeError errors) 823 | RuntimeException (s, errors) => 824 (1, encodeItem(Context(3, Primitive), 825 [encodeString(prettyAsString s)]) @ mapEnc encodeError errors) 826 | PreludeFail s => 827 (2, encodeItem(Context(3, Primitive), [encodeString s])) 828 | CompileFail errors => 829 (3, mapEnc encodeError errors) 830 | CompileCancelled errors => 831 (4, mapEnc encodeError errors) 832 in 833 sendASN1(encodeItem(Application(3, Constructed), 834 encodeRequestId requestId @ encodeParseId parseTreeId @ 835 encodeItem(Context(1, Primitive), [encodeInt(FixedInt.toInt finalOffset)]) @ 836 encodeItem(Context(2, Primitive), [encodeInt(FixedInt.toInt resultCode)]) @ 837 resultData)) 838 end 839 840 | makeResponse (PropertyResponse { requestId, parseTreeId, location, commands }) = 841 let 842 fun encCommand c = encodeItem(Context(2, Primitive), [encodeString c]) 843 in 844 sendASN1(encodeItem(Application(4, Constructed), 845 encodeRequestId requestId @ encodeParseId parseTreeId @ 846 encodeItem(Context(1, Constructed), encodeLocation location) @ 847 mapEnc encCommand commands)) 848 end 849 850 | makeResponse (MoveResponse { requestId, parseTreeId, location }) = 851 sendASN1(encodeItem(Application(7, Constructed), 852 encodeRequestId requestId @ encodeParseId parseTreeId @ 853 encodeItem(Context(1, Constructed), encodeLocation location))) 854 855 | makeResponse (TypeResponse { requestId, parseTreeId, location, typeRes }) = 856 let 857 val typeData = 858 case typeRes of 859 NONE => [] 860 | SOME t => encodeItem(Context(2, Primitive), [encodeString(prettyAsString t)]) 861 in 862 sendASN1(encodeItem(Application(8, Constructed), 863 encodeRequestId requestId @ encodeParseId parseTreeId @ 864 encodeItem(Context(1, Constructed), encodeLocation location) @ typeData)) 865 end 866 867 | makeResponse (DecResponse { requestId, parseTreeId, location, decLocation }) = 868 let 869 val decData = 870 case decLocation of 871 NONE => [] 872 | SOME l => encodeItem(Context(2, Constructed), encodeFullLocation l) 873 in 874 sendASN1(encodeItem(Application(9, Constructed), 875 encodeRequestId requestId @ encodeParseId parseTreeId @ 876 encodeItem(Context(1, Constructed), encodeLocation location) @ decData)) 877 end 878 879 | makeResponse (RefResponse { requestId, parseTreeId, location, references }) = 880 let 881 fun encLoc l = encodeItem(Context(2, Constructed), encodeLocation l) 882 in 883 sendASN1(encodeItem(Application(10, Constructed), 884 encodeRequestId requestId @ encodeParseId parseTreeId @ 885 encodeItem(Context(1, Constructed), encodeLocation location) @ 886 mapEnc encLoc references)) 887 end 888 889 | makeResponse (UnknownResponse { requestId, ... }) = 890 (* Send an Error packet. *) 891 sendASN1(encodeItem(Application(0, Constructed), 892 if requestId = "" then [] 893 else encodeRequestId requestId)) 894 895 fun sendResponse () = 896 ( 897 makeResponse response handle _ => protocolError "Exception" 898 ) 899 in 900 (* Sending the response packet must be atomic with respect to any other 901 output to stdOut. *) 902 ThreadLib.protect outputLock sendResponse () 903 end (* sendResponse *) 904 in 905 (readRequest, sendStartedMessage, sendResponse) 906 end 907 908 (* Get the current parse tree and identifier. *) 909 fun getCurrentParse() = 910 withLock (fn () => let val (id, trees) = ! parseTree in (trees, ! lastParsetree, id) end) 911 (* Update lastParsetree if the id is still valid. *) 912 fun updateLastParse(id, pt) = 913 let 914 fun f () = 915 if id = #1 (! parseTree) then lastParsetree := pt else () 916 in 917 withLock f 918 end 919 (* Set parse tree and ID as a result of a compilation. Sets lastParsetree to the 920 head of the updated parse tree. *) 921 fun setParseTree(pt, id) = 922 let 923 fun f () = 924 ( 925 parseTree := (id, pt); 926 case pt of 927 [] => lastParsetree := NONE 928 | hd :: _ => lastParsetree := SOME hd 929 ) 930 in 931 withLock f 932 end 933 934 (* The source text may consist of several "programs". When we compile a "program" we 935 have to provide a way for the parsetree for this "program" to navigate to others 936 even though they won't have been compiled yet. This enables it to work. *) 937 (* We have to return functions for the parent, for the next sibling even if there 938 isn't one and for the previous sibling. *) 939 fun toplevelParseTree (parseRootRef as ref currentList) = 940 let 941 open PolyML 942 (* This is called when we have processed the previous "programs" but 943 not yet processed this one. *) 944 fun makelist([], _) = (* Shouldn't happen *) raise Fail "Null list" 945 | makelist(l as (locn, props) :: tl, previous) = 946 let 947 fun this () = makelist(l, previous) 948 (* If there is another item in the list we need a 949 property that moves there whose "previous" property 950 comes here. *) 951 val next = 952 case tl of 953 [] => [] 954 | _ => [PTnextSibling( 955 fn () => makelist(tl, [PTpreviousSibling this]))] 956 in 957 (locn, previous @ next @ props) 958 end 959 fun parent () = 960 case ! parseRootRef of 961 [] => raise Fail "Empty Tree" 962 | trees as (hd :: _) => 963 let 964 (* Navigation for one or more topdecs. *) 965 val fullLoc = 966 case (hd, List.last trees) of 967 (({ file, startLine, startPosition, ... }, _), 968 ({ endLine, endPosition, ... }, _)) => 969 { 970 file=file, startLine=startLine, 971 startPosition=startPosition, 972 endLine=endLine, endPosition=endPosition 973 } 974 in 975 (fullLoc, [PTfirstChild(fn () => makelist(trees, []))]) 976 end 977 978 val itemCount = List.length currentList 979 980 fun moveToNth n = 981 let 982 fun move (tree, 0) = tree 983 | move ((loc, opts), n) = 984 case List.find(fn PTnextSibling _ => true | _ => false) opts of 985 NONE => 986 let 987 (* We have to put a dummy item in at the end since when we 988 created the parent properties for the last "program" we will 989 have passed in a "next" entry even though there wasn't 990 actually a "next". *) 991 val { file, startLine, startPosition, ... } = loc 992 val lastPos = 993 { file = file, startLine = startLine, endLine = startLine, 994 startPosition = startPosition, endPosition = startPosition } 995 val opts = 996 List.filter(fn PTparent _ => true | PTpreviousSibling _ => true | _ => false) opts 997 in 998 (lastPos, opts) 999 end 1000 | SOME (PTnextSibling f) => move(f(), n-1) 1001 | SOME _ => raise Match (* Shouldn't happen *) 1002 in 1003 case ! parseRootRef of 1004 [] => raise Fail "Empty Tree" 1005 | trees => move(makelist(trees, []), n) 1006 end 1007 val previous = 1008 case currentList of 1009 [] => NONE (* This is the first. *) 1010 | _ => SOME(fn () => moveToNth(itemCount-1)) 1011 fun next () = moveToNth(itemCount+1) 1012 in 1013 { parent = SOME parent, next = SOME next, previous = previous } 1014 end 1015 1016 (* Move in the selected direction. Returns the tree as the result of the move. *) 1017 fun navigateTo(searchLocation as {startOffset:FixedInt.int, endOffset:FixedInt.int}, lastParsetree) = 1018 case lastParsetree of 1019 NONE => NONE 1020 | SOME({ startPosition, endPosition, ... }, tree) => 1021 let 1022 open PolyML 1023 datatype direction = Up | Down | Left | Right 1024 fun find([], _) = NONE (* No change *) 1025 | find(PTparent p :: _, Up) = SOME p 1026 | find(PTpreviousSibling p :: _, Left) = SOME p 1027 | find(PTnextSibling p :: _, Right) = SOME p 1028 | find(PTfirstChild p :: _, Down) = SOME p 1029 | find(_ :: tl, dir) = find (tl, dir) 1030 in 1031 if startOffset = startPosition andalso endOffset = endPosition 1032 then (* We're there already. *) lastParsetree 1033 else if startOffset >= startPosition andalso endOffset <= endPosition 1034 then (* It's this node or a child. *) 1035 let 1036 val child = find(tree, Down) 1037 in 1038 (* See if the element we want is actually a child. *) 1039 case child of 1040 SOME child => 1041 let 1042 (* See which child it is. *) 1043 fun findChild(location as {startPosition, endPosition, ...}, child) = 1044 if startOffset >= startPosition andalso endOffset <= endPosition 1045 then SOME (location, child) 1046 else 1047 case find(child, Right) of 1048 NONE => NONE 1049 | SOME next => findChild(next()) 1050 in 1051 case findChild(child()) of 1052 NONE => lastParsetree (* In this *) 1053 | SOME child => navigateTo(searchLocation, SOME child) 1054 end 1055 | NONE => lastParsetree (* No children. *) 1056 end 1057 else (* Must go out. *) 1058 ( 1059 case find(tree, Up) of 1060 SOME p => navigateTo(searchLocation, SOME(p())) 1061 | NONE => NONE (* Not found *) 1062 ) 1063 end 1064 1065 (* Main protocol loop. *) 1066 fun runProtocol currentCompilation = 1067 let 1068 (* Return the location of the given tree. *) 1069 fun treeLocation NONE = {startOffset = 0, endOffset = 0} 1070 | treeLocation (SOME ({startPosition, endPosition, ...}, _)) = 1071 {startOffset = startPosition, endOffset = endPosition} 1072 in 1073 case readRequest () of 1074 PropertyRequest { requestId: string, parseTreeId: string, location } => 1075 let (* Properties of selected node. *) 1076 (* Get the current parse tree and check the ID matches *) 1077 val (_, lastParsetree, currentParseID) = getCurrentParse() 1078 val (commands, location) = 1079 if parseTreeId = currentParseID 1080 then 1081 let 1082 val newTree = navigateTo(location, lastParsetree) 1083 (* Update the last tree if it's still valid. *) 1084 val () = updateLastParse(currentParseID, newTree) 1085 val commands = 1086 case newTree of 1087 NONE => [] 1088 | (SOME(_, tree)) => 1089 let 1090 open PolyML 1091 fun printCode(PTparent _, rest) = "U" :: rest 1092 | printCode(PTpreviousSibling _, rest) = "P" :: rest 1093 | printCode(PTnextSibling _, rest) = "N" :: rest 1094 | printCode(PTfirstChild _, rest) = "C" :: rest 1095 | printCode(PTtype _, rest) = "T" :: rest 1096 | printCode(PTdeclaredAt _, rest) = "I" :: rest 1097 | printCode(PTopenedAt _, rest) = "J" :: rest 1098 | printCode(PTstructureAt _, rest) = "S" :: rest 1099 | printCode(PTreferences(_, _::_), rest) = "V" :: rest 1100 (* Only include references if there is at least one 1101 local reference. *) 1102 | printCode(PTreferences(_, []), rest) = rest 1103 | printCode(PTprint _, rest) = rest 1104 | printCode(PTbreakPoint _, rest) = rest 1105 | printCode(PTcompletions _, rest) = rest 1106 | printCode(PTdefId _, rest) = rest 1107 | printCode(PTrefId _, rest) = rest 1108 in 1109 List.foldl printCode [] tree 1110 end 1111 in 1112 (commands, treeLocation newTree) 1113 end 1114 else ([], { startOffset = 0, endOffset = 0 }) (* Wrong ID. *) 1115 in 1116 sendResponse( 1117 PropertyResponse { 1118 requestId = requestId, parseTreeId = currentParseID, 1119 location = location, commands = commands 1120 }); 1121 runProtocol currentCompilation 1122 end 1123 1124 | MoveRequest { requestId, parseTreeId, location, direction } => 1125 let (* Get location after a move relative to a selected node. *) 1126 val (_, lastParsetree, currentParseID) = getCurrentParse() 1127 val newLocation = 1128 if parseTreeId = currentParseID 1129 then 1130 let 1131 (* Move to the given location, then move in the required direction. *) 1132 val newTree = 1133 case navigateTo(location, lastParsetree) of 1134 NONE => NONE 1135 | SOME(location, tree) => 1136 let 1137 open PolyML 1138 fun find([], _) = (location, tree) (* No change *) 1139 | find(PTparent p :: _, DirUp) = p() 1140 | find(PTpreviousSibling p :: _, DirLeft) = p() 1141 | find(PTnextSibling p :: _, DirRight) = p() 1142 | find(PTfirstChild p :: _, DirDown) = p() 1143 | find(_ :: tl, dir) = find (tl, dir) 1144 1145 in 1146 SOME(find(tree, direction)) 1147 end 1148 (* Update the last tree if it's still valid. *) 1149 val () = updateLastParse(currentParseID, newTree) 1150 in 1151 treeLocation newTree (* Return the location of the updated tree. *) 1152 end 1153 else { startOffset = 0, endOffset = 0 } (* *) 1154 in 1155 sendResponse( 1156 MoveResponse { 1157 requestId = requestId, parseTreeId = currentParseID, location = newLocation 1158 }); 1159 runProtocol currentCompilation 1160 end 1161 1162 | TypeRequest { requestId, parseTreeId, location } => 1163 let (* Type of value at selected node. *) 1164 val (_, lastParsetree, currentParseID) = getCurrentParse() 1165 val (typeRes, location) = 1166 if parseTreeId = currentParseID 1167 then 1168 let 1169 (* Move to the required location. *) 1170 val newTree = navigateTo(location, lastParsetree) 1171 val () = updateLastParse(currentParseID, newTree) 1172 (* If it has a type return it. *) 1173 val typeRes = 1174 case newTree of 1175 NONE => NONE 1176 | (SOME(_, tree)) => 1177 ( 1178 (* Print the type if it's there. Don't include any mark-up. *) 1179 (* TODO: This uses the global name space to find types and structures. 1180 It really should use the local name space but that requires adding 1181 an environment to the parse tree. *) 1182 case List.find (fn (PolyML.PTtype _) => true | _ => false) tree of 1183 SOME(PolyML.PTtype t) => 1184 SOME(PolyML.NameSpace.Values.printType(t, 100, SOME PolyML.globalNameSpace)) 1185 | _ => NONE 1186 ) 1187 in 1188 (typeRes, treeLocation newTree) 1189 end 1190 else (NONE, { startOffset = 0, endOffset = 0 }) 1191 in 1192 sendResponse( 1193 TypeResponse { 1194 requestId = requestId, parseTreeId = currentParseID, 1195 location = location, typeRes = typeRes 1196 }); 1197 runProtocol currentCompilation 1198 end 1199 1200 | DecRequest { requestId, parseTreeId, location, decType } => 1201 let (* Information about declaration location of identifier at selected node. *) 1202 val (_, lastParsetree, currentParseID) = getCurrentParse() 1203 val (decLocation, location) = 1204 if parseTreeId = currentParseID 1205 then 1206 let 1207 (* Move to the required location. *) 1208 val newTree = navigateTo(location, lastParsetree) 1209 val () = updateLastParse(currentParseID, newTree) 1210 val decLocation = 1211 (* If it has the right kind of property return it. *) 1212 case newTree of 1213 NONE => NONE 1214 | (SOME(_, tree)) => 1215 let 1216 open PolyML 1217 val getLoc = 1218 case decType of 1219 DecLocal => (fn (PTdeclaredAt p) => SOME p | _ => NONE) 1220 | DecOpen => (fn (PTopenedAt p) => SOME p | _ => NONE) 1221 | DecParent => (fn (PTstructureAt p) => SOME p | _ => NONE) 1222 (* Seatch in the properties of the current node for the property we want. *) 1223 fun findLoc [] = NONE 1224 | findLoc (hd::tl) = 1225 case getLoc hd of 1226 SOME location => SOME location 1227 | NONE => (* Keep trying. *) findLoc tl 1228 in 1229 findLoc tree 1230 end 1231 in 1232 (decLocation, treeLocation newTree) 1233 end 1234 else (NONE, { startOffset = 0, endOffset = 0 }) 1235 in 1236 sendResponse( 1237 DecResponse { 1238 requestId = requestId, parseTreeId = currentParseID, 1239 location = location, decLocation = decLocation 1240 }); 1241 runProtocol currentCompilation 1242 end 1243 1244 | RefRequest { requestId, parseTreeId, location } => 1245 let (* Type of value at selected node. *) 1246 val (_, lastParsetree, currentParseID) = getCurrentParse() 1247 val (references, location) = 1248 if parseTreeId = currentParseID 1249 then 1250 let 1251 (* Move to the required location. *) 1252 val newTree = navigateTo(location, lastParsetree) 1253 val () = updateLastParse(currentParseID, newTree) 1254 (* Find the local references. *) 1255 val references = 1256 case newTree of 1257 NONE => [] 1258 | SOME(_, tree) => 1259 ( 1260 case List.find (fn (PolyML.PTreferences _) => true | _ => false) tree of 1261 SOME(PolyML.PTreferences(_, l)) => 1262 List.map (fn {startPosition, endPosition, ...} => 1263 { startOffset=startPosition, endOffset=endPosition}) l 1264 | _ => [] 1265 ) 1266 in 1267 (references, treeLocation newTree) 1268 end 1269 else ([], { startOffset = 0, endOffset = 0 }) 1270 in 1271 sendResponse( 1272 RefResponse { 1273 requestId = requestId, parseTreeId = currentParseID, 1274 location = location, references = references 1275 }); 1276 runProtocol currentCompilation 1277 end 1278 1279 | CompileRequest { requestId, fileName, startPosition, preludeCode, sourceCode } => 1280 (* Unlike the other requests this is done asynchronously. *) 1281 let 1282 fun compileThread () = 1283 let 1284 type errorMsg = 1285 { message: PolyML.pretty, hard: bool, location: PolyML.location, 1286 context: PolyML.pretty option } 1287 (* Even success may include warning messages. *) 1288 datatype compileResult = 1289 Success 1290 | Exception of exn 1291 | Interrupted 1292 | Errors 1293 1294 local 1295 open PolyML.NameSpace 1296 (* Put in the results without printing. *) 1297 fun resultFun 1298 { fixes: (string * Infixes.fixity) list, values: (string * Values.value) list, 1299 structures: (string * Structures.structureVal) list, signatures: (string * Signatures.signatureVal) list, 1300 functors: (string * Functors.functorVal) list, types: (string * TypeConstrs.typeConstr) list} = 1301 let 1302 open PolyML 1303 in 1304 List.app (#enterFix globalNameSpace) fixes; 1305 List.app (#enterType globalNameSpace) types; 1306 List.app (#enterSig globalNameSpace) signatures; 1307 List.app (#enterStruct globalNameSpace) structures; 1308 List.app (#enterFunct globalNameSpace) functors; 1309 List.app (#enterVal globalNameSpace) values 1310 end 1311 in 1312 (* Compile the prelude. Simply returns true if it succeeded and false on any error. 1313 Note: Unlike the main compilation this is run with the interlock held and 1314 interrupts deferred. *) 1315 fun compilePreludeString stringInput: string option = 1316 let 1317 val stringStream = TextIO.openString stringInput 1318 1319 fun compilerResultFun (_, codeOpt) = 1320 case codeOpt of 1321 SOME code => (fn () => resultFun(code())) 1322 | NONE => raise Fail "Static Errors" 1323 1324 fun compilerLoop () = 1325 (* Compile each "program" until either we get to the end or an exception. *) 1326 if TextIO.endOfStream stringStream 1327 then NONE (* Reached the end of the input without error. *) 1328 else 1329 let 1330 (* Compile the code and get the result. *) 1331 open PolyML PolyML.Compiler 1332 val (code, result) = 1333 (PolyML.compiler(fn () => TextIO.input1 stringStream, 1334 [CPOutStream TextIO.print, CPCompilerResultFun compilerResultFun]), 1335 NONE) 1336 handle exn => (fn() => (), SOME(exnMessage exn)) 1337 in 1338 case result of 1339 NONE => 1340 ( 1341 (* No exception in compiler: run the code and check that it 1342 runs successfully. *) 1343 case ((code(); NONE) handle exn => SOME(exnMessage exn)) of 1344 NONE => compilerLoop () (* Continue. *) 1345 | exn => exn 1346 ) 1347 | error => error 1348 end 1349 1350 fun runloop () = 1351 let 1352 val res = compilerLoop() 1353 in 1354 (* The prelude may update the current parse tree. *) 1355 case !parseTree of 1356 (_, []) => lastParsetree := NONE 1357 | (_, hd :: _) => lastParsetree := SOME hd; 1358 res 1359 end 1360 in 1361 (* This is run with the lock held. *) 1362 withLock runloop 1363 end 1364 1365 (* Compile the main source code. *) 1366 fun compileString(stringInput, startPosition: int) = 1367 let 1368 val errorList = ref [] 1369 val stringPosition = ref 0 1370 val stringSize = String.size stringInput 1371 val resultTrees : PolyML.parseTree list ref = ref [] 1372 val lastTreePosition = ref 0 1373 fun readIn () = 1374 let 1375 val posn = ! stringPosition 1376 in 1377 if posn >= stringSize 1378 then NONE 1379 else SOME(String.sub(stringInput, posn)) before (stringPosition := posn+1) 1380 end 1381 (* We need to define our own compilerResultFun in order to capture the parse trees. *) 1382 fun compilerResultFun (parsetree, codeOpt) = 1383 ( 1384 (* Add the parsetree to the list. Record this as the position of the last valid tree. *) 1385 case parsetree of 1386 SOME pt => 1387 (resultTrees := ! resultTrees @ [pt]; lastTreePosition := !stringPosition) 1388 | NONE => (); (* Not if parse failed. *) 1389 case codeOpt of 1390 SOME code => (fn () => resultFun(code())) 1391 | NONE => raise Fail "Static Errors" 1392 ) 1393 1394 fun compilerLoop () = 1395 (* Compile each "program" until either we get to the end or an exception. *) 1396 if ! stringPosition >= stringSize 1397 then Success (* Reached the end of the input without error. *) 1398 else 1399 let 1400 open PolyML PolyML.Compiler 1401 val (code, result) = 1402 (PolyML.compiler(readIn, 1403 [CPOutStream TextIO.print, CPLineOffset (fn () => startPosition + !stringPosition), 1404 CPErrorMessageProc (fn msg => errorList := !errorList @ [msg]), 1405 CPCompilerResultFun compilerResultFun, CPFileName fileName, 1406 CPRootTree (toplevelParseTree resultTrees)]), 1407 Success) 1408 handle Fail _ => (fn() => (), Errors) 1409 | _ (* E.g. Interrupted *) => (fn() => (), Interrupted) 1410 in 1411 case result of 1412 Success => (* Compilation succeeded. *) 1413 ( 1414 (* Run the code. If it raised an exception pass that back. *) 1415 case (code(); Success) handle exn => Exception exn of 1416 Success => compilerLoop () (* Continue. *) 1417 | fault => fault 1418 ) 1419 | error => error 1420 end 1421 in 1422 (compilerLoop (), startPosition + !lastTreePosition, 1423 ! resultTrees, ! errorList) 1424 end 1425 end 1426 in 1427 if 1428 (* First run the prelude. If there are any errors report them and stop. *) 1429 case compilePreludeString preludeCode of 1430 NONE => true (* Succeeded - continue *) 1431 | SOME preludeError => (* Error - stop *) 1432 let 1433 (* Leave the parse tree unchanged. *) 1434 val (_, _, currentId) = getCurrentParse() 1435 in 1436 sendResponse( 1437 CompilerResponse { 1438 requestId = requestId, parseTreeId = currentId, 1439 finalOffset = startPosition, result = PreludeFail preludeError 1440 }); 1441 false 1442 end 1443 then (* We can do the main compilation. *) 1444 let 1445 local 1446 open Thread.Thread 1447 in 1448 (* The rest of this code is interruptible 1449 TODO: Multiple interrupts could result in not sending a 1450 result packet. *) 1451 val () = 1452 setAttributes [EnableBroadcastInterrupt true, InterruptState InterruptAsynch] 1453 end; 1454 val (result, finalPosition, resultTrees, errors) = 1455 compileString(sourceCode, FixedInt.toInt startPosition) 1456 fun makeErrorPacket 1457 {message: PolyML.pretty, hard: bool, location, ...} = 1458 { 1459 hardError = hard, 1460 location = location, 1461 message = message 1462 } 1463 val errorPackets = List.map makeErrorPacket errors 1464 val compileResult = 1465 case result of 1466 Success => Succeeded errorPackets (* May be warning messages. *) 1467 | Exception exn => 1468 let 1469 open PolyML 1470 val exLoc = 1471 case exceptionLocation exn of 1472 SOME loc => [ContextLocation loc] 1473 | NONE => [] 1474 val exceptionString = 1475 (PrettyBlock(0, false, exLoc, 1476 [ prettyRepresentation(exn, FixedInt.fromInt(!PolyML.Compiler.printDepth)) ])) 1477 in 1478 RuntimeException(exceptionString, errorPackets) 1479 end 1480 | Interrupted => CompileCancelled errorPackets 1481 | Errors => CompileFail errorPackets 1482 (* Update the tree unless parsing failed and we don't have one. *) 1483 val parseTreeId = 1484 case resultTrees of 1485 [] => #3 (getCurrentParse()) (* Return existing tree. *) 1486 | _ => (setParseTree(resultTrees, requestId); requestId) 1487 in 1488 (* Send the response. *) 1489 sendResponse( 1490 CompilerResponse { 1491 requestId = requestId, parseTreeId = parseTreeId, 1492 finalOffset = FixedInt.fromInt finalPosition, result = compileResult 1493 }) 1494 end 1495 else () (* Prelude failed. *) 1496 end (* compileThread *) 1497 1498 open Thread.Thread 1499 1500 (* First see if the last compilation has terminated. Starting a new 1501 compilation before the previous one has finished is really a 1502 protocol error. *) 1503 val isStillRunning = 1504 case currentCompilation of 1505 NONE => false 1506 | SOME (_, lastCompileThread) => isActive lastCompileThread 1507 in 1508 if isStillRunning 1509 then sendResponse( 1510 CompilerResponse { 1511 requestId = requestId, parseTreeId = #3 (getCurrentParse()), 1512 finalOffset = startPosition, result = PreludeFail "Thread still running" 1513 }) 1514 else 1515 let 1516 (* The compile thread is run with interrupts deferred initially. *) 1517 val thread = fork(compileThread, [InterruptState InterruptDefer]) 1518 in 1519 runProtocol (SOME(requestId, thread)) 1520 end 1521 end 1522 1523 | KillRequest { requestId: string } => (* Kill compilation. *) 1524 ( 1525 case currentCompilation of 1526 NONE => () (* No compilation. *) 1527 | SOME (id, thread) => 1528 if requestId = id 1529 then Thread.Thread.interrupt thread 1530 else () (* Different ID running. *); 1531 runProtocol currentCompilation 1532 ) 1533 1534 | UnknownRequest req => (* Respond with an empty response. *) 1535 ( 1536 sendResponse(UnknownResponse req); 1537 runProtocol currentCompilation 1538 ) 1539 end 1540 in 1541 let 1542 (* Turn off interrupts for the interface thread. *) 1543 open Thread.Thread 1544 in 1545 setAttributes[EnableBroadcastInterrupt false, InterruptState InterruptDefer] 1546 end; 1547 sendStartedMessage(); 1548 runProtocol NONE (* No compilation. *) 1549 end (* runIDEProtocol. *) 1550 1551 local 1552 val polySpecificGeneralCall = RunCall.rtsCallFull2 "PolySpecificGeneral" 1553 in 1554 fun polySpecificGeneral(code: int, arg:'a):'b = RunCall.unsafeCast(polySpecificGeneralCall(RunCall.unsafeCast(code, arg))) 1555 end 1556 1557in 1558 structure PolyML = 1559 struct 1560 (* This is the root function to run the Poly/ML top level. *) 1561 fun rootFunction () : unit = 1562 let 1563 val argList = CommandLine.arguments() 1564 fun rtsRelease() = polySpecificGeneral (10, ()) 1565 fun rtsHelp() = polySpecificGeneral (19, ()) 1566 val gitVersion = 1567 case polySpecificGeneral (9, ()) of 1568 "" => "" 1569 | s => " (Git version " ^ s ^ ")" 1570 1571 fun switchOption option = List.exists(fn s => s = option) argList 1572 in 1573 if switchOption "-v" 1574 then (* -v option : Print version information and exit *) 1575 print (String.concat ["Poly/ML ", PolyML.Compiler.compilerVersion, 1576 " RTS version: ", rtsRelease(), gitVersion, "\n"]) 1577 1578 else if switchOption "--help" 1579 then (* --help option: Print argument information and exit. *) 1580 ( 1581 print (String.concat ["Poly/ML ", PolyML.Compiler.compilerVersion, gitVersion, "\n"]); 1582 print "Compiler arguments:\n"; 1583 print "\n"; 1584 print "-v Print the version of Poly/ML and exit\n"; 1585 print "--help Print this message and exit\n"; 1586 print "-q Suppress the start-up message and turn off printing of results\n"; 1587 print "-i Interactive mode. Default if input is from a terminal\n"; 1588 print "--use FILE Executes 'use \"FILE\";' before the ML shell starts\n"; 1589 print "--eval STRING Compiles and executes STRING as ML before the ML shell starts\n"; 1590 print "--error-exit Exit shell on unhandled exception\n"; 1591 print "--with-markup Include extra mark-up information when printing\n"; 1592 print "--ideprotocol[=v2] Run the IDE communications protocol\n"; 1593 print "--script FILE The input is a script. Skips the first line if it begins with #!."; 1594 print "\nRun time system arguments:\n"; 1595 print (rtsHelp()) 1596 ) 1597 1598 else if switchOption "--ideprotocol" 1599 then runIDEProtocol () (* Run the IDE communication protocol. *) 1600 1601 else if switchOption "--script" 1602 then 1603 let 1604 (* The next argument is the file name. Open it but skip 1605 the first line if it's #!. The rest of this code is 1606 largely copied from PolyML.use. *) 1607 fun getFileName("--script" :: fileName :: _) = fileName 1608 | getFileName [] = (print "Missing file name after --script\n"; OS.Process.exit OS.Process.failure) 1609 | getFileName(_ :: tail) = getFileName tail 1610 val fileName = getFileName argList 1611 open TextIO 1612 val inStream = getInstream(TextIO.openIn fileName) 1613 open StreamIO 1614 val stream = ref inStream 1615 1616 val lineNo = ref 1 1617 val (start, _) = inputN(inStream, 2) 1618 fun getChar () = 1619 case input1 (! stream) of 1620 NONE => NONE 1621 | SOME (eoln as #"\n", strm) => 1622 ( 1623 lineNo := !lineNo + 1; 1624 stream := strm; 1625 SOME eoln 1626 ) 1627 | SOME(c, strm) => (stream := strm; SOME c) 1628 val () = 1629 if start = "#!" 1630 then while (case getChar () of NONE => false | SOME #"\n" => false | SOME _ => true) do () 1631 else () 1632 val () = PolyML.print_depth 0 (* Quieten. *) 1633 in 1634 while not (endOfStream(!stream)) do 1635 let 1636 open PolyML.Compiler 1637 val code = PolyML.compiler(getChar, [CPFileName fileName, CPLineNo(fn () => !lineNo)]) 1638 handle exn => 1639 ( closeIn(!stream); PolyML.Exception.reraise exn ) 1640 in 1641 code() handle exn => 1642 ( 1643 (* Report exceptions in running code. *) 1644 TextIO.print ("Exception- " ^ exnMessage exn ^ " raised\n"); 1645 input1 (! stream); 1646 PolyML.Exception.reraise exn 1647 ) 1648 end; 1649 (* Normal termination: close the stream. *) 1650 closeIn (! stream) 1651 end 1652 1653 else (* Enter normal Poly/ML top-level. *) 1654 let 1655 open Signal 1656 val () = 1657 if switchOption "-q" 1658 then PolyML.print_depth 0 1659 else print (String.concat ["Poly/ML ", PolyML.Compiler.compilerVersion, gitVersion, "\n"]); 1660 (* Set up a handler for SIGINT if that is currently set to SIG_DFL. 1661 If a handler has been set up by an initialisation function don't replace it. *) 1662 val () = 1663 case signal(2, SIG_IGN) of 1664 SIG_IGN => () 1665 | SIG_DFL => (signal(2, SIG_HANDLE(fn _ => Thread.Thread.broadcastInterrupt())); ()) 1666 | oldHandle => (signal(2, oldHandle); ()) 1667 1668 fun tryUseFileArguments [] = () (* done successfully *) 1669 1670 | tryUseFileArguments ["--use"] = 1671 ( 1672 print "'--use' requires a filename to be given as the next argument.\n"; 1673 OS.Process.exit OS.Process.failure 1674 ) 1675 1676 | tryUseFileArguments ("--use" :: filenameArg :: moreArgs) = 1677 ( 1678 PolyML.use filenameArg 1679 handle _ => 1680 ( 1681 print("Error trying to use the file: '" ^ filenameArg ^ "'\n"); 1682 OS.Process.exit OS.Process.failure 1683 ); 1684 tryUseFileArguments moreArgs 1685 ) 1686 1687 | tryUseFileArguments ["--eval"] = 1688 ( 1689 print "'--eval' requires a string to be given as the next argument.\n"; 1690 OS.Process.exit OS.Process.failure 1691 ) 1692 1693 | tryUseFileArguments ("--eval" :: useString :: moreArgs) = 1694 let 1695 (* Compile and execute commands from the string. *) 1696 val p = ref 0 1697 in 1698 while !p < size useString do 1699 let 1700 fun getChar() = 1701 if !p >= size useString 1702 then NONE 1703 else SOME(String.sub(useString, !p)) before p := !p+1 1704 val code = 1705 PolyML.compiler(getChar, []) 1706 handle _ => OS.Process.exit OS.Process.failure 1707 in 1708 code() handle exn => 1709 ( 1710 (* Report exceptions in running code. *) 1711 print ("Exception- " ^ exnMessage exn ^ " raised\n"); 1712 OS.Process.exit OS.Process.failure 1713 ) 1714 end; 1715 tryUseFileArguments moreArgs 1716 end 1717 1718 | tryUseFileArguments (_ :: args) = tryUseFileArguments args 1719 1720 in 1721 tryUseFileArguments argList; 1722 PolyML.shell (); 1723 OS.Process.exit OS.Process.success (* Run any "atExit" functions and then quit. *) 1724 end 1725 end; 1726 1727 structure IDEInterface = 1728 struct 1729 val parseTree = parseTree 1730 val runIDEProtocol = runIDEProtocol 1731 end; 1732 1733 open PolyML (* Add this to the PolyML structure. *) 1734 end 1735end; 1736