spook

A "game" for the 2023 Autumn Lisp Game Jam. Won first place! (from the bottom...)
git clone https://kaka.farm/~git/spook
Log | Files | Refs | LICENSE

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 }