runtime.js (14585B)
1 /* runtime.js - SPOCK runtime (javascript part) */ 2 3 4 SPOCK.modules = {}; 5 SPOCK.symbolTable = {}; 6 SPOCK.stack = 0; 7 SPOCK.limit = SPOCK.STACKSIZE; 8 SPOCK.debug = false; 9 SPOCK.running = false; 10 SPOCK.runHook = []; 11 SPOCK.inBrowser = "document" in this; 12 SPOCK.global = this; 13 14 SPOCK.Continuation = function(func, args) { 15 this.k_callee = func; 16 this.k_arguments = args; 17 }; 18 19 SPOCK.Result = function(val) { 20 this.value = val; 21 }; 22 23 SPOCK.Symbol = function(name) { 24 this.name = name; 25 this.plist = {}; 26 }; 27 28 SPOCK.Pair = function(car, cdr) { 29 this.car = car; 30 this.cdr = cdr; 31 }; 32 33 SPOCK.String = function(chars) { 34 if(typeof chars === "string") { 35 this.parts = [chars]; 36 this.length = chars.length; 37 } 38 else if(typeof chars === "number") this.parts = [chars.toString()]; 39 else this.parts = chars; // assumes chars is array 40 }; 41 42 SPOCK.Char = function(str) { 43 this.character = str.charAt(0); 44 }; 45 46 SPOCK.Port = function(direction, methods) { 47 var port = this; 48 var read = methods.read || function() { 49 SPOCK.error("reading from non-input port", port); 50 }; 51 52 function doread(n) { 53 if(n === 0) return ""; 54 else if(this.peeked) { 55 var p = this.peeked; 56 this.peeked = false; 57 58 if(n === 1) return p; 59 else return p + read(n - 1); 60 } 61 else return read(n); 62 } 63 64 this.peeked = false; 65 this.direction = direction; 66 this.read = doread; 67 this.write = methods.write || function() { 68 SPOCK.error("writing to non-output port", port) 69 }; 70 this.close = methods.close || function() {}; 71 this.flush = methods.flush || function() {}; 72 this.ready = methods.ready || function() { return true; }; 73 this.closed = false; 74 }; 75 76 SPOCK.Promise = function(thunk) { 77 this.thunk = thunk; 78 }; 79 80 SPOCK.EndOfFile = function() {}; 81 SPOCK.EOF = new SPOCK.EndOfFile(); 82 83 SPOCK.check = function(val, type, loc) { 84 if(typeof type === "function" && val instanceof type) return val; 85 if(typeof val === type) return val; 86 else SPOCK.error((loc ? "(" + loc + ") " : "") + 87 "bad argument type" + 88 (typeof type === "string" ? " - expected `" + type + "'" : ""), 89 val); 90 }; 91 92 SPOCK.intern = function(str) { 93 var old = SPOCK.symbolTable[ str ]; 94 95 if(old) return old; 96 else return SPOCK.symbolTable[ str ] = new SPOCK.Symbol(str); 97 }; 98 99 SPOCK.stringify = function(x, readable) { 100 if(readable === undefined) readable = true; 101 102 if(typeof x === "function") return "#<procedure>"; 103 else if(x === undefined) return "#<undefined>"; 104 else if(x === null) return "()"; 105 else if(x instanceof SPOCK.Continuation) return "#<continuation>"; 106 else if(x instanceof SPOCK.Symbol) return x.name; 107 else if(x instanceof SPOCK.Pair) { 108 var str = "("; 109 var f = false; 110 111 for(var p = x; p !== null && p instanceof SPOCK.Pair; p = p.cdr) { 112 if(f) str += " "; 113 114 str += SPOCK.stringify(p.car, readable); 115 f = true; 116 } 117 118 if(p !== null) str += " . " + SPOCK.stringify(p, readable); 119 120 return str + ")"; 121 } 122 else if(x instanceof Array) { 123 var str = "#("; 124 var f = false; 125 126 for(var i in x) { 127 if(f) str += " "; 128 129 str += SPOCK.stringify(x[ i ], readable); 130 f = true; 131 } 132 133 return str + ")"; 134 } 135 else if(x instanceof SPOCK.String) { 136 if(readable) 137 return "\"" + x.normalize() + "\""; // XXX does not escape embedded characters 138 else return x.normalize(); 139 } 140 else if(x instanceof SPOCK.Char) { 141 if(readable) return x.character; 142 143 switch(x.character) { 144 case "\n": return "#\\newline"; 145 case "\t": return "#\\tab"; 146 case "\r": return "#\\return"; 147 case " ": return "#\\space"; 148 default: return "#\\" + x.character; 149 } 150 } 151 else if(x instanceof SPOCK.Port) 152 return "#<" + x.direction + " port" + 153 (x.name ? (" \"" + x.name + "\">") : ">"); 154 else if(x instanceof SPOCK.Promise) return "#<promise>"; 155 else if(x instanceof SPOCK.EndOfFile) return "#<eof>"; 156 else return x.toString(); 157 }; 158 159 SPOCK.error = function(msg) { 160 var args = Array.prototype.splice.call(arguments, 1); 161 162 function argstr(x) { 163 return SPOCK.stringify(x, true); 164 } 165 166 if(args.length > 0) 167 msg = msg + ":\n " + SPOCK.map(argstr, args).join("\n "); 168 169 throw new Error(msg); 170 }; 171 172 if(this.quit) SPOCK.exit = quit; 173 else SPOCK.exit = function(code) { 174 SPOCK.error("no suitable primitive available for `exit'"); 175 }; 176 177 SPOCK.String.prototype.normalize = function() { 178 if(this.parts.length === 0) return ""; 179 180 this.parts = [this.parts.join("")]; 181 return this.parts[ 0 ]; 182 }; 183 184 SPOCK.jstring = function(x) { 185 if(typeof x === "string") return x; 186 else if(x instanceof SPOCK.String) return x.normalize(); 187 else return x; 188 }; 189 190 SPOCK.list = function() { 191 var lst = null; 192 var len = arguments.length; 193 194 for(var i = len - 1; i >= 0; --i) 195 lst = new SPOCK.Pair(arguments[ i ], lst); 196 197 return lst; 198 }; 199 200 SPOCK.length = function(lst) { 201 for(var n = 0; lst instanceof SPOCK.Pair; ++n) 202 lst = lst.cdr; 203 204 return n; 205 }; 206 207 SPOCK.map = function(func, array) { 208 var len = array.length; 209 var a2 = new Array(len); 210 211 for(var i in array) 212 a2[ i ] = func(array[ i ]); 213 214 return a2; 215 }; 216 217 SPOCK.eqvp = function(x, y) { 218 if(x === y) return true; 219 else if(x instanceof SPOCK.Char) 220 return y instanceof SPOCK.Char && x.character === y.character; 221 else return false; 222 }; 223 224 SPOCK.equalp = function(x, y) { 225 if(x === y) return true; 226 else if(x instanceof SPOCK.Pair) 227 return y instanceof SPOCK.Pair && 228 SPOCK.equalp(x.car, y.car) && 229 SPOCK.equalp(x.cdr, y.cdr); 230 else if(x instanceof Array) { 231 var len = x.length; 232 if(!(y instanceof Array) || y.length != len) return false; 233 for(var i = 0; i < len; ++i) { 234 if(!SPOCK.equalp(x[ i ], y[ i ])) return false; 235 } 236 return true; 237 } 238 else if(x instanceof SPOCK.Char) 239 return y instanceof SPOCK.Char && x.characters === y.characters; 240 else if(x instanceof SPOCK.String) { 241 var s1 = x.normalize(); 242 243 if(y instanceof SPOCK.String) return s1 === y.normalize(); 244 else if(typeof y === 'string') return s1 === y; 245 else return false; 246 } 247 else if(typeof x === 'string') { 248 if(y instanceof SPOCK.String) return x === y.normalize(); 249 else if(typeof y === 'string') return x === y; 250 else return false; 251 } 252 else return false; 253 }; 254 255 SPOCK.count = function(args, loc) { 256 if(--SPOCK.stack <= 0) 257 return new SPOCK.Continuation(args.callee, Array.prototype.slice.call(args)); 258 else return false; 259 }; 260 261 SPOCK.rest = function(args, count, loc) { 262 var rest = null; 263 264 // this will not unwind, but decrease the counter 265 SPOCK.count(args, loc); 266 267 for(var i = args.length - 1; i >= count; --i) 268 rest = new SPOCK.Pair(args[ i ], rest); 269 270 return rest; 271 }; 272 273 SPOCK.statistics = function() {}; 274 275 SPOCK.run = function(func) { // optional arguments 276 function terminate(result) { 277 return new SPOCK.Result(result); 278 } 279 280 var k = terminate; 281 var args = [k].concat(Array.prototype.slice.call(arguments, 1)); 282 var oldstack = SPOCK.stack; 283 var oldlimit = SPOCK.limit; 284 var oldrunning = SPOCK.running; 285 SPOCK.limit = Math.max(10, oldlimit - oldstack); 286 SPOCK.stack = SPOCK.limit; 287 SPOCK.running = true; 288 289 function restore() { 290 SPOCK.stack = oldstack; 291 SPOCK.limit = oldlimit; 292 SPOCK.running = oldrunning; 293 294 if(!oldrunning) { 295 for(var i in SPOCK.runHook) 296 (SPOCK.runHook[ i ])(false); 297 } 298 } 299 300 var result; 301 302 if(!oldrunning) { 303 for(var i in SPOCK.runHook) 304 (SPOCK.runHook[ i ])(true); 305 } 306 307 while(true) { 308 result = func.apply(SPOCK.global, args); 309 310 if(result instanceof SPOCK.Continuation) { 311 SPOCK.stack = SPOCK.STACKSIZE; 312 func = result.k_callee; 313 args = result.k_arguments; 314 } 315 else if(result instanceof SPOCK.Result) { 316 restore(); 317 return result.value; 318 } 319 else { 320 restore(); 321 SPOCK.error("unexpected return of non-continuation", result); 322 } 323 } 324 325 return result; 326 }; 327 328 SPOCK.callback = function(proc) { 329 return function() { 330 var args = Array.prototype.slice.call(arguments); 331 args.unshift(proc); 332 return SPOCK.run.apply(this, args); 333 }; 334 }; 335 336 SPOCK.callbackMethod = function(proc) { 337 var g = this; 338 return function() { 339 var args = Array.prototype.slice.call(arguments); 340 args.unshift(this); 341 args.unshift(proc); 342 return SPOCK.run.apply(g, args); 343 }; 344 }; 345 346 SPOCK.go = function(proc) { 347 (SPOCK.callback(proc))(); 348 }; 349 350 if("java" in this) { // rhino 351 SPOCK.makeJavaInputPort = function(jp) { 352 return new SPOCK.Port("input", { 353 read: function(n) { 354 var buffer = ""; 355 356 while(n--) { 357 var b = jp.read(); 358 359 if(b === -1) break; 360 else buffer += String.fromCharCode(b); 361 } 362 363 return buffer === "" ? SPOCK.EOF : buffer; 364 }, 365 366 close: function() { jp.close(); } 367 }); 368 }; 369 370 SPOCK.makeJavaOutputPort = function(jp) { 371 return new SPOCK.Port("output", { 372 write: function(s) { 373 var len = s.length; 374 375 for(var i = 0; i < len; ++i) 376 jp.write(s.charCodeAt(i)); 377 }, 378 379 flush: function() { jp.flush(); }, 380 close: function() { jp.close(); } 381 }); 382 }; 383 384 SPOCK.log = function() { 385 java.lang.System.err.println(Array.prototype.slice.call(arguments).join("")); 386 }; 387 388 SPOCK.stdin = SPOCK.makeJavaInputPort(java.lang.System[ "in" ]); 389 SPOCK.stdout = SPOCK.makeJavaOutputPort(java.lang.System.out); 390 SPOCK.stderr = SPOCK.makeJavaOutputPort(java.lang.System.err); 391 SPOCK.stderr.name = "[stderr]"; 392 } 393 else { 394 if("console" in this) SPOCK.log = console.log; // firebug 395 else if(SPOCK.inBrowser) // inside browser 396 SPOCK.log = function() { 397 var msg = arguments.join(" "); 398 399 if(msg.charAt(msg.length - 1) == "\n") 400 msg = msg.substring(0, msg.length - 1); 401 402 this.defaultStatus = msg; 403 }; 404 else if("print" in this) SPOCK.log = print; // spidermonkey/v8 405 else if(typeof process !== undefined) SPOCK.log = console.log; // Node.JS 406 else SPOCK.error("no suitable output primitive available"); 407 408 (function() { 409 var buffer = []; 410 411 function flush() { 412 if(buffer.length > 0) { 413 SPOCK.log(buffer.join("")); 414 buffer = []; 415 } 416 } 417 418 function write(s) { 419 var parts = SPOCK.stringify(s, false).split("\n"); 420 var len = parts.length - 1; 421 422 if(len > 0) { // contains newline? 423 buffer.push(parts[ 0 ]); 424 flush(); 425 426 if(len > 1) { 427 for(var i = 1; i < len; ++i) 428 SPOCK.log(parts[ i ]); 429 } 430 431 buffer.push(parts[ len ]); 432 } 433 else buffer.push(parts[ 0 ]); 434 } 435 436 SPOCK.stdout = new SPOCK.Port("output", { write: write, flush: flush }); 437 var inp; 438 var ibuffer = ""; 439 440 if(this.prompt) { 441 inp = function(n) { 442 while(true) { 443 if(ibuffer.length <= n) { 444 var part = ibuffer.slice(0, n); 445 ibuffer = ibuffer.slice(n); 446 return part; 447 } 448 449 var input = prompt("Expecting input for " + this.toString()); 450 451 if(input === null) return SPOCK.EOF; 452 else ibuffer += input; 453 } 454 }; 455 } 456 else { 457 inp = function(n) { 458 SPOCK.error("no input possible for standard input port"); 459 }; 460 } 461 462 SPOCK.stdin = new SPOCK.Port("input", { read: inp }); 463 SPOCK.stderr = SPOCK.stdout; 464 })(); 465 } 466 467 SPOCK.stdin.name = "[stdin]"; 468 SPOCK.stdout.name = "[stdout]"; 469 470 SPOCK.flush = function() { 471 // note that this always prints a newline when console.log or print is used 472 SPOCK.stdout.flush(); 473 474 if(SPOCK.stderr !== SPOCK.stdout) 475 SPOCK.stderr.flush(); 476 477 SPOCK.statistics(); 478 }; 479 480 if(this.gc) SPOCK.gc = gc; 481 else SPOCK.gc = function() {}; 482 483 SPOCK.openInputUrlHook = function(url) { 484 SPOCK.error("can not open", url); 485 }; 486 487 SPOCK.openOutputUrlHook = function(url) { 488 SPOCK.error("can not open", url); 489 }; 490 491 if("java" in this) { 492 SPOCK.openInputFile = function(filename) { 493 var stream; 494 495 try { 496 stream = new java.io.FileInputStream(filename); 497 } 498 catch(e) { 499 SPOCK.error(e.message); 500 } 501 502 var port = SPOCK.makeJavaInputPort(stream); 503 port.name = filename; 504 return port; 505 }; 506 507 SPOCK.openOutputFile = function(filename) { 508 var stream; 509 510 try { 511 stream = new java.io.FileOutputStream(filename); 512 } 513 catch(e) { 514 SPOCK.error(e.message); 515 } 516 517 var port = SPOCK.makeJavaOutputPort(stream); 518 port.name = filename; 519 return port; 520 }; 521 522 SPOCK.fileExists = function(filename) { 523 return (new java.io.File(filename)).exists(); 524 }; 525 } 526 else { 527 if(SPOCK.inBrowser) { 528 SPOCK.openInputFile = function(filename) { 529 if(filename.match(/^[a-z0-9]+:/)) 530 return SPOCK.openInputUrlHook(filename); 531 532 var cookies = document.cookie.split("; "); 533 var buffer = null; 534 535 for(var i = 0; i < cookies.length; ++i) { 536 var c = cookies[ i ]; 537 var p = c.indexOf("="); 538 539 if(filename === c.substring(0, p)) { 540 buffer = c.substring(p + 1); 541 break; 542 } 543 } 544 545 if(!buffer) SPOCK.error("can not open file", filename); 546 547 var pos = 0; 548 549 return new SPOCK.Port("input", { 550 read: function(n) { 551 if(pos >= buffer.length) return SPOCK.EOF; 552 else if(pos + len >= buffer.length) 553 return buffer.substring(pos); 554 555 var p1 = pos; 556 pos += n; 557 return buffer.substring(p1, p1 + n); 558 }, 559 560 ready: function() { return pos < buffer.length; } 561 }); 562 }; 563 564 SPOCK.openOutputFile = function(filename, expiry) { 565 if(filename.match(/^[a-z0-9]+:/)) 566 return SPOCK.openOutputUrlHook(filename); 567 568 return new SPOCK.Port("output", { 569 write: function(s) { buffer += s; }, 570 close: function() { 571 var now = (new Date()).getTime(); 572 var exp = now + (expiry || (1000 * 60 * 60 * 24 * 365)); 573 document.cookie = filename + "=" + encodeURIComponent(buffer) + 574 "; expires=" + (new Date(exp)).toGMTString(); 575 } 576 }); 577 }; 578 } 579 else { 580 SPOCK.openInputFile = function(filename) { 581 SPOCK.error("file-I/O not available"); 582 } 583 584 SPOCK.openOutputFile = function(filename) { 585 SPOCK.error("file-I/O not available"); 586 } 587 } 588 589 SPOCK.fileExists = function(filename) { 590 SPOCK.error("`file-exists?' not available"); 591 }; 592 } 593 594 if("document" in this) { // browser? 595 SPOCK.load = function(url, k) { 596 // http://www.nczonline.net/blog/2009/07/28/the-best-way-to-load-external-javascript/ 597 var script = document.createElement("script") 598 599 script.type = "text/javascript"; 600 k = k || function() {}; 601 602 if (script.readyState){ //IE 603 script.onreadystatechange = function(){ 604 if (script.readyState == "loaded" || script.readyState == "complete"){ 605 script.onreadystatechange = null; 606 k(url); 607 } 608 }; 609 } 610 else { //Others 611 script.onload = function(){ 612 k(url); 613 }; 614 } 615 616 script.src = url; 617 document.getElementsByTagName("head")[0].appendChild(script); 618 }; 619 } 620 else if("load" in this) { // rhino/SM 621 SPOCK.load = function(filename, k) { 622 load(filename); 623 624 if(k) k(filename); 625 }; 626 }