├── LICENSE ├── README.md ├── arith.ts ├── examples ├── fib.l ├── lisp.js └── run-on-web.html └── lisp.ts /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 OKI Software Co., Ltd. 2 | Copyright (c) 2019 SUZUKI Hisao 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a 5 | copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lisp in TypeScript 2 | 3 | This is a Lisp interpreter compatible with 4 | [lisp-in-dart](https://github.com/nukata/lisp-in-dart), 5 | [lisp-in-cs](https://github.com/nukata/lisp-in-cs) and 6 | [lisp-in-go](https://github.com/nukata/lisp-in-go). 7 | 8 | I first wrote it six years ago (2016|H28) in TypeScript 1.7 and revised it slightly 9 | one year later (2017|H29) in TypeScript 2.2. 10 | It had been presented under the MIT License at 11 | (broken link) 12 | until the spring of 2017|H29. 13 | I made the repository in GitHub four years ago (2018|H30). 14 | 15 | Three yeares ago (2019|R1) I made use of `BigInt` if possible. 16 | If you run it on a recent Node.js, you will enjoy infinite-precision integers. 17 | I also revised REPL. 18 | 19 | Now (2022|R4) I revised it again to make it run with Deno and recent Safari. 20 | Note that `BigInt("+")` returns `0n` on the current Safari. 21 | 22 | Just as lisp-in-dart, lisp-in-cs and lisp-in-go, 23 | this is a Lisp-1 with TCO (tail call optimization) 24 | and partially hygienic macros but being a subset of Common Lisp 25 | in a loose meaning. 26 | It is easy to write a nontrivial script which runs both in this and in 27 | Common Lisp. 28 | Examples are found in 29 | [lisp-in-dart/examples](https://github.com/nukata/lisp-in-dart/tree/master/examples). 30 | 31 | 32 | ## How to run 33 | 34 | Compile [`lisp.ts`](lisp.ts) with Deno to get `lisp.js`, or just 35 | use [`examples/lisp.js`](examples/lisp.js), which I provided in the same way. 36 | 37 | ``` 38 | $ deno --version 39 | deno 1.20.3 (release, x86_64-apple-darwin) 40 | v8 10.0.139.6 41 | typescript 4.6.2 42 | $ deno bundle lisp.ts lisp.js 43 | Check file:///Users/suzuki/tmp/lisp-in-typescript/lisp.ts 44 | Bundle file:///Users/suzuki/tmp/lisp-in-typescript/lisp.ts 45 | Emit "lisp.js" (39.84KB) 46 | $ 47 | ``` 48 | 49 | Run `lisp.js` with Node.js and Deno. 50 | 51 | ``` 52 | $ node --version 53 | v17.8.0 54 | $ node lisp.js 55 | > (+ 5 6) 56 | 11 57 | > `(a b ,(cons 'c 'd)) 58 | (a b (c . d)) 59 | > (list 60 | 1 61 | 2 62 | 3) 63 | (1 2 3) 64 | > (defun fact (n) 65 | (if (= n 0) 66 | 1 67 | (* n 68 | (fact (- n 1)) ))) 69 | fact 70 | > (fact 100) 71 | 93326215443944152681699238856266700490715968264381621468592963895217599993229915 72 | 608941463976156518286253697920827223758251185210916864000000000000000000000000 73 | > (dump) 74 | (fact dotimes dolist while nconc last nreverse _nreverse assoc assq member memq 75 | listp or mapcar and append _append letrec let when if equal /= <= >= > setcdr se 76 | tcar null rem = identity print consp not cdddr cddar cdadr cdaar caddr cadar caa 77 | dr caaar cddr cdar cadr caar defun defmacro *version* dump exit apply symbol-nam 78 | e intern make-symbol gensym *gensym-counter* terpri princ prin1 truncate / - * + 79 | mod % < eql numberp stringp length rplacd rplaca list eq atom cons cdr car) 80 | > *version* 81 | (2.1 "TypeScript" "Nukata Lisp") 82 | > (exit 0) 83 | $ 84 | ``` 85 | 86 | You can run it with Lisp script(s). 87 | If you put a "`-`" after the scripts, it will 88 | begin an interactive session after running the scripts. 89 | 90 | ``` 91 | $ cat examples/fib.l 92 | (defun fib (n) 93 | (if (<= n 1) 94 | n 95 | (+ (fib (- n 1)) 96 | (fib (- n 2))))) 97 | $ deno run --allow-read lisp.js examples/fib.l - 98 | > (fib 10) 99 | 55 100 | > (fib 20) 101 | 6765 102 | > (exit 0) 103 | $ 104 | ``` 105 | 106 | You can use `lisp.js` also from an HTML file to run the Lisp 107 | interpreter on Web browsers. 108 | See a simple example of [`exmples/run-on-web.html`](examples/run-on-web.html). 109 | 110 | ``` 111 | $ open examples/run-on-web.html 112 | ``` 113 | 114 | 115 | ## License 116 | 117 | This is under the MIT License. 118 | -------------------------------------------------------------------------------- /arith.ts: -------------------------------------------------------------------------------- 1 | // A little arithmetic in TypeScript 4.6 by SUZUKI Hisao (R01.08.04/R04.03.26) 2 | // derived from arith.ts at github.com/nukata/little-scheme-in-typescript 3 | 4 | export type Numeric = number | bigint; 5 | 6 | // A Number value is treated as an inexact number. 7 | // A BigInt value is treated as an exact number. 8 | // Any intergers should be represented by BigInt if possible. 9 | // If the runtime does not have BigInt, arithmetic will be done with Number. 10 | 11 | export const ZERO = (typeof BigInt === 'undefined') ? 0 : BigInt(0); 12 | export const ONE = (typeof BigInt === 'undefined') ? 1 : BigInt(1); 13 | 14 | // Is x a Numeric? 15 | export function isNumeric(x: unknown): x is Numeric { 16 | const t = typeof x; 17 | return t === 'number' || t === 'bigint'; 18 | } 19 | 20 | // x + y 21 | export function add(x: Numeric, y: Numeric): Numeric { 22 | if (typeof x === 'number') { 23 | if (typeof y === 'number') 24 | return x + y; 25 | else 26 | return x + Number(y); 27 | } else { 28 | if (typeof y === 'number') 29 | return Number(x) + y; 30 | else 31 | return x + y; 32 | } 33 | } 34 | 35 | // x - y 36 | export function subtract(x: Numeric, y: Numeric): Numeric { 37 | if (typeof x === 'number') { 38 | if (typeof y === 'number') 39 | return x - y; 40 | else 41 | return x - Number(y); 42 | } else { 43 | if (typeof y === 'number') 44 | return Number(x) - y; 45 | else 46 | return x - y; 47 | } 48 | } 49 | 50 | // x * y 51 | export function multiply(x: Numeric, y: Numeric): Numeric { 52 | if (typeof x === 'number') { 53 | if (typeof y === 'number') 54 | return x * y; 55 | else 56 | return x * Number(y); 57 | } else { 58 | if (typeof y === 'number') 59 | return Number(x) * y; 60 | else 61 | return x * y; 62 | } 63 | } 64 | 65 | // x / y (rounded quotient) 66 | export function divide(x: Numeric, y: Numeric): Numeric { 67 | return Number(x) / Number(y); 68 | } 69 | 70 | // Calculate the quotient of x and y. 71 | export function quotient(x: Numeric, y: Numeric): Numeric { 72 | if (typeof x === 'number' || typeof y === 'number') { 73 | const q = Math.trunc(Number(x) / Number(y)); 74 | if (typeof BigInt === 'undefined') 75 | return q; 76 | else 77 | return BigInt(q); 78 | } else { 79 | return x / y; 80 | } 81 | } 82 | 83 | // Calculate the remainder of the quotient of x and y. 84 | export function remainder(x: Numeric, y: Numeric): Numeric { 85 | if (typeof x === 'number' || typeof y === 'number') 86 | return Number(x) % Number(y); 87 | else 88 | return x % y; 89 | } 90 | 91 | // Compare x and y. 92 | // -1, 0 or 1 as x is less than, equal to, or greater than y. 93 | export function compare(x: Numeric, y: Numeric): number { 94 | if (typeof x === 'number') { 95 | if (typeof y === 'number') 96 | return Math.sign(x - y); 97 | else 98 | return Math.sign(x - Number(y)); 99 | } else { 100 | if (typeof y === 'number') 101 | return Math.sign(Number(x) - y); 102 | else 103 | return (x < y) ? -1 : (y < x) ? 1 : 0; 104 | } 105 | } 106 | 107 | // Try to parse the token as a Numeric or null. 108 | export function tryToParse(token: string): Numeric | null { 109 | try { 110 | return BigInt(token); 111 | } catch (_ex) { 112 | const n = Number(token); 113 | if (isNaN(n)) 114 | return null; 115 | return n; 116 | } 117 | } 118 | 119 | // Convert x to string. 120 | export function convertToString(x: Numeric): string { 121 | const s = x + ''; 122 | if (typeof BigInt !== 'undefined') 123 | if (typeof x === 'number') 124 | if (Number.isInteger(x) && !s.includes('e')) 125 | return s + '.0'; // 123.0 => '123.0' 126 | return s; 127 | } 128 | -------------------------------------------------------------------------------- /examples/fib.l: -------------------------------------------------------------------------------- 1 | (defun fib (n) 2 | (if (<= n 1) 3 | n 4 | (+ (fib (- n 1)) 5 | (fib (- n 2))))) 6 | -------------------------------------------------------------------------------- /examples/lisp.js: -------------------------------------------------------------------------------- 1 | // deno-fmt-ignore-file 2 | // deno-lint-ignore-file 3 | // This code was bundled using `deno bundle` and it's not recommended to edit it manually 4 | 5 | const ZERO = typeof BigInt === 'undefined' ? 0 : BigInt(0); 6 | const ONE = typeof BigInt === 'undefined' ? 1 : BigInt(1); 7 | function isNumeric(x) { 8 | const t = typeof x; 9 | return t === 'number' || t === 'bigint'; 10 | } 11 | function add(x, y) { 12 | if (typeof x === 'number') { 13 | if (typeof y === 'number') return x + y; 14 | else return x + Number(y); 15 | } else { 16 | if (typeof y === 'number') return Number(x) + y; 17 | else return x + y; 18 | } 19 | } 20 | function subtract(x, y) { 21 | if (typeof x === 'number') { 22 | if (typeof y === 'number') return x - y; 23 | else return x - Number(y); 24 | } else { 25 | if (typeof y === 'number') return Number(x) - y; 26 | else return x - y; 27 | } 28 | } 29 | function multiply(x, y) { 30 | if (typeof x === 'number') { 31 | if (typeof y === 'number') return x * y; 32 | else return x * Number(y); 33 | } else { 34 | if (typeof y === 'number') return Number(x) * y; 35 | else return x * y; 36 | } 37 | } 38 | function divide(x, y) { 39 | return Number(x) / Number(y); 40 | } 41 | function quotient(x, y) { 42 | if (typeof x === 'number' || typeof y === 'number') { 43 | const q = Math.trunc(Number(x) / Number(y)); 44 | if (typeof BigInt === 'undefined') return q; 45 | else return BigInt(q); 46 | } else { 47 | return x / y; 48 | } 49 | } 50 | function remainder(x, y) { 51 | if (typeof x === 'number' || typeof y === 'number') return Number(x) % Number(y); 52 | else return x % y; 53 | } 54 | function compare(x, y) { 55 | if (typeof x === 'number') { 56 | if (typeof y === 'number') return Math.sign(x - y); 57 | else return Math.sign(x - Number(y)); 58 | } else { 59 | if (typeof y === 'number') return Math.sign(Number(x) - y); 60 | else return x < y ? -1 : y < x ? 1 : 0; 61 | } 62 | } 63 | function tryToParse(token) { 64 | try { 65 | return BigInt(token); 66 | } catch (_ex) { 67 | const n = Number(token); 68 | if (isNaN(n)) return null; 69 | return n; 70 | } 71 | } 72 | function convertToString(x) { 73 | const s = x + ''; 74 | if (typeof BigInt !== 'undefined') { 75 | if (typeof x === 'number') { 76 | if (Number.isInteger(x) && !s.includes('e')) return s + '.0'; 77 | } 78 | } 79 | return s; 80 | } 81 | function assert(x, message) { 82 | if (!x) throw new Error("Assertion Failure: " + (message || "")); 83 | } 84 | let readLine; 85 | let write; 86 | let exit; 87 | class Cell { 88 | constructor(car, cdr){ 89 | this.car = car; 90 | this.cdr = cdr; 91 | } 92 | toString() { 93 | return "(" + this.car + " . " + this.cdr + ")"; 94 | } 95 | get length() { 96 | return foldl(0, this, (i, _)=>i + 1 97 | ); 98 | } 99 | car; 100 | cdr; 101 | } 102 | function foldl(x, j, fn) { 103 | while(j !== null){ 104 | x = fn(x, j.car); 105 | j = j.cdr; 106 | } 107 | return x; 108 | } 109 | function mapcar(j, fn) { 110 | if (j === null) return null; 111 | const a = fn(j.car); 112 | let d = j.cdr; 113 | if (d instanceof Cell) d = mapcar(d, fn); 114 | if (Object.is(j.car, a) && Object.is(j.cdr, d)) return j; 115 | return new Cell(a, d); 116 | } 117 | class Sym { 118 | constructor(name){ 119 | this.name = name; 120 | } 121 | toString() { 122 | return this.name; 123 | } 124 | get isInterned() { 125 | return symTable[this.name] === this; 126 | } 127 | name; 128 | } 129 | class Keyword extends Sym { 130 | constructor(name){ 131 | super(name); 132 | } 133 | } 134 | const symTable = {}; 135 | function newSym(name, isKeyword = false) { 136 | let result = symTable[name]; 137 | assert(result === undefined || !isKeyword, name); 138 | if (result === undefined) { 139 | result = isKeyword ? new Keyword(name) : new Sym(name); 140 | symTable[name] = result; 141 | } 142 | return result; 143 | } 144 | function newKeyword(name) { 145 | return newSym(name, true); 146 | } 147 | const backQuoteSym = newSym("`"); 148 | const commaAtSym = newSym(",@"); 149 | const commaSym = newSym(","); 150 | const dotSym = newSym("."); 151 | const leftParenSym = newSym("("); 152 | const rightParenSym = newSym(")"); 153 | const singleQuoteSym = newSym("'"); 154 | const appendSym = newSym("append"); 155 | const consSym = newSym("cons"); 156 | const listSym = newSym("list"); 157 | const restSym = newSym("&rest"); 158 | const unquoteSym = newSym("unquote"); 159 | const unquoteSplicingSym = newSym("unquote-splicing"); 160 | const condSym = newKeyword("cond"); 161 | const lambdaSym = newKeyword("lambda"); 162 | const macroSym = newKeyword("macro"); 163 | const prognSym = newKeyword("progn"); 164 | const quasiquoteSym = newKeyword("quasiquote"); 165 | const quoteSym = newKeyword("quote"); 166 | const setqSym = newKeyword("setq"); 167 | function cdrCell(x) { 168 | const k = x.cdr; 169 | if (k instanceof Cell) return k; 170 | else if (k === null) return null; 171 | else throw new EvalException("proper list expected", x); 172 | } 173 | class Func { 174 | constructor(carity){ 175 | this.carity = carity; 176 | } 177 | get arity() { 178 | return this.carity < 0 ? -this.carity : this.carity; 179 | } 180 | get hasRest() { 181 | return this.carity < 0; 182 | } 183 | get fixedArgs() { 184 | return this.carity < 0 ? -this.carity - 1 : this.carity; 185 | } 186 | makeFrame(arg) { 187 | const frame = new Array(this.arity); 188 | const n = this.fixedArgs; 189 | let i = 0; 190 | for(; i < n && arg !== null; i++){ 191 | frame[i] = arg.car; 192 | arg = cdrCell(arg); 193 | } 194 | if (i !== n || arg !== null && !this.hasRest) throw new EvalException("arity not matched", this); 195 | if (this.hasRest) frame[n] = arg; 196 | return frame; 197 | } 198 | evalFrame(frame, interp, env) { 199 | const n = this.fixedArgs; 200 | for(let i = 0; i < n; i++)frame[i] = interp.eval(frame[i], env); 201 | if (this.hasRest && frame[n] instanceof Cell) { 202 | let z = null; 203 | let y = null; 204 | for(let j = frame[n]; j !== null; j = cdrCell(j)){ 205 | const e = interp.eval(j.car, env); 206 | const x = new Cell(e, null); 207 | if (z === null) { 208 | z = x; 209 | } else { 210 | assert(y !== null); 211 | y.cdr = x; 212 | } 213 | y = x; 214 | } 215 | frame[n] = z; 216 | } 217 | } 218 | carity; 219 | } 220 | class DefinedFunc extends Func { 221 | constructor(carity, body){ 222 | super(carity); 223 | this.body = body; 224 | } 225 | body; 226 | } 227 | class Macro extends DefinedFunc { 228 | constructor(carity, body){ 229 | super(carity, body); 230 | } 231 | toString() { 232 | return `#`; 233 | } 234 | expandWith(interp, arg) { 235 | const frame = this.makeFrame(arg); 236 | const env = new Cell(frame, null); 237 | let x = null; 238 | for(let j = this.body; j !== null; j = cdrCell(j))x = interp.eval(j.car, env); 239 | return x; 240 | } 241 | static make(carity, body, env) { 242 | assert(env === null); 243 | return new Macro(carity, body); 244 | } 245 | } 246 | class Lambda extends DefinedFunc { 247 | constructor(carity, body){ 248 | super(carity, body); 249 | } 250 | toString() { 251 | return `#`; 252 | } 253 | static make(carity, body, env) { 254 | assert(env === null); 255 | return new Lambda(carity, body); 256 | } 257 | } 258 | class Closure extends DefinedFunc { 259 | constructor(carity, body, env){ 260 | super(carity, body); 261 | this.env = env; 262 | } 263 | static makeFrom(x, env) { 264 | return new Closure(x.carity, x.body, env); 265 | } 266 | toString() { 267 | return `#`; 268 | } 269 | makeEnv(interp, arg, interpEnv) { 270 | const frame = this.makeFrame(arg); 271 | this.evalFrame(frame, interp, interpEnv); 272 | return new Cell(frame, this.env); 273 | } 274 | static make(carity, body, env) { 275 | return new Closure(carity, body, env); 276 | } 277 | env; 278 | } 279 | class BuiltInFunc extends Func { 280 | constructor(name, carity, body){ 281 | super(carity); 282 | this.name = name; 283 | this.body = body; 284 | } 285 | toString() { 286 | return "#<" + this.name + ":" + this.carity + ">"; 287 | } 288 | evalWith(interp, arg, interpEnv) { 289 | const frame = this.makeFrame(arg); 290 | this.evalFrame(frame, interp, interpEnv); 291 | try { 292 | return this.body(frame); 293 | } catch (ex) { 294 | if (ex instanceof EvalException) throw ex; 295 | else throw new EvalException(ex + " -- " + this.name, frame); 296 | } 297 | } 298 | name; 299 | body; 300 | } 301 | class Arg { 302 | constructor(level, offset, symbol){ 303 | this.level = level; 304 | this.offset = offset; 305 | this.symbol = symbol; 306 | } 307 | toString() { 308 | return "#" + this.level + ":" + this.offset + ":" + this.symbol; 309 | } 310 | setValue(x, env) { 311 | for(let i = 0; i < this.level; i++)env = env.cdr; 312 | env.car[this.offset] = x; 313 | } 314 | getValue(env) { 315 | for(let i = 0; i < this.level; i++)env = env.cdr; 316 | return env.car[this.offset]; 317 | } 318 | level; 319 | offset; 320 | symbol; 321 | } 322 | class EvalException extends Error { 323 | trace = []; 324 | constructor(msg, x, quoteString = true){ 325 | super(msg + ": " + str(x, quoteString)); 326 | } 327 | toString() { 328 | let s = "EvalException: " + this.message; 329 | for (const line of this.trace)s += "\n\t" + line; 330 | return s; 331 | } 332 | } 333 | class NotVariableException extends EvalException { 334 | constructor(x){ 335 | super("variable expected", x); 336 | } 337 | } 338 | class FormatException extends Error { 339 | constructor(msg){ 340 | super(msg); 341 | } 342 | } 343 | const EndOfFile = { 344 | toString: ()=>"EOF" 345 | }; 346 | class Interp { 347 | globals = new Map(); 348 | constructor(){ 349 | this.def("car", 1, (a)=>a[0] === null ? null : a[0].car 350 | ); 351 | this.def("cdr", 1, (a)=>a[0] === null ? null : a[0].cdr 352 | ); 353 | this.def("cons", 2, (a)=>new Cell(a[0], a[1]) 354 | ); 355 | this.def("atom", 1, (a)=>a[0] instanceof Cell ? null : true 356 | ); 357 | this.def("eq", 2, (a)=>Object.is(a[0], a[1]) ? true : null 358 | ); 359 | this.def("list", -1, (a)=>a[0] 360 | ); 361 | this.def("rplaca", 2, (a)=>{ 362 | a[0].car = a[1]; 363 | return a[1]; 364 | }); 365 | this.def("rplacd", 2, (a)=>{ 366 | a[0].cdr = a[1]; 367 | return a[1]; 368 | }); 369 | this.def("length", 1, (a)=>a[0] === null ? 0 : quotient(a[0].length, 1) 370 | ); 371 | this.def("stringp", 1, (a)=>typeof a[0] === "string" ? true : null 372 | ); 373 | this.def("numberp", 1, (a)=>isNumeric(a[0]) ? true : null 374 | ); 375 | this.def("eql", 2, (a)=>{ 376 | const x = a[0]; 377 | const y = a[1]; 378 | return x === y ? true : isNumeric(x) && isNumeric(y) && compare(x, y) === 0 ? true : null; 379 | }); 380 | this.def("<", 2, (a)=>compare(a[0], a[1]) < 0 ? true : null 381 | ); 382 | this.def("%", 2, (a)=>remainder(a[0], a[1]) 383 | ); 384 | this.def("mod", 2, (a)=>{ 385 | const x = a[0]; 386 | const y = a[1]; 387 | const q = remainder(x, y); 388 | return compare(multiply(x, y), ZERO) < 0 ? add(q, y) : q; 389 | }); 390 | this.def("+", -1, (a)=>foldl(ZERO, a[0], (i, j)=>add(i, j) 391 | ) 392 | ); 393 | this.def("*", -1, (a)=>foldl(ONE, a[0], (i, j)=>multiply(i, j) 394 | ) 395 | ); 396 | this.def("-", -2, (a)=>{ 397 | const x = a[0]; 398 | const y = a[1]; 399 | return y == null ? -x : foldl(x, y, (i, j)=>subtract(i, j) 400 | ); 401 | }); 402 | this.def("/", -3, (a)=>foldl(divide(a[0], a[1]), a[2], (i, j)=>divide(i, j) 403 | ) 404 | ); 405 | this.def("truncate", -2, (a)=>{ 406 | const x = a[0]; 407 | const y = a[1]; 408 | if (y === null) { 409 | return quotient(x, ONE); 410 | } else if (y.cdr === null) { 411 | return quotient(x, y.car); 412 | } else { 413 | throw "one or two arguments expected"; 414 | } 415 | }); 416 | this.def("prin1", 1, (a)=>{ 417 | write(str(a[0], true)); 418 | return a[0]; 419 | }); 420 | this.def("princ", 1, (a)=>{ 421 | write(str(a[0], false)); 422 | return a[0]; 423 | }); 424 | this.def("terpri", 0, (_a)=>{ 425 | write("\n"); 426 | return true; 427 | }); 428 | const gensymCounter = newSym("*gensym-counter*"); 429 | this.globals.set(gensymCounter, ONE); 430 | this.def("gensym", 0, (_a)=>{ 431 | const i = this.globals.get(gensymCounter); 432 | this.globals.set(gensymCounter, add(i, ONE)); 433 | return new Sym("G" + i); 434 | }); 435 | this.def("make-symbol", 1, (a)=>new Sym(a[0]) 436 | ); 437 | this.def("intern", 1, (a)=>newSym(a[0]) 438 | ); 439 | this.def("symbol-name", 1, (a)=>a[0].name 440 | ); 441 | this.def("apply", 2, (a)=>this.eval(new Cell(a[0], mapcar(a[1], qqQuote)), null) 442 | ); 443 | this.def("exit", 1, (a)=>exit(Number(a[0])) 444 | ); 445 | this.def("dump", 0, (_a)=>{ 446 | let s = null; 447 | for (const x of this.globals.keys())s = new Cell(x, s); 448 | return s; 449 | }); 450 | this.globals.set(newSym("*version*"), new Cell(2.1, new Cell("TypeScript", new Cell("Nukata Lisp", null)))); 451 | } 452 | def(name, carity, body) { 453 | const sym = newSym(name); 454 | this.globals.set(sym, new BuiltInFunc(name, carity, body)); 455 | } 456 | eval(x, env) { 457 | try { 458 | for(;;){ 459 | if (x instanceof Arg) { 460 | assert(env !== null); 461 | return x.getValue(env); 462 | } else if (x instanceof Sym) { 463 | const value = this.globals.get(x); 464 | if (value === undefined) throw new EvalException("void variable", x); 465 | return value; 466 | } else if (x instanceof Cell) { 467 | let fn = x.car; 468 | const arg = cdrCell(x); 469 | if (fn instanceof Keyword) { 470 | switch(fn){ 471 | case quoteSym: 472 | if (arg !== null && arg.cdr === null) return arg.car; 473 | throw new EvalException("bad quote", x); 474 | case prognSym: 475 | x = this.evalProgN(arg, env); 476 | break; 477 | case condSym: 478 | x = this.evalCond(arg, env); 479 | break; 480 | case setqSym: 481 | return this.evalSetQ(arg, env); 482 | case lambdaSym: 483 | return this.compile(arg, env, Closure.make); 484 | case macroSym: 485 | if (env !== null) throw new EvalException("nested macro", x); 486 | return this.compile(arg, null, Macro.make); 487 | case quasiquoteSym: 488 | if (arg !== null && arg.cdr === null) { 489 | x = qqExpand(arg.car); 490 | break; 491 | } 492 | throw new EvalException("bad quasiquote", x); 493 | default: 494 | throw new EvalException("bad keyword", fn); 495 | } 496 | } else { 497 | if (fn instanceof Sym) { 498 | fn = this.globals.get(fn); 499 | if (fn === undefined) throw new EvalException("undefined", x.car); 500 | } else { 501 | fn = this.eval(fn, env); 502 | } 503 | if (fn instanceof Closure) { 504 | env = fn.makeEnv(this, arg, env); 505 | x = this.evalProgN(fn.body, env); 506 | } else if (fn instanceof Macro) { 507 | x = fn.expandWith(this, arg); 508 | } else if (fn instanceof BuiltInFunc) { 509 | return fn.evalWith(this, arg, env); 510 | } else { 511 | throw new EvalException("not applicable", fn); 512 | } 513 | } 514 | } else if (x instanceof Lambda) { 515 | return Closure.makeFrom(x, env); 516 | } else { 517 | return x; 518 | } 519 | } 520 | } catch (ex) { 521 | if (ex instanceof EvalException) { 522 | if (ex.trace.length < 10) ex.trace.push(str(x)); 523 | } 524 | throw ex; 525 | } 526 | } 527 | evalProgN(j, env) { 528 | if (j === null) return null; 529 | for(;;){ 530 | const x = j.car; 531 | j = cdrCell(j); 532 | if (j === null) return x; 533 | this.eval(x, env); 534 | } 535 | } 536 | evalCond(j, env) { 537 | for(; j !== null; j = cdrCell(j)){ 538 | const clause = j.car; 539 | if (clause instanceof Cell) { 540 | const result = this.eval(clause.car, env); 541 | if (result !== null) { 542 | const body = cdrCell(clause); 543 | if (body === null) return qqQuote(result); 544 | else return this.evalProgN(body, env); 545 | } 546 | } else if (clause !== null) { 547 | throw new EvalException("cond test expected", clause); 548 | } 549 | } 550 | return null; 551 | } 552 | evalSetQ(j, env) { 553 | let result = null; 554 | for(; j !== null; j = cdrCell(j)){ 555 | const lval = j.car; 556 | j = cdrCell(j); 557 | if (j === null) throw new EvalException("right value expected", lval); 558 | result = this.eval(j.car, env); 559 | if (lval instanceof Arg) { 560 | assert(env !== null); 561 | lval.setValue(result, env); 562 | } else if (lval instanceof Sym && !(lval instanceof Keyword)) { 563 | this.globals.set(lval, result); 564 | } else { 565 | throw new NotVariableException(lval); 566 | } 567 | } 568 | return result; 569 | } 570 | compile(arg, env, make) { 571 | if (arg === null) throw new EvalException("arglist and body expected", arg); 572 | const table = new Map(); 573 | const [hasRest, arity] = makeArgTable(arg.car, table); 574 | let body = cdrCell(arg); 575 | body = scanForArgs(body, table); 576 | body = this.expandMacros(body, 20); 577 | body = this.compileInners(body); 578 | return make(hasRest ? -arity : arity, body, env); 579 | } 580 | expandMacros(j, count) { 581 | if (count > 0 && j instanceof Cell) { 582 | let k = j.car; 583 | switch(k){ 584 | case quoteSym: 585 | case lambdaSym: 586 | case macroSym: 587 | return j; 588 | case quasiquoteSym: 589 | { 590 | const d = cdrCell(j); 591 | if (d !== null && d.cdr === null) { 592 | const z = qqExpand(d.car); 593 | return this.expandMacros(z, count); 594 | } 595 | throw new EvalException("bad quasiquote", j); 596 | } 597 | default: 598 | if (k instanceof Sym) k = this.globals.get(k); 599 | if (k instanceof Macro) { 600 | const d = cdrCell(j); 601 | const z = k.expandWith(this, d); 602 | return this.expandMacros(z, count - 1); 603 | } 604 | return mapcar(j, (x)=>this.expandMacros(x, count) 605 | ); 606 | } 607 | } else { 608 | return j; 609 | } 610 | } 611 | compileInners(j) { 612 | if (j instanceof Cell) { 613 | const k = j.car; 614 | switch(k){ 615 | case quoteSym: 616 | return j; 617 | case lambdaSym: 618 | { 619 | const d = cdrCell(j); 620 | return this.compile(d, null, Lambda.make); 621 | } 622 | case macroSym: 623 | throw new EvalException("nested macro", j); 624 | default: 625 | return mapcar(j, (x)=>this.compileInners(x) 626 | ); 627 | } 628 | } else { 629 | return j; 630 | } 631 | } 632 | } 633 | function makeArgTable(arg, table) { 634 | if (arg === null) { 635 | return [ 636 | false, 637 | 0 638 | ]; 639 | } else if (arg instanceof Cell) { 640 | let ag = arg; 641 | let offset = 0; 642 | let hasRest = false; 643 | for(; ag !== null; ag = cdrCell(ag)){ 644 | let j = ag.car; 645 | if (hasRest) throw new EvalException("2nd rest", j); 646 | if (j === restSym) { 647 | ag = cdrCell(ag); 648 | if (ag === null) throw new NotVariableException(ag); 649 | j = ag.car; 650 | if (j === restSym) throw new NotVariableException(j); 651 | hasRest = true; 652 | } 653 | let sym; 654 | if (j instanceof Sym) sym = j; 655 | else if (j instanceof Arg) sym = j.symbol; 656 | else throw new NotVariableException(j); 657 | if (table.has(sym)) throw new EvalException("duplicated argument name", j); 658 | table.set(sym, new Arg(0, offset, sym)); 659 | offset++; 660 | } 661 | return [ 662 | hasRest, 663 | offset 664 | ]; 665 | } else { 666 | throw new EvalException("arglist expected", arg); 667 | } 668 | } 669 | function scanForArgs(j, table) { 670 | if (j instanceof Sym) { 671 | const k = table.get(j); 672 | return k === undefined ? j : k; 673 | } else if (j instanceof Arg) { 674 | const k = table.get(j.symbol); 675 | return k === undefined ? new Arg(j.level + 1, j.offset, j.symbol) : k; 676 | } else if (j instanceof Cell) { 677 | if (j.car === quoteSym) { 678 | return j; 679 | } else if (j.car === quasiquoteSym) { 680 | return new Cell(quasiquoteSym, scanForQQ(j.cdr, table, 0)); 681 | } else { 682 | return mapcar(j, (x)=>scanForArgs(x, table) 683 | ); 684 | } 685 | } else { 686 | return j; 687 | } 688 | } 689 | function scanForQQ(j, table, level) { 690 | if (j instanceof Cell) { 691 | const k = j.car; 692 | if (k === quasiquoteSym) { 693 | return new Cell(k, scanForQQ(j.cdr, table, level + 1)); 694 | } else if (k === unquoteSym || k === unquoteSplicingSym) { 695 | const d = level === 0 ? scanForArgs(j.cdr, table) : scanForQQ(j.cdr, table, level - 1); 696 | if (Object.is(d, j.cdr)) return j; 697 | return new Cell(k, d); 698 | } else { 699 | return mapcar(j, (x)=>scanForQQ(x, table, level) 700 | ); 701 | } 702 | } else { 703 | return j; 704 | } 705 | } 706 | function qqExpand(x) { 707 | return qqExpand0(x, 0); 708 | } 709 | function qqExpand0(x, level) { 710 | if (x instanceof Cell) { 711 | if (x.car === unquoteSym) { 712 | if (level === 0) return x.cdr.car; 713 | } 714 | const t = qqExpand1(x, level); 715 | if (t.car instanceof Cell && t.cdr === null) { 716 | const k = t.car; 717 | if (k.car == listSym || k.car === consSym) return k; 718 | } 719 | return new Cell(appendSym, t); 720 | } else { 721 | return qqQuote(x); 722 | } 723 | } 724 | function qqQuote(x) { 725 | if (x instanceof Sym || x instanceof Cell) return new Cell(quoteSym, new Cell(x, null)); 726 | return x; 727 | } 728 | function qqExpand1(x, level) { 729 | if (x instanceof Cell) { 730 | if (x.car === unquoteSym) { 731 | if (level === 0) return x.cdr; 732 | level--; 733 | } else if (x.car === quasiquoteSym) { 734 | level++; 735 | } 736 | const h = qqExpand2(x.car, level); 737 | const t = qqExpand1(x.cdr, level); 738 | if (t.car === null && t.cdr === null) { 739 | return new Cell(h, null); 740 | } else if (h instanceof Cell) { 741 | if (h.car === listSym) { 742 | const tcar = t.car; 743 | if (tcar instanceof Cell) { 744 | if (tcar.car === listSym) { 745 | const hh = qqConcat(h, tcar.cdr); 746 | return new Cell(hh, t.cdr); 747 | } 748 | } 749 | if (h.cdr instanceof Cell) { 750 | const hh = qqConsCons(h.cdr, tcar); 751 | return new Cell(hh, t.cdr); 752 | } 753 | } 754 | } 755 | return new Cell(h, t); 756 | } else { 757 | return new Cell(qqQuote(x), null); 758 | } 759 | } 760 | function qqConcat(x, y) { 761 | if (x === null) return y; 762 | return new Cell(x.car, qqConcat(x.cdr, y)); 763 | } 764 | function qqConsCons(x, y) { 765 | if (x === null) return y; 766 | return new Cell(consSym, new Cell(x.car, new Cell(qqConsCons(x.cdr, y), null))); 767 | } 768 | function qqExpand2(y, level) { 769 | if (y instanceof Cell) { 770 | switch(y.car){ 771 | case unquoteSym: 772 | if (level === 0) return new Cell(listSym, y.cdr); 773 | level--; 774 | break; 775 | case unquoteSplicingSym: 776 | if (level === 0) return y.cdr.car; 777 | level--; 778 | break; 779 | case quasiquoteSym: 780 | level++; 781 | break; 782 | } 783 | } 784 | return new Cell(listSym, new Cell(qqExpand0(y, level), null)); 785 | } 786 | class Reader { 787 | token; 788 | tokens = []; 789 | lineNo = 1; 790 | push(text) { 791 | const tokenPat = /\s+|;.*$|("(\\.?|.)*?"|,@?|[^()'`~"; \t]+|.)/g; 792 | for (const line of text.split("\n")){ 793 | for(;;){ 794 | const result = tokenPat.exec(line); 795 | if (result === null) break; 796 | const s = result[1]; 797 | if (s !== undefined) this.tokens.push(s); 798 | } 799 | this.tokens.push("\n"); 800 | } 801 | } 802 | copyFrom(other) { 803 | this.tokens = other.tokens.slice(); 804 | this.lineNo = other.lineNo; 805 | } 806 | clear() { 807 | this.tokens.length = 0; 808 | } 809 | isEmpty() { 810 | return this.tokens.every((t)=>t === "\n" 811 | ); 812 | } 813 | read() { 814 | try { 815 | this.readToken(); 816 | return this.parseExpression(); 817 | } catch (ex) { 818 | if (ex === EndOfFile) throw EndOfFile; 819 | else if (ex instanceof FormatException) throw new EvalException("syntax error", ex.message + " at " + this.lineNo, false); 820 | else throw ex; 821 | } 822 | } 823 | parseExpression() { 824 | switch(this.token){ 825 | case leftParenSym: 826 | this.readToken(); 827 | return this.parseListBody(); 828 | case singleQuoteSym: 829 | this.readToken(); 830 | return new Cell(quoteSym, new Cell(this.parseExpression(), null)); 831 | case backQuoteSym: 832 | this.readToken(); 833 | return new Cell(quasiquoteSym, new Cell(this.parseExpression(), null)); 834 | case commaSym: 835 | this.readToken(); 836 | return new Cell(unquoteSym, new Cell(this.parseExpression(), null)); 837 | case commaAtSym: 838 | this.readToken(); 839 | return new Cell(unquoteSplicingSym, new Cell(this.parseExpression(), null)); 840 | case dotSym: 841 | case rightParenSym: 842 | throw new FormatException('unexpected "' + this.token + '"'); 843 | default: 844 | return this.token; 845 | } 846 | } 847 | parseListBody() { 848 | if (this.token === rightParenSym) { 849 | return null; 850 | } else { 851 | const e1 = this.parseExpression(); 852 | this.readToken(); 853 | let e2; 854 | if (this.token == dotSym) { 855 | this.readToken(); 856 | e2 = this.parseExpression(); 857 | this.readToken(); 858 | if (this.token !== rightParenSym) throw new FormatException('")" expected: ' + this.token); 859 | } else { 860 | e2 = this.parseListBody(); 861 | } 862 | return new Cell(e1, e2); 863 | } 864 | } 865 | readToken() { 866 | for(;;){ 867 | const t = this.tokens.shift(); 868 | if (t === undefined) { 869 | throw EndOfFile; 870 | } else if (t === "\n") { 871 | this.lineNo += 1; 872 | } else if (t === "+" || t === "-") { 873 | this.token = newSym(t); 874 | return; 875 | } else { 876 | if (t[0] === '"') { 877 | let s = t; 878 | const n = s.length - 1; 879 | if (n < 1 || s[n] !== '"') throw new FormatException("bad string: " + s); 880 | s = s.substring(1, n); 881 | s = s.replace(/\\./g, (m)=>{ 882 | const val = Reader.escapes[m]; 883 | return val === undefined ? m : val; 884 | }); 885 | this.token = s; 886 | return; 887 | } 888 | const n = tryToParse(t); 889 | if (n !== null) this.token = n; 890 | else if (t === "nil") this.token = null; 891 | else if (t === "t") this.token = true; 892 | else this.token = newSym(t); 893 | return; 894 | } 895 | } 896 | } 897 | static escapes = { 898 | "\\\\": "\\", 899 | '\\"': '"', 900 | "\\n": "\n", 901 | "\\r": "\r", 902 | "\\f": "\f", 903 | "\\b": "\b", 904 | "\\t": "\t", 905 | "\\v": "\v" 906 | }; 907 | } 908 | const quotes = { 909 | [quoteSym.name]: "'", 910 | [quasiquoteSym.name]: "`", 911 | [unquoteSym.name]: ",", 912 | [unquoteSplicingSym.name]: ",@" 913 | }; 914 | function str(x, quoteString = true, count, printed) { 915 | if (x === null) { 916 | return "nil"; 917 | } else if (x === true) { 918 | return "t"; 919 | } else if (x instanceof Cell) { 920 | if (x.car instanceof Sym) { 921 | const q = quotes[x.car.name]; 922 | if (q !== undefined && x.cdr instanceof Cell) { 923 | if (x.cdr.cdr == null) return q + str(x.cdr.car, true, count, printed); 924 | } 925 | } 926 | return "(" + strListBody(x, count, printed) + ")"; 927 | } else if (typeof x === "string") { 928 | if (!quoteString) return x; 929 | const bf = [ 930 | '"' 931 | ]; 932 | for (const ch of x){ 933 | switch(ch){ 934 | case "\b": 935 | bf.push("\\b"); 936 | break; 937 | case "\t": 938 | bf.push("\\t"); 939 | break; 940 | case "\n": 941 | bf.push("\\n"); 942 | break; 943 | case "\v": 944 | bf.push("\\v"); 945 | break; 946 | case "\f": 947 | bf.push("\\f"); 948 | break; 949 | case "\r": 950 | bf.push("\\r"); 951 | break; 952 | case "\"": 953 | bf.push("\\\""); 954 | break; 955 | case "\\": 956 | bf.push("\\\\"); 957 | break; 958 | default: 959 | bf.push(ch); 960 | break; 961 | } 962 | } 963 | bf.push('"'); 964 | return bf.join(""); 965 | } else if (x instanceof Array) { 966 | const s = x.map((e)=>str(e, true, count, printed) 967 | ).join(", "); 968 | return "[" + s + "]"; 969 | } else if (x instanceof Sym) { 970 | return x.isInterned ? x.name : "#:" + x; 971 | } else if (isNumeric(x)) { 972 | return convertToString(x); 973 | } else { 974 | return x + ""; 975 | } 976 | } 977 | function strListBody(x, count, printed) { 978 | if (printed === undefined) printed = []; 979 | if (count === undefined) count = 4; 980 | const s = []; 981 | let y; 982 | for(y = x; y instanceof Cell; y = y.cdr){ 983 | if (printed.indexOf(y) < 0) { 984 | printed.push(y); 985 | count = 4; 986 | } else { 987 | count--; 988 | if (count < 0) { 989 | s.push("..."); 990 | return s.join(" "); 991 | } 992 | } 993 | s.push(str(y.car, true, count, printed)); 994 | } 995 | if (y !== null) { 996 | s.push("."); 997 | s.push(str(y, true, count, printed)); 998 | } 999 | for(y = x; y instanceof Cell; y = y.cdr){ 1000 | const i = printed.indexOf(y); 1001 | if (i >= 0) printed.splice(i, 1); 1002 | } 1003 | return s.join(" "); 1004 | } 1005 | class REPL { 1006 | stdInTokens = new Reader(); 1007 | async readExpression(prompt1, prompt2) { 1008 | const oldTokens = new Reader(); 1009 | for(;;){ 1010 | oldTokens.copyFrom(this.stdInTokens); 1011 | try { 1012 | return this.stdInTokens.read(); 1013 | } catch (ex) { 1014 | if (ex === EndOfFile) { 1015 | write(oldTokens.isEmpty() ? prompt1 : prompt2); 1016 | const line = await readLine(); 1017 | if (line === null) return EndOfFile; 1018 | oldTokens.push(line); 1019 | this.stdInTokens.copyFrom(oldTokens); 1020 | } else { 1021 | this.stdInTokens.clear(); 1022 | throw ex; 1023 | } 1024 | } 1025 | } 1026 | } 1027 | async readEvalPrintLoop(interp) { 1028 | for(;;){ 1029 | try { 1030 | const exp = await this.readExpression("> ", " "); 1031 | if (exp === EndOfFile) { 1032 | write("Goodbye\n"); 1033 | return; 1034 | } 1035 | const result = interp.eval(exp, null); 1036 | write(str(result) + "\n"); 1037 | } catch (ex) { 1038 | if (ex instanceof EvalException) write(ex + "\n"); 1039 | else throw ex; 1040 | } 1041 | } 1042 | } 1043 | } 1044 | function run(interp, text) { 1045 | const tokens = new Reader(); 1046 | tokens.push(text); 1047 | let result = undefined; 1048 | while(!tokens.isEmpty()){ 1049 | const exp = tokens.read(); 1050 | result = interp.eval(exp, null); 1051 | } 1052 | return result; 1053 | } 1054 | const prelude = ` 1055 | (setq defmacro 1056 | (macro (name args &rest body) 1057 | \`(progn (setq ,name (macro ,args ,@body)) 1058 | ',name))) 1059 | 1060 | (defmacro defun (name args &rest body) 1061 | \`(progn (setq ,name (lambda ,args ,@body)) 1062 | ',name)) 1063 | 1064 | (defun caar (x) (car (car x))) 1065 | (defun cadr (x) (car (cdr x))) 1066 | (defun cdar (x) (cdr (car x))) 1067 | (defun cddr (x) (cdr (cdr x))) 1068 | (defun caaar (x) (car (car (car x)))) 1069 | (defun caadr (x) (car (car (cdr x)))) 1070 | (defun cadar (x) (car (cdr (car x)))) 1071 | (defun caddr (x) (car (cdr (cdr x)))) 1072 | (defun cdaar (x) (cdr (car (car x)))) 1073 | (defun cdadr (x) (cdr (car (cdr x)))) 1074 | (defun cddar (x) (cdr (cdr (car x)))) 1075 | (defun cdddr (x) (cdr (cdr (cdr x)))) 1076 | (defun not (x) (eq x nil)) 1077 | (defun consp (x) (not (atom x))) 1078 | (defun print (x) (prin1 x) (terpri) x) 1079 | (defun identity (x) x) 1080 | 1081 | (setq 1082 | = eql 1083 | rem % 1084 | null not 1085 | setcar rplaca 1086 | setcdr rplacd) 1087 | 1088 | (defun > (x y) (< y x)) 1089 | (defun >= (x y) (not (< x y))) 1090 | (defun <= (x y) (not (< y x))) 1091 | (defun /= (x y) (not (= x y))) 1092 | 1093 | (defun equal (x y) 1094 | (cond ((atom x) (eql x y)) 1095 | ((atom y) nil) 1096 | ((equal (car x) (car y)) (equal (cdr x) (cdr y))))) 1097 | 1098 | (defmacro if (test then &rest else) 1099 | \`(cond (,test ,then) 1100 | ,@(cond (else \`((t ,@else)))))) 1101 | 1102 | (defmacro when (test &rest body) 1103 | \`(cond (,test ,@body))) 1104 | 1105 | (defmacro let (args &rest body) 1106 | ((lambda (vars vals) 1107 | (defun vars (x) 1108 | (cond (x (cons (if (atom (car x)) 1109 | (car x) 1110 | (caar x)) 1111 | (vars (cdr x)))))) 1112 | (defun vals (x) 1113 | (cond (x (cons (if (atom (car x)) 1114 | nil 1115 | (cadar x)) 1116 | (vals (cdr x)))))) 1117 | \`((lambda ,(vars args) ,@body) ,@(vals args))) 1118 | nil nil)) 1119 | 1120 | (defmacro letrec (args &rest body) ; (letrec ((v e) ...) body...) 1121 | (let (vars setqs) 1122 | (defun vars (x) 1123 | (cond (x (cons (caar x) 1124 | (vars (cdr x)))))) 1125 | (defun sets (x) 1126 | (cond (x (cons \`(setq ,(caar x) ,(cadar x)) 1127 | (sets (cdr x)))))) 1128 | \`(let ,(vars args) ,@(sets args) ,@body))) 1129 | 1130 | (defun _append (x y) 1131 | (if (null x) 1132 | y 1133 | (cons (car x) (_append (cdr x) y)))) 1134 | (defmacro append (x &rest y) 1135 | (if (null y) 1136 | x 1137 | \`(_append ,x (append ,@y)))) 1138 | 1139 | (defmacro and (x &rest y) 1140 | (if (null y) 1141 | x 1142 | \`(cond (,x (and ,@y))))) 1143 | 1144 | (defun mapcar (f x) 1145 | (and x (cons (f (car x)) (mapcar f (cdr x))))) 1146 | 1147 | (defmacro or (x &rest y) 1148 | (if (null y) 1149 | x 1150 | \`(cond (,x) 1151 | ((or ,@y))))) 1152 | 1153 | (defun listp (x) 1154 | (or (null x) (consp x))) ; NB (listp (lambda (x) (+ x 1))) => nil 1155 | 1156 | (defun memq (key x) 1157 | (cond ((null x) nil) 1158 | ((eq key (car x)) x) 1159 | (t (memq key (cdr x))))) 1160 | 1161 | (defun member (key x) 1162 | (cond ((null x) nil) 1163 | ((equal key (car x)) x) 1164 | (t (member key (cdr x))))) 1165 | 1166 | (defun assq (key alist) 1167 | (cond (alist (let ((e (car alist))) 1168 | (if (and (consp e) (eq key (car e))) 1169 | e 1170 | (assq key (cdr alist))))))) 1171 | 1172 | (defun assoc (key alist) 1173 | (cond (alist (let ((e (car alist))) 1174 | (if (and (consp e) (equal key (car e))) 1175 | e 1176 | (assoc key (cdr alist))))))) 1177 | 1178 | (defun _nreverse (x prev) 1179 | (let ((next (cdr x))) 1180 | (setcdr x prev) 1181 | (if (null next) 1182 | x 1183 | (_nreverse next x)))) 1184 | (defun nreverse (list) ; (nreverse (quote (a b c d))) => (d c b a)) 1185 | (cond (list (_nreverse list nil)))) 1186 | 1187 | (defun last (list) 1188 | (if (atom (cdr list)) 1189 | list 1190 | (last (cdr list)))) 1191 | 1192 | (defun nconc (&rest lists) 1193 | (if (null (cdr lists)) 1194 | (car lists) 1195 | (if (null (car lists)) 1196 | (apply nconc (cdr lists)) 1197 | (setcdr (last (car lists)) 1198 | (apply nconc (cdr lists))) 1199 | (car lists)))) 1200 | 1201 | (defmacro while (test &rest body) 1202 | (let ((loop (gensym))) 1203 | \`(letrec ((,loop (lambda () (cond (,test ,@body (,loop)))))) 1204 | (,loop)))) 1205 | 1206 | (defmacro dolist (spec &rest body) ; (dolist (name list [result]) body...) 1207 | (let ((name (car spec)) 1208 | (list (gensym))) 1209 | \`(let (,name 1210 | (,list ,(cadr spec))) 1211 | (while ,list 1212 | (setq ,name (car ,list)) 1213 | ,@body 1214 | (setq ,list (cdr ,list))) 1215 | ,@(if (cddr spec) 1216 | \`((setq ,name nil) 1217 | ,(caddr spec)))))) 1218 | 1219 | (defmacro dotimes (spec &rest body) ; (dotimes (name count [result]) body...) 1220 | (let ((name (car spec)) 1221 | (count (gensym))) 1222 | \`(let ((,name 0) 1223 | (,count ,(cadr spec))) 1224 | (while (< ,name ,count) 1225 | ,@body 1226 | (setq ,name (+ ,name 1))) 1227 | ,@(if (cddr spec) 1228 | \`(,(caddr spec)))))) 1229 | `; 1230 | if (typeof Deno !== 'undefined') { 1231 | const decoder = new TextDecoder(); 1232 | const buf = new Uint8Array(8000); 1233 | readLine = async function() { 1234 | const n = await Deno.stdin.read(buf); 1235 | if (n == null) return null; 1236 | return decoder.decode(buf.subarray(0, n)); 1237 | }; 1238 | const encoder = new TextEncoder(); 1239 | write = (s)=>{ 1240 | const bb = encoder.encode(s); 1241 | for(let n = 0; n < bb.length;)n += Deno.stdout.writeSync(bb.subarray(n)); 1242 | }; 1243 | exit = Deno.exit; 1244 | const main = async function() { 1245 | const interp = new Interp(); 1246 | run(interp, prelude); 1247 | let repl = undefined; 1248 | const args = Deno.args.length == 0 ? [ 1249 | '-' 1250 | ] : Deno.args; 1251 | try { 1252 | for (const fileName of args){ 1253 | if (fileName === '-') { 1254 | if (repl === undefined) { 1255 | repl = new REPL(); 1256 | await repl.readEvalPrintLoop(interp); 1257 | } 1258 | } else { 1259 | const text = Deno.readTextFileSync(fileName); 1260 | run(interp, text); 1261 | } 1262 | } 1263 | } catch (ex) { 1264 | console.log(ex); 1265 | exit(1); 1266 | } 1267 | }; 1268 | main(); 1269 | } else if (typeof process === 'object') { 1270 | let readLine_atFirst = true; 1271 | let readLine_resolve = null; 1272 | readLine = ()=>new Promise((resolve, _reject)=>{ 1273 | if (readLine_atFirst) { 1274 | process.stdin.setEncoding('utf8'); 1275 | process.stdin.on('data', (line)=>{ 1276 | if (readLine_resolve !== null) { 1277 | readLine_resolve(line); 1278 | readLine_resolve = null; 1279 | } 1280 | }); 1281 | process.stdin.on('end', ()=>{ 1282 | if (readLine_resolve !== null) { 1283 | readLine_resolve(null); 1284 | readLine_resolve = null; 1285 | } 1286 | }); 1287 | readLine_atFirst = false; 1288 | } 1289 | readLine_resolve = resolve; 1290 | }) 1291 | ; 1292 | write = (s)=>process.stdout.write(s) 1293 | ; 1294 | exit = process.exit; 1295 | const main = async function() { 1296 | const interp = new Interp(); 1297 | run(interp, prelude); 1298 | let repl = undefined; 1299 | let fs = undefined; 1300 | let argv = process.argv; 1301 | if (argv.length <= 2) argv = [ 1302 | '', 1303 | '', 1304 | '-' 1305 | ]; 1306 | try { 1307 | for(let i = 2; i < argv.length; i++){ 1308 | const fileName = argv[i]; 1309 | if (fileName == '-') { 1310 | if (repl === undefined) { 1311 | repl = new REPL(); 1312 | await repl.readEvalPrintLoop(interp); 1313 | } 1314 | } else { 1315 | fs = fs || require('fs'); 1316 | const text = fs.readFileSync(fileName, 'utf8'); 1317 | run(interp, text); 1318 | } 1319 | } 1320 | } catch (ex) { 1321 | console.log(ex); 1322 | exit(1); 1323 | } 1324 | }; 1325 | main(); 1326 | } 1327 | -------------------------------------------------------------------------------- /examples/run-on-web.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lisp in TypeScript on Browser 5 | 6 | 17 | 18 | 19 | 20 |

A Lisp interpreter in TypeScript on your Browser

21 |
March 28, 2022 (R4.3.28 in Japan)
SUZUKI Hisao 22 |
23 | 24 |

25 | Now you have a Lisp interpreter on your browser. 26 | It does not communicate with any servers; it runs offline. 27 | 28 |

29 | Click the Evaluate button and 30 | Lisp expressions written in the text area will be evaluated; 31 | the output and result will be displayed below the button. 32 | 33 |

34 | Initially, a program that calculates Fibonacci numbers for 5 and 10 35 | is written in the text area as an example. 36 | Just click the Evaluate button and 37 | 5 and 55 will be displayed. 38 | Modify the program or wipe it off and write your Lisp expressions there. 39 | 40 |

41 | The Lisp interpreter lisp.js used here has been compiled 42 | from lisp.ts with a TypeScript compiler in Deno 1.20. 43 | 44 |

45 | 55 | 56 |

57 | 58 |

59 |


 60 | 
 61 |     
 99 |   
100 | 
101 | 


--------------------------------------------------------------------------------
/lisp.ts:
--------------------------------------------------------------------------------
   1 | /*
   2 |   Nukata Lisp 2.1.0 in TypeScript 4.6 by SUZUKI Hisao (H28.02.08/R04.03.28)
   3 |     $ deno bundle lisp.ts lisp.js && deno run lisp.js
   4 |   | $ deno bundle lisp.ts lisp.js && node lisp.js
   5 |   | $ deno run lisp.ts
   6 | */
   7 | 
   8 | import {
   9 |     Numeric, ZERO, ONE, isNumeric,
  10 |     add, subtract, multiply, divide, quotient, remainder, compare,
  11 |     tryToParse, convertToString
  12 | } from "./arith.ts";
  13 | 
  14 | // An inefficient substitution of assert statement in Dart
  15 | function assert(x: boolean, message?: string): asserts x {
  16 |     if (! x)
  17 |         throw new Error("Assertion Failure: " + (message || ""));
  18 | }
  19 | 
  20 | // Read a line as a string, or null when EOF is read.
  21 | let readLine: () => Promise;
  22 | 
  23 | let write: (s: string) => void; // Output string s (a new line on \n char).
  24 | let exit: (n: number) => void;  // Terminate the process with exit code n.
  25 | 
  26 | //----------------------------------------------------------------------
  27 | 
  28 | // Lisp cons cell
  29 | class Cell {
  30 |     constructor(public car: unknown,
  31 |                 public cdr: unknown) {}
  32 | 
  33 |     toString(): string { return "(" + this.car + " . " + this.cdr + ")" }
  34 | 
  35 |     // Length as a list
  36 |     get length(): number { return foldl(0, this, (i, _) => i + 1) }
  37 | }
  38 | 
  39 | // Lisp's list
  40 | type List = Cell | null;
  41 | 
  42 | // foldl(x, (a b c), fn) => fn(fn(fn(x, a), b), c)
  43 | function foldl(x: T, j: List, fn: (x: T, y: unknown) => T): T {
  44 |     while (j !== null) {
  45 |         x = fn(x, j.car);
  46 |         j = j.cdr as List;
  47 |     }
  48 |     return x;
  49 | }
  50 | 
  51 | // mapcar((a b c), fn) => (fn(a) fn(b) fn(c))
  52 | function mapcar(j: List, fn: (x: unknown) => unknown): List {
  53 |     if (j === null)
  54 |         return null;
  55 |     const a = fn(j.car);
  56 |     let d = j.cdr;
  57 |     if (d instanceof Cell)
  58 |         d = mapcar(d, fn);
  59 |     if (Object.is(j.car, a) && Object.is(j.cdr, d))
  60 |         return j;
  61 |     return new Cell(a, d);
  62 | }
  63 | 
  64 | 
  65 | // Lisp symbol
  66 | class Sym {
  67 |     // Construct an uninterned symbol.
  68 |     constructor(public readonly name: string) {}
  69 | 
  70 |     toString(): string { return this.name }
  71 | 
  72 |     // Is it interned?
  73 |     get isInterned(): boolean { return symTable[this.name] === this; }
  74 | }
  75 | 
  76 | // Expression keyword
  77 | class Keyword extends Sym {
  78 |     constructor(name: string) {
  79 |         super(name);
  80 |     }
  81 | }
  82 | 
  83 | // The table of interned symbols
  84 | const symTable: {[key: string]: Sym} = {};
  85 | 
  86 | // Construct an interned symbol; construct a Keyword if isKeyword holds.
  87 | function newSym(name: string, isKeyword = false): Sym {
  88 |     let result = symTable[name];
  89 |     assert((result === undefined || ! isKeyword), name);
  90 |     if (result === undefined) {
  91 |         result = isKeyword ? new Keyword(name) : new Sym(name);
  92 |         symTable[name] = result;
  93 |     }
  94 |     return result;
  95 | }
  96 | 
  97 | function newKeyword(name: string): Keyword { return newSym(name, true) }
  98 | 
  99 | const backQuoteSym = newSym("`");
 100 | const commaAtSym = newSym(",@");
 101 | const commaSym = newSym(",");
 102 | const dotSym = newSym(".");
 103 | const leftParenSym = newSym("(");
 104 | const rightParenSym = newSym(")");
 105 | const singleQuoteSym = newSym("'");
 106 | 
 107 | const appendSym = newSym("append");
 108 | const consSym = newSym("cons");
 109 | const listSym = newSym("list");
 110 | const restSym = newSym ("&rest");
 111 | const unquoteSym = newSym("unquote");
 112 | const unquoteSplicingSym = newSym("unquote-splicing");
 113 | 
 114 | const condSym = newKeyword("cond");
 115 | const lambdaSym = newKeyword("lambda");
 116 | const macroSym = newKeyword("macro");
 117 | const prognSym = newKeyword("progn");
 118 | const quasiquoteSym = newKeyword("quasiquote");
 119 | const quoteSym = newKeyword("quote");
 120 | const setqSym = newKeyword("setq");
 121 | 
 122 | //----------------------------------------------------------------------
 123 | 
 124 | // Get cdr of list x as a Cell or null.
 125 | function cdrCell(x: Cell): List {
 126 |     const k = x.cdr;
 127 |     if (k instanceof Cell)
 128 |         return k;
 129 |     else if (k === null)
 130 |         return null;
 131 |     else
 132 |         throw new EvalException("proper list expected", x);
 133 | }
 134 | 
 135 | 
 136 | // Common base class of Lisp functions
 137 | abstract class Func {
 138 |     // carity is a number of arguments, made negative if the func has &rest.
 139 |     constructor(public readonly carity: number) {}
 140 | 
 141 |     get arity(): number {
 142 |         return (this.carity < 0) ? -this.carity : this.carity;
 143 |     }
 144 | 
 145 |     get hasRest(): boolean {
 146 |         return (this.carity < 0);
 147 |     }
 148 | 
 149 |     get fixedArgs(): number {   // Number of fixed arguments
 150 |         return (this.carity < 0) ? -this.carity - 1 : this.carity;
 151 |     }
 152 | 
 153 |     // Make a call-frame from a list of actual arguments.
 154 |     makeFrame(arg: List): unknown[] {
 155 |         const frame = new Array(this.arity);
 156 |         const n = this.fixedArgs;
 157 |         let i = 0;
 158 |         for (; i < n && arg !== null; i++) { // Set the list of fiexed args.
 159 |             frame[i] = arg.car;
 160 |             arg = cdrCell(arg);
 161 |         }
 162 |         if (i !== n || (arg !== null && ! this.hasRest))
 163 |             throw new EvalException("arity not matched", this);
 164 |         if (this.hasRest)
 165 |             frame[n] = arg;
 166 |         return frame;
 167 |     }
 168 | 
 169 |     // Evaluate each expression of a frame.
 170 |     evalFrame(frame: unknown[], interp: Interp, env: List): void {
 171 |         const n = this.fixedArgs;
 172 |         for (let i = 0; i < n; i++)
 173 |             frame[i] = interp.eval(frame[i], env);
 174 |         if (this.hasRest && frame[n] instanceof Cell) {
 175 |             let z: List = null;
 176 |             let y: List = null;
 177 |             for (let j = frame[n] as List; j !== null; j = cdrCell(j)) {
 178 |                 const e = interp.eval(j.car, env);
 179 |                 const x = new Cell(e, null);
 180 |                 if (z === null) {
 181 |                     z = x;
 182 |                 } else {
 183 |                     assert(y !== null);
 184 |                     y.cdr = x;
 185 |                 }
 186 |                 y = x;
 187 |             }
 188 |             frame[n] = z;
 189 |         }
 190 |     }
 191 | }
 192 | 
 193 | 
 194 | // Common base class of functions which are defined with Lisp expressions
 195 | abstract class DefinedFunc extends Func {
 196 |     // body is a Lisp list as the function body.
 197 |     constructor(carity: number,
 198 |                 public readonly body: List) {
 199 |         super(carity);
 200 |     }
 201 | }
 202 | 
 203 | // Common function type which represents any factory methods of DefinedFunc
 204 | type FuncFactory = (carity: number, body: List, env: List) => DefinedFunc;
 205 | 
 206 | 
 207 | // Compiled macro expression
 208 | class Macro extends DefinedFunc {
 209 |     constructor(carity: number, body: List) {
 210 |         super(carity, body);
 211 |     }
 212 | 
 213 |     toString(): string { return `#` }
 214 | 
 215 |     // Expand the macro with a list of actual arguments.
 216 |     expandWith(interp: Interp, arg: List): unknown {
 217 |         const frame = this.makeFrame(arg);
 218 |         const env = new Cell(frame, null);
 219 |         let x: unknown = null;
 220 |         for (let j = this.body; j !== null; j = cdrCell(j))
 221 |             x = interp.eval(j.car, env);
 222 |         return x;
 223 |     }
 224 | 
 225 |     static make(carity: number, body: List, env: List): DefinedFunc {
 226 |         assert(env === null);
 227 |         return new Macro(carity, body);
 228 |     }
 229 | }
 230 | 
 231 | 
 232 | // Compiled lambda expression (within another function)
 233 | class Lambda extends DefinedFunc {
 234 |     constructor(carity: number, body: List) {
 235 |         super(carity, body);
 236 |     }
 237 | 
 238 |     toString(): string { return `#` }
 239 | 
 240 |     static make(carity: number, body: List, env: List): DefinedFunc {
 241 |         assert(env === null);
 242 |         return new Lambda(carity, body);
 243 |     }
 244 | }
 245 | 
 246 | 
 247 | // Compiled lambda expression (Closure with environment)
 248 | class Closure extends DefinedFunc {
 249 |     // env is the environment of the closure.
 250 |     constructor(carity: number, body: List, private readonly env: List) {
 251 |         super(carity, body);
 252 |     }
 253 | 
 254 |     static makeFrom(x: Lambda, env: List) {
 255 |         return new Closure(x.carity, x.body, env);
 256 |     }
 257 |     
 258 |     toString(): string {
 259 |         return `#`;
 260 |     }
 261 | 
 262 |     // Make a new environment from a list of actual arguments.
 263 |     makeEnv(interp: Interp, arg: List, interpEnv: List): Cell {
 264 |         const frame = this.makeFrame(arg);
 265 |         this.evalFrame(frame, interp, interpEnv);
 266 |         return new Cell(frame, this.env); // Prepend the frame to the env.
 267 |     }
 268 | 
 269 |     static make(carity: number, body: List, env: List): DefinedFunc {
 270 |         return new Closure(carity, body, env);
 271 |     }
 272 | }
 273 | 
 274 | 
 275 | // Function type which represents any built-in function bodies
 276 | type BuiltInFuncBody = (frame: unknown[]) => unknown;
 277 | 
 278 | // Built-in function
 279 | class BuiltInFunc extends Func {
 280 |     // name is the function name; body is the function body.
 281 |     constructor(private readonly name: string,
 282 |                 carity: number,
 283 |                 private readonly body: BuiltInFuncBody) {
 284 |         super(carity);
 285 |     }
 286 | 
 287 |     toString(): string { return "#<" + this.name + ":" + this.carity + ">" }
 288 | 
 289 |     // Invoke the built-in function with a list of actual arguments.
 290 |     evalWith(interp: Interp, arg: List, interpEnv: List): unknown {
 291 |         const frame = this.makeFrame(arg);
 292 |         this.evalFrame(frame, interp, interpEnv);
 293 |         try {
 294 |             return this.body(frame);
 295 |         } catch (ex) {
 296 |             if (ex instanceof EvalException)
 297 |                 throw ex;
 298 |             else
 299 |                 throw new EvalException(ex + " -- " + this.name, frame);
 300 |         }
 301 |     }
 302 | }
 303 | 
 304 | 
 305 | // Bound variable in a compiled lambda/macro expression
 306 | class Arg {
 307 |     constructor(public readonly level: number,
 308 |                 public readonly offset: number,
 309 |                 public readonly symbol: Sym) {}
 310 | 
 311 |     toString(): string {
 312 |         return "#" + this.level + ":" + this.offset + ":" + this.symbol;
 313 |     }
 314 | 
 315 |     // Set a value x to the location corresponding to the variable in env.
 316 |     setValue(x: unknown, env: Cell): void {
 317 |         for (let i = 0; i < this.level; i++)
 318 |             env = env.cdr as Cell;
 319 |         (env.car as unknown[])[this.offset] = x;
 320 |     }
 321 | 
 322 |     // Get a value from the location corresponding to the variable in env.
 323 |     getValue(env: Cell): unknown {
 324 |         for (let i = 0; i < this.level; i++)
 325 |             env = env.cdr as Cell;
 326 |         return (env.car as unknown[])[this.offset];
 327 |     }
 328 | }
 329 | 
 330 | 
 331 | // Exception in evaluation
 332 | class EvalException extends Error {
 333 |     readonly trace: string[] = [];
 334 | 
 335 |     constructor(msg: string, x: unknown, quoteString=true) {
 336 |         super(msg + ": " + str(x, quoteString));
 337 |     }
 338 | 
 339 |     toString(): string {
 340 |         let s = "EvalException: " + this.message;
 341 |         for (const line of this.trace)
 342 |             s += "\n\t" + line;
 343 |         return s;
 344 |     }
 345 | }
 346 | 
 347 | // Exception which indicates an absence of a variable
 348 | class NotVariableException extends EvalException {
 349 |     constructor(x: unknown) {
 350 |         super("variable expected", x);
 351 |     }
 352 | }
 353 | 
 354 | 
 355 | // Exception thrown when something does not have an expected format
 356 | class FormatException extends Error {
 357 |     constructor(msg: string) {
 358 |         super(msg);
 359 |     }
 360 | }
 361 | 
 362 | 
 363 | // Singleton for end-of-file
 364 | const EndOfFile = { toString: () => "EOF" };
 365 | 
 366 | //----------------------------------------------------------------------
 367 | 
 368 | // Core of the interpreter
 369 | class Interp {
 370 |     // Table of the global values of symbols
 371 |     private readonly globals: Map = new Map();
 372 | 
 373 |     constructor() {
 374 |         this.def("car", 1, (a: unknown[]) =>
 375 |                  (a[0] === null) ? null : (a[0]).car);
 376 |         this.def("cdr", 1, (a: unknown[]) =>
 377 |                  (a[0] === null) ? null : (a[0]).cdr);
 378 |         this.def("cons", 2, (a: unknown[]) =>
 379 |                  new Cell(a[0], a[1]));
 380 |         this.def("atom", 1, (a: unknown[]) =>
 381 |                  (a[0] instanceof Cell) ? null : true);
 382 |         this.def("eq", 2, (a: unknown[]) =>
 383 |                  Object.is(a[0], a[1]) ? true : null);
 384 | 
 385 |         this.def("list", -1, (a: unknown[]) => a[0]);
 386 |         this.def("rplaca", 2, (a: unknown[]) =>
 387 |                  { ( a[0]).car = a[1]; return a[1] });
 388 |         this.def("rplacd", 2, (a: unknown[]) =>
 389 |                  { ( a[0]).cdr = a[1]; return a[1] });
 390 |         this.def("length", 1, (a: unknown[]) =>
 391 |                  (a[0] === null ? 0 :
 392 |                   quotient((a[0] as Cell | string).length, 1)));
 393 |         this.def("stringp", 1, (a: unknown[]) =>
 394 |                  (typeof a[0] === "string") ? true : null);
 395 |         this.def("numberp", 1, (a: unknown[]) =>
 396 |                  isNumeric(a[0]) ? true : null);
 397 | 
 398 |         this.def("eql", 2, (a: unknown[]) => {
 399 |             const x = a[0];
 400 |             const y = a[1];
 401 |             return (x === y) ? true :
 402 |                 (isNumeric(x) && isNumeric(y) && compare(x, y) === 0) ? true :
 403 |                 null;
 404 |         });
 405 | 
 406 |         this.def("<", 2, (a: unknown[]) =>
 407 |                  (compare(a[0] as Numeric, a[1] as Numeric) < 0) ? true :
 408 |                  null);
 409 | 
 410 |         this.def("%", 2, (a: unknown[]) =>
 411 |                  remainder(a[0] as Numeric, a[1] as Numeric));
 412 | 
 413 |         this.def("mod", 2, (a: unknown[]) => {
 414 |             const x = a[0] as Numeric;
 415 |             const y = a[1] as Numeric;
 416 |             const q = remainder(x, y);
 417 |             return (compare(multiply(x, y), ZERO) < 0) ? add(q, y) : q;
 418 |         });
 419 | 
 420 |         this.def("+", -1, (a: unknown[]) =>
 421 |                  foldl(ZERO, a[0] as List,
 422 |                        (i, j) => add(i as Numeric, j as Numeric)));
 423 | 
 424 |         this.def("*", -1, (a: unknown[]) =>
 425 |                  foldl(ONE, a[0] as List,
 426 |                        (i, j) => multiply(i as Numeric, j as Numeric)));
 427 | 
 428 |         this.def("-", -2, (a: unknown[]) => {
 429 |             const x = a[0] as Numeric;
 430 |             const y = a[1] as List;
 431 |             return (y == null) ? -x :
 432 |                 foldl(x, y, (i, j) => subtract(i as Numeric, j as Numeric));
 433 |         })
 434 | 
 435 |         this.def("/", -3, (a: unknown[]) => 
 436 |                  foldl(divide(a[0] as Numeric, a[1] as Numeric),
 437 |                        a[2] as List,
 438 |                        (i, j) => divide(i as Numeric, j as Numeric)));
 439 | 
 440 |         this.def("truncate", -2, (a: unknown[]) => {
 441 |             const x = a[0] as Numeric;
 442 |             const y = a[1] as List;
 443 |             if (y === null) {
 444 |                 return quotient(x, ONE);
 445 |             } else if (y.cdr === null) {
 446 |                 return quotient(x, y.car as Numeric);
 447 |             } else {
 448 |                 throw "one or two arguments expected";
 449 |             }
 450 |         });
 451 | 
 452 |         this.def("prin1", 1, (a: unknown[]) => {
 453 |             write(str(a[0], true));
 454 |             return a[0];
 455 |         });
 456 |         this.def("princ", 1, (a: unknown[]) => {
 457 |             write(str(a[0], false));
 458 |             return a[0];
 459 |         });
 460 |         this.def("terpri", 0, (_a: unknown[]) => {
 461 |             write("\n");
 462 |             return true;
 463 |         });
 464 | 
 465 |         const gensymCounter = newSym("*gensym-counter*");
 466 |         this.globals.set(gensymCounter, ONE);
 467 |         this.def("gensym", 0, (_a: unknown[]) => {
 468 |             const i = this.globals.get(gensymCounter) as Numeric;
 469 |             this.globals.set(gensymCounter, add(i, ONE));
 470 |             return new Sym("G" + i); // an uninterned symbol
 471 |         });
 472 | 
 473 |         this.def("make-symbol", 1, (a: unknown[]) => new Sym(a[0] as string));
 474 |         this.def("intern", 1, (a: unknown[]) => newSym(a[0] as string));
 475 |         this.def("symbol-name", 1, (a: unknown[]) => ( a[0]).name);
 476 | 
 477 |         this.def("apply", 2, (a: unknown[]) =>
 478 |                  this.eval(new Cell(a[0],
 479 |                                     mapcar(a[1] as List, qqQuote)),
 480 |                            null));
 481 | 
 482 |         this.def("exit", 1, (a: unknown[]) => exit(Number(a[0])));
 483 |         this.def("dump", 0, (_a: unknown[]) => {
 484 |             let s: List = null;
 485 |             for (const x of this.globals.keys())
 486 |                 s = new Cell(x, s);
 487 |             return s;
 488 |         });
 489 | 
 490 |         // named after Tōkai-dō Mikawa-koku Nukata-gun (東海道 三河国 額田郡)
 491 |         this.globals.set(newSym("*version*"),
 492 |                          new Cell(2.10,
 493 |                                   new Cell("TypeScript",
 494 |                                            new Cell("Nukata Lisp", null))));
 495 |     }
 496 | 
 497 |     // Define a built-in function by giving a name, a carity, and a body.
 498 |     def(name: string, carity: number, body: BuiltInFuncBody) {
 499 |         const sym = newSym(name);
 500 |         this.globals.set(sym, new BuiltInFunc(name, carity, body));
 501 |     }
 502 | 
 503 |     // Evaluate a Lisp expression in an environment.
 504 |     eval(x: unknown, env: List): unknown {
 505 |         try {
 506 |             for (;;) {
 507 |                 if (x instanceof Arg) {
 508 |                     assert(env !== null);
 509 |                     return x.getValue(env);
 510 |                 } else if (x instanceof Sym) {
 511 |                     const value = this.globals.get(x);
 512 |                     if (value === undefined)
 513 |                         throw new EvalException("void variable", x);
 514 |                     return value;
 515 |                 } else if (x instanceof Cell) {
 516 |                     let fn = x.car;
 517 |                     const arg = cdrCell(x);
 518 |                     if (fn instanceof Keyword) {
 519 |                         switch ( fn) {
 520 |                         case quoteSym:
 521 |                             if (arg !== null && arg.cdr === null)
 522 |                                 return arg.car;
 523 |                             throw new EvalException("bad quote", x);
 524 |                         case prognSym:
 525 |                             x = this.evalProgN(arg, env);
 526 |                             break;
 527 |                         case condSym:
 528 |                             x = this.evalCond(arg, env);
 529 |                             break;
 530 |                         case setqSym:
 531 |                             return this.evalSetQ(arg, env);
 532 |                         case lambdaSym:
 533 |                             return this.compile(arg, env, Closure.make);
 534 |                         case macroSym:
 535 |                             if (env !== null)
 536 |                                 throw new EvalException("nested macro", x);
 537 |                             return this.compile(arg, null, Macro.make);
 538 |                         case quasiquoteSym:
 539 |                             if (arg !== null && arg.cdr === null) {
 540 |                                 x = qqExpand(arg.car);
 541 |                                 break;
 542 |                             }
 543 |                             throw new EvalException("bad quasiquote", x);
 544 |                         default:
 545 |                             throw new EvalException("bad keyword", fn);
 546 |                         }
 547 |                     } else {    // Application of a function
 548 |                         // Expand fn = eval(fn, env) here on Sym for speed.
 549 |                         if (fn instanceof Sym) {
 550 |                             fn = this.globals.get(fn);
 551 |                             if (fn === undefined)
 552 |                                 throw new EvalException("undefined", x.car);
 553 |                         } else {
 554 |                             fn = this.eval(fn, env);
 555 |                         }
 556 | 
 557 |                         if (fn instanceof Closure) {
 558 |                             env = fn.makeEnv(this, arg, env);
 559 |                             x = this.evalProgN(fn.body, env);
 560 |                         } else if (fn instanceof Macro) {
 561 |                             x = fn.expandWith(this, arg);
 562 |                         } else if (fn instanceof BuiltInFunc) {
 563 |                             return fn.evalWith(this, arg, env);
 564 |                         } else {
 565 |                             throw new EvalException("not applicable", fn);
 566 |                         }
 567 |                     }
 568 |                 } else if (x instanceof Lambda) {
 569 |                     return Closure.makeFrom(x, env);
 570 |                 } else {
 571 |                     return x;   // numbers, strings, null etc.
 572 |                 }
 573 |             }
 574 |         } catch (ex) {
 575 |             if (ex instanceof EvalException) {
 576 |                 if (ex.trace.length < 10)
 577 |                     ex.trace.push(str(x));
 578 |             }
 579 |             throw ex;
 580 |         }
 581 |     }
 582 | 
 583 |     // (progn E1 E2 .. En) => Evaluate E1, E2, .. except for En and return it.
 584 |     private evalProgN(j: List, env: List): unknown {
 585 |         if (j === null)
 586 |             return null;
 587 |         for (;;) {
 588 |             const x = j.car;
 589 |             j = cdrCell(j);
 590 |             if (j === null)
 591 |                 return x; // The tail exp will be evaluated at the caller.
 592 |             this.eval(x, env);
 593 |         }
 594 |     }
 595 | 
 596 |     // Evaluate a conditional expression and return the selection unevaluated.
 597 |     private evalCond(j: List, env: List): unknown {
 598 |         for (; j !== null; j = cdrCell(j)) {
 599 |             const clause = j.car;
 600 |             if (clause instanceof Cell) {
 601 |                 const result = this.eval(clause.car, env);
 602 |                 if (result !== null) { // If the condition holds
 603 |                     const body = cdrCell(clause);
 604 |                     if (body === null)
 605 |                         return qqQuote(result);
 606 |                     else
 607 |                         return this.evalProgN(body, env);
 608 |                 }
 609 |             } else if (clause !== null) {
 610 |                 throw new EvalException("cond test expected", clause);
 611 |             }
 612 |         }
 613 |         return null;            // No clause holds.
 614 |     }
 615 | 
 616 |     // (setq V1 E1 ..) => Evaluate Ei and assign it to Vi; return the last.
 617 |     private evalSetQ(j: List, env: List): unknown {
 618 |         let result: unknown = null;
 619 |         for (; j !== null; j = cdrCell(j)) {
 620 |             const lval = j.car;
 621 |             j = cdrCell(j);
 622 |             if (j === null)
 623 |                 throw new EvalException("right value expected", lval);
 624 |             result = this.eval(j.car, env);
 625 |             if (lval instanceof Arg) {
 626 |                 assert(env !== null);
 627 |                 lval.setValue(result, env);
 628 |             } else if (lval instanceof Sym && !(lval instanceof Keyword)) {
 629 |                 this.globals.set(lval, result);
 630 |             } else {
 631 |                 throw new NotVariableException(lval);
 632 |             }
 633 |         }
 634 |         return result;
 635 |     }
 636 | 
 637 |     // Compile a Lisp list (macro ..) or (lambda ..).
 638 |     private compile(arg: List, env: List, make: FuncFactory): DefinedFunc {
 639 |         if (arg === null)
 640 |             throw new EvalException("arglist and body expected", arg);
 641 |         const table = new Map();
 642 |         const [hasRest, arity] = makeArgTable(arg.car, table);
 643 |         let body = cdrCell(arg);
 644 |         body = scanForArgs(body, table) as List;
 645 |         // Expand macros up to 20 nestings
 646 |         body = this.expandMacros(body, 20) as List;
 647 |         body = this.compileInners(body) as List;
 648 |         return make((hasRest) ? -arity : arity, body, env);
 649 |     }
 650 | 
 651 |     // Expand macros and quasi-quotations in an expression.
 652 |     private expandMacros(j: unknown, count: number): unknown {
 653 |         if (count > 0 && j instanceof Cell) {
 654 |             let k = j.car;
 655 |             switch (k) {
 656 |             case quoteSym:
 657 |             case lambdaSym:
 658 |             case macroSym:
 659 |                 return j;
 660 |             case quasiquoteSym: {
 661 |                 const d = cdrCell(j);
 662 |                 if (d !== null && d.cdr === null) {
 663 |                     const z = qqExpand(d.car);
 664 |                     return this.expandMacros(z, count);
 665 |                 }
 666 |                 throw new EvalException("bad quasiquote", j);
 667 |             }
 668 |             default:
 669 |                 if (k instanceof Sym)
 670 |                     k = this.globals.get(k);
 671 |                 if (k instanceof Macro) {
 672 |                     const d = cdrCell(j);
 673 |                     const z = k.expandWith(this, d);
 674 |                     return this.expandMacros(z, count - 1);
 675 |                 }
 676 |                 return mapcar(j, (x) => this.expandMacros(x, count));
 677 |             }
 678 |         } else {
 679 |             return j;
 680 |         }
 681 |     }
 682 | 
 683 |     // Replace inner lambda expressions with Lambda instances.
 684 |     private compileInners(j: unknown): unknown {
 685 |         if (j instanceof Cell) {
 686 |             const k = j.car;
 687 |             switch (k) {
 688 |             case quoteSym:
 689 |                 return j;
 690 |             case lambdaSym: {
 691 |                 const d = cdrCell(j);
 692 |                 return this.compile(d, null, Lambda.make);
 693 |             }
 694 |             case macroSym:
 695 |                 throw new EvalException("nested macro", j);
 696 |             default:
 697 |                 return mapcar(j, (x) => this.compileInners(x));
 698 |             }
 699 |         } else {
 700 |             return j;
 701 |         }
 702 |     }
 703 | }
 704 | 
 705 | //----------------------------------------------------------------------
 706 | 
 707 | // Make an argument table; return a pair of rest-yes/no and the arity.
 708 | function makeArgTable(arg: unknown, table: Map): [boolean, number]
 709 | {
 710 |     if (arg === null) {
 711 |         return [false, 0];
 712 |     } else if (arg instanceof Cell) {
 713 |         let ag = arg as List;
 714 |         let offset = 0;         // offset value within the call-frame
 715 |         let hasRest = false;
 716 |         for (; ag !== null; ag = cdrCell(ag)) {
 717 |             let j = ag.car;
 718 |             if (hasRest)
 719 |                 throw new EvalException("2nd rest", j);
 720 |             if (j === restSym) { // &rest var
 721 |                 ag = cdrCell(ag);
 722 |                 if (ag === null)
 723 |                     throw new NotVariableException(ag);
 724 |                 j = ag.car;
 725 |                 if (j === restSym)
 726 |                     throw new NotVariableException(j);
 727 |                 hasRest = true;
 728 |             }
 729 |             let sym: Sym;
 730 |             if (j instanceof Sym)
 731 |                 sym = j;
 732 |             else if (j instanceof Arg)
 733 |                 sym = j.symbol;
 734 |             else
 735 |                 throw new NotVariableException(j);
 736 |             if (table.has(sym))
 737 |                 throw new EvalException("duplicated argument name", j);
 738 |             table.set(sym, new Arg(0, offset, sym));
 739 |             offset++;
 740 |         }
 741 |         return [hasRest, offset];
 742 |     } else {
 743 |         throw new EvalException("arglist expected", arg);
 744 |     }
 745 | }
 746 | 
 747 | // Scan 'j' for formal arguments in 'table' and replace them with Args.
 748 | // And scan 'j' for free Args not in 'table' and promote their levels.
 749 | function scanForArgs(j: unknown, table: Map): unknown {
 750 |     if (j instanceof Sym) {
 751 |         const k = table.get(j);
 752 |         return (k === undefined) ? j : k;
 753 |     } else if (j instanceof Arg) {
 754 |         const k = table.get(j.symbol);
 755 |         return (k === undefined) ?
 756 |             new Arg(j.level + 1, j.offset, j.symbol) : k;
 757 |     } else if (j instanceof Cell) {
 758 |         if (j.car === quoteSym) {
 759 |             return j;
 760 |         } else if (j.car === quasiquoteSym) {
 761 |             return new Cell(quasiquoteSym, scanForQQ(j.cdr, table, 0));
 762 |         } else {
 763 |             return mapcar(j, (x) => scanForArgs(x, table));
 764 |         }
 765 |     } else {
 766 |         return j;
 767 |     }
 768 | }
 769 | 
 770 | // Scan for quasi-quotes and scanForArgs them depending on the nesting level.
 771 | function scanForQQ(j: unknown, table: Map, level: number): unknown {
 772 |     if (j instanceof Cell) {
 773 |         const k = j.car;
 774 |         if (k === quasiquoteSym) {
 775 |             return new Cell(k, scanForQQ(j.cdr, table, level + 1));
 776 |         } else if (k === unquoteSym || k === unquoteSplicingSym) {
 777 |             const d = (level === 0) ?
 778 |                 scanForArgs(j.cdr, table) :
 779 |                 scanForQQ(j.cdr, table, level - 1);
 780 |             if (Object.is(d, j.cdr))
 781 |                 return j;
 782 |             return new Cell(k, d);
 783 |         } else {
 784 |             return mapcar(j, (x) => scanForQQ(x, table, level));
 785 |         }
 786 |     } else {
 787 |         return j;
 788 |     }
 789 | }
 790 | 
 791 | //----------------------------------------------------------------------
 792 | // Quasi-Quotation
 793 | 
 794 | // Expand x of any quasi-quotation `x into the equivalent S-expression.
 795 | function qqExpand(x: unknown): unknown {
 796 |     return qqExpand0(x, 0);     // Begin with the nesting level 0.
 797 | }
 798 | 
 799 | function qqExpand0(x: unknown, level: number): unknown {
 800 |     if (x instanceof Cell) {
 801 |         if (x.car === unquoteSym) { // ,a
 802 |             if (level === 0)
 803 |                 return (x.cdr as Cell).car; // ,a => a
 804 |         }
 805 |         const t = qqExpand1(x, level);
 806 |         if (t.car instanceof Cell && t.cdr === null) {
 807 |             const k = t.car;
 808 |             if (k.car == listSym || k.car === consSym)
 809 |                 return k;
 810 |         }
 811 |         return new Cell(appendSym, t);
 812 |     } else {
 813 |         return qqQuote(x);
 814 |     }
 815 | }
 816 | 
 817 | // Quote x so that the result evaluates to x.
 818 | function qqQuote(x: unknown): unknown {
 819 |     if (x instanceof Sym || x instanceof Cell)
 820 |         return new Cell(quoteSym, new Cell(x, null));
 821 |     return x;
 822 | }
 823 | 
 824 | // Expand x of `x so that the result can be used as an argument of append.
 825 | // Example 1: (,a b) => ((list a 'b))
 826 | // Example 2: (,a ,@(cons 2 3)) => ((cons a (cons 2 3)))
 827 | function qqExpand1(x: unknown, level: number): Cell {
 828 |     if (x instanceof Cell) {
 829 |         if (x.car === unquoteSym) { // ,a
 830 |             if (level === 0)
 831 |                 return x.cdr as Cell // ,a => (a)
 832 |             level--;
 833 |         } else if (x.car === quasiquoteSym) { // `a
 834 |             level++;
 835 |         }
 836 |         const h = qqExpand2(x.car, level);
 837 |         const t = qqExpand1(x.cdr, level); // !== null
 838 |         if (t.car === null && t.cdr === null) {
 839 |             return new Cell(h, null);
 840 |         } else if (h instanceof Cell) {
 841 |             if (h.car === listSym) {
 842 |                 const tcar = t.car;
 843 |                 if (tcar instanceof Cell) {
 844 |                     if (tcar.car === listSym) {
 845 |                         const hh = qqConcat(h, tcar.cdr);
 846 |                         return new Cell(hh, t.cdr);
 847 |                     }
 848 |                 }
 849 |                 if (h.cdr instanceof Cell) {
 850 |                     const hh = qqConsCons(h.cdr, tcar);
 851 |                     return new Cell(hh, t.cdr);
 852 |                 }
 853 |             }
 854 |         }
 855 |         return new Cell(h, t);
 856 |     } else {
 857 |         return new Cell(qqQuote(x), null);
 858 |     }
 859 | }
 860 | 
 861 | // (1 2), (3 4) => (1 2 3 4)
 862 | function qqConcat(x: Cell, y: unknown): unknown {
 863 |     if (x === null)
 864 |         return y;
 865 |     return new Cell(x.car, qqConcat(x.cdr as Cell, y));
 866 | }
 867 | 
 868 | // (1 2 3), "a" => (cons 1 (cons 2 (cons 3 "a")))
 869 | function qqConsCons(x: Cell, y: unknown): unknown {
 870 |     if (x === null)
 871 |         return y;
 872 |     return new Cell(consSym,
 873 |                     new Cell(x.car,
 874 |                              new Cell(qqConsCons(x.cdr as Cell, y),
 875 |                                       null)))
 876 | }
 877 | 
 878 | // Expand x.car (=y) of `x so that the result can be used as an arg of append.
 879 | // Example: ,a => (list a); ,@(foo 1 2) => (foo 1 2); b => (list 'b)
 880 | function qqExpand2(y: unknown, level: number): unknown {
 881 |     if (y instanceof Cell) {
 882 |         switch (y.car) {
 883 |         case unquoteSym:        // ,a
 884 |             if (level === 0)
 885 |                 return new Cell(listSym, y.cdr); // ,a => (list a)
 886 |             level--;
 887 |             break;
 888 |         case unquoteSplicingSym: // ,@a
 889 |             if (level === 0)
 890 |                 return (y.cdr as Cell).car; // ,@a => a
 891 |             level--;
 892 |             break;
 893 |         case quasiquoteSym:     // `a
 894 |             level++;
 895 |             break;
 896 |         }
 897 |     }
 898 |     return new Cell(listSym, new Cell(qqExpand0(y, level), null));
 899 | }
 900 | 
 901 | //----------------------------------------------------------------------
 902 | 
 903 | // A list of tokens, which works as a reader of Lisp expressions
 904 | class Reader {
 905 |     private token: unknown;
 906 |     private tokens: string[] = [];
 907 |     private lineNo = 1;
 908 | 
 909 |     // Split a text into a list of tokens and append it to this.tokens.
 910 |     // For "(a \n 1)" it appends ["(", "a", "\n", "1", ")", "\n"] to tokens.
 911 |     push(text: string): void {
 912 |         const tokenPat = /\s+|;.*$|("(\\.?|.)*?"|,@?|[^()'`~"; \t]+|.)/g;
 913 |         for (const line of text.split("\n")) {
 914 |             for (;;) {
 915 |                 const result = tokenPat.exec(line);
 916 |                 if (result === null)
 917 |                     break;
 918 |                 const s = result[1];
 919 |                 if (s !== undefined)
 920 |                     this.tokens.push(s)
 921 |             }
 922 |             this.tokens.push("\n");
 923 |         }
 924 |     }
 925 | 
 926 |     // Make this be a clone of the other.
 927 |     copyFrom(other: Reader): void {
 928 |         this.tokens = other.tokens.slice();
 929 |         this.lineNo = other.lineNo;
 930 |     }
 931 | 
 932 |     // Make this have no tokens.
 933 |     clear(): void {
 934 |         this.tokens.length = 0;
 935 |     }
 936 | 
 937 |     // Does this have no tokens?
 938 |     isEmpty(): boolean {
 939 |         return this.tokens.every((t: string) => t === "\n");
 940 |     }
 941 | 
 942 |     // Read a Lisp expression; throw EndOfFile if this.tokens run out.
 943 |     read(): unknown {
 944 |         try {
 945 |             this.readToken();
 946 |             return this.parseExpression();
 947 |         } catch (ex) {
 948 |             if (ex === EndOfFile)
 949 |                 throw EndOfFile;
 950 |             else if (ex instanceof FormatException)
 951 |                 throw new EvalException(
 952 |                     "syntax error", ex.message + " at " + this.lineNo, false);
 953 |             else
 954 |                 throw ex;
 955 |         }
 956 |     }
 957 | 
 958 |     private parseExpression(): unknown {
 959 |         switch (this.token) {
 960 |         case leftParenSym:      // (a b c)
 961 |             this.readToken();
 962 |             return this.parseListBody();
 963 |         case singleQuoteSym:    // 'a => (quote a)
 964 |             this.readToken();
 965 |             return new Cell(quoteSym,
 966 |                             new Cell(this.parseExpression(), null));
 967 |         case backQuoteSym:      // `a => (quasiquote a)
 968 |             this.readToken();
 969 |             return new Cell(quasiquoteSym,
 970 |                             new Cell(this.parseExpression(), null));
 971 |         case commaSym:          // ,a => (unquote a)
 972 |             this.readToken();
 973 |             return new Cell(unquoteSym,
 974 |                             new Cell(this.parseExpression(), null));
 975 |         case commaAtSym:        // ,@a => (unquote-splicing a)
 976 |             this.readToken();
 977 |             return new Cell(unquoteSplicingSym,
 978 |                             new Cell(this.parseExpression(), null));
 979 |         case dotSym:
 980 |         case rightParenSym:
 981 |             throw new FormatException('unexpected "' + this.token + '"');
 982 |         default:
 983 |             return this.token;
 984 |         }
 985 |     }
 986 | 
 987 |     private parseListBody(): unknown {
 988 |         if (this.token === rightParenSym) {
 989 |             return null;
 990 |         } else {
 991 |             const e1 = this.parseExpression();
 992 |             this.readToken();
 993 |             let e2: unknown;
 994 |             if (this.token == dotSym) { // (a . b)
 995 |                 this.readToken();
 996 |                 e2 = this.parseExpression();
 997 |                 this.readToken();
 998 |                 if (this.token !== rightParenSym)
 999 |                     throw new FormatException('")" expected: ' + this.token);
1000 |             } else {
1001 |                 e2 = this.parseListBody();
1002 |             }
1003 |             return new Cell(e1, e2);
1004 |         }
1005 |     }
1006 | 
1007 |     // Read the next token and set it to this.token.
1008 |     private readToken(): void {
1009 |         for (;;) {
1010 |             const t = this.tokens.shift();
1011 |             if (t === undefined) {
1012 |                 throw EndOfFile;
1013 |             } else if (t === "\n") {
1014 |                 this.lineNo += 1;
1015 |             } else if (t === "+" || t === "-") {
1016 |                 // N.B. BigInt("+") and BigInt("-") return 0n in Safari.
1017 |                 this.token = newSym(t);
1018 |                 return;
1019 |             } else {
1020 |                 if (t[0] === '"') {
1021 |                     let s = t;
1022 |                     const n = s.length - 1;
1023 |                     if (n < 1 || s[n] !== '"')
1024 |                         throw new FormatException("bad string: " + s);
1025 |                     s = s.substring(1, n);
1026 |                     s = s.replace(/\\./g, (m: string) => {
1027 |                         const val = Reader.escapes[m];
1028 |                         return (val === undefined) ? m : val;
1029 |                     });
1030 |                     this.token = s;
1031 |                     return;
1032 |                 }
1033 |                 const n = tryToParse(t);
1034 |                 if (n !== null)
1035 |                     this.token = n;
1036 |                 else if (t === "nil")
1037 |                     this.token = null;
1038 |                 else if (t === "t")
1039 |                     this.token = true;
1040 |                 else
1041 |                     this.token = newSym(t);
1042 |                 return;
1043 |             }
1044 |         }
1045 |     }
1046 | 
1047 |     private static escapes: {[key: string]: string} = {
1048 |         "\\\\": "\\",
1049 |         '\\"': '"',
1050 |         "\\n": "\n", "\\r": "\r", "\\f": "\f",
1051 |         "\\b": "\b", "\\t": "\t", "\\v": "\v"
1052 |     };
1053 | }
1054 | 
1055 | //----------------------------------------------------------------------
1056 | 
1057 | // Mapping from a quote symbol to its string representation
1058 | const quotes: {[key: string]: string} = {
1059 |     [quoteSym.name]: "'",
1060 |     [quasiquoteSym.name]: "`",
1061 |     [unquoteSym.name]: ",",
1062 |     [unquoteSplicingSym.name]: ",@"
1063 | };
1064 | 
1065 | // Make a string representation of Lisp expression
1066 | function str(x: unknown, quoteString = true,
1067 |              count?: number, printed?: Cell[]) : string
1068 | {
1069 |     if (x === null) {
1070 |         return "nil";
1071 |     } else if (x === true) {
1072 |         return "t";
1073 |     } else if (x instanceof Cell) {
1074 |         if (x.car instanceof Sym) {
1075 |             const q = quotes[x.car.name];
1076 |             if (q !== undefined && x.cdr instanceof Cell)
1077 |                 if (x.cdr.cdr == null)
1078 |                     return q + str(x.cdr.car, true, count, printed);
1079 |         }
1080 |         return "(" + strListBody(x, count, printed) + ")";
1081 |     } else if (typeof x === "string") {
1082 |         if (! quoteString)
1083 |             return x;
1084 |         const bf: string[] = ['"'];
1085 |         for (const ch of x) {
1086 |             switch (ch) {
1087 |             case "\b": bf.push("\\b"); break;
1088 |             case "\t": bf.push("\\t"); break;
1089 |             case "\n": bf.push("\\n"); break;
1090 |             case "\v": bf.push("\\v"); break;
1091 |             case "\f": bf.push("\\f"); break;
1092 |             case "\r": bf.push("\\r"); break;
1093 |             case "\"": bf.push("\\\""); break;
1094 |             case "\\": bf.push("\\\\"); break;
1095 |             default:
1096 |                 bf.push(ch); break;
1097 |             }
1098 |         }
1099 |         bf.push('"');
1100 |         return bf.join("");
1101 |     } else if (x instanceof Array) {
1102 |         const s = x.map((e) => str(e, true, count, printed)).join(", ");
1103 |         return "[" + s + "]";
1104 |     } else if (x instanceof Sym) {
1105 |         return (x.isInterned) ? x.name : "#:" + x;
1106 |     } else if (isNumeric(x)) {
1107 |         return convertToString(x);
1108 |     } else {
1109 |         return x + "";
1110 |     }
1111 | }
1112 | 
1113 | // Make a string representation of list, omitting its "(" and ")".
1114 | function strListBody(x: Cell, count?: number, printed?: Cell[]): string {
1115 |     if (printed === undefined)
1116 |         printed = [];
1117 |     if (count === undefined)
1118 |         count = 4;         // threshold of ellipsis for circular lists
1119 |     const s: string[] = [];
1120 |     let y: unknown;
1121 |     for (y = x; y instanceof Cell; y = y.cdr) {
1122 |         if (printed.indexOf(y) < 0) {
1123 |             printed.push(y);
1124 |             count = 4;
1125 |         } else {
1126 |             count--;
1127 |             if (count < 0) {
1128 |                 s.push("...");  // an ellipsis for a circular list
1129 |                 return s.join(" ");
1130 |             }
1131 |         }
1132 |         s.push(str(y.car, true, count, printed));
1133 |     }
1134 |     if (y !== null) {
1135 |         s.push(".");
1136 |         s.push(str(y, true, count, printed));
1137 |     }
1138 |     for (y = x; y instanceof Cell; y = y.cdr) {
1139 |         const i = printed.indexOf(y);
1140 |         if (i >= 0)
1141 |             printed.splice(i, 1)
1142 |     }
1143 |     return s.join(" ");
1144 | }
1145 | 
1146 | //----------------------------------------------------------------------
1147 | 
1148 | // Read-Eval-Print Loop
1149 | class REPL {
1150 |     private readonly stdInTokens: Reader = new Reader();
1151 | 
1152 |     // Read an expression from the standard-in asynchronously.
1153 |     private async readExpression(prompt1: string, prompt2: string):
1154 |     Promise {
1155 |         const oldTokens = new Reader();
1156 |         for (;;) {
1157 |             oldTokens.copyFrom(this.stdInTokens);
1158 |             try {
1159 |                 return this.stdInTokens.read();
1160 |             } catch (ex) {
1161 |                 if (ex === EndOfFile) {
1162 |                     write(oldTokens.isEmpty() ? prompt1 : prompt2);
1163 |                     const line = await readLine();
1164 |                     if (line === null)
1165 |                         return EndOfFile;
1166 |                     oldTokens.push(line);
1167 |                     this.stdInTokens.copyFrom(oldTokens);
1168 |                 } else {
1169 |                     this.stdInTokens.clear(); // Discard the erroneous tokens.
1170 |                     throw ex;
1171 |                 }
1172 |             }
1173 |         }
1174 |     }
1175 | 
1176 |     // Repeat Read-Eval-Print until End-Of-File asynchronously.
1177 |     async readEvalPrintLoop(interp: Interp): Promise {
1178 |         for (;;) {
1179 |             try {
1180 |                 const exp = await this.readExpression("> ", "  ");
1181 |                 if (exp === EndOfFile) {
1182 |                     write("Goodbye\n");
1183 |                     return;
1184 |                 }
1185 |                 const result = interp.eval(exp, null);
1186 |                 write(str(result) + "\n");
1187 |             } catch (ex) {
1188 |                 if (ex instanceof EvalException)
1189 |                     write(ex + "\n");
1190 |                 else
1191 |                     throw ex;
1192 |             }
1193 |         }
1194 |     }
1195 | }
1196 | 
1197 | // Evaluate a string as a list of Lisp exps; return the result of the last exp.
1198 | function run(interp: Interp, text: string): unknown {
1199 |     const tokens = new Reader();
1200 |     tokens.push(text);
1201 |     let result = undefined;
1202 |     while (! tokens.isEmpty()) {
1203 |         const exp = tokens.read();
1204 |         result = interp.eval(exp, null);
1205 |     }
1206 |     return result;
1207 | }
1208 | 
1209 | // Lisp initialization script
1210 | const prelude = `
1211 | (setq defmacro
1212 |       (macro (name args &rest body)
1213 |              \`(progn (setq ,name (macro ,args ,@body))
1214 |                      ',name)))
1215 | 
1216 | (defmacro defun (name args &rest body)
1217 |   \`(progn (setq ,name (lambda ,args ,@body))
1218 |           ',name))
1219 | 
1220 | (defun caar (x) (car (car x)))
1221 | (defun cadr (x) (car (cdr x)))
1222 | (defun cdar (x) (cdr (car x)))
1223 | (defun cddr (x) (cdr (cdr x)))
1224 | (defun caaar (x) (car (car (car x))))
1225 | (defun caadr (x) (car (car (cdr x))))
1226 | (defun cadar (x) (car (cdr (car x))))
1227 | (defun caddr (x) (car (cdr (cdr x))))
1228 | (defun cdaar (x) (cdr (car (car x))))
1229 | (defun cdadr (x) (cdr (car (cdr x))))
1230 | (defun cddar (x) (cdr (cdr (car x))))
1231 | (defun cdddr (x) (cdr (cdr (cdr x))))
1232 | (defun not (x) (eq x nil))
1233 | (defun consp (x) (not (atom x)))
1234 | (defun print (x) (prin1 x) (terpri) x)
1235 | (defun identity (x) x)
1236 | 
1237 | (setq
1238 |  = eql
1239 |  rem %
1240 |  null not
1241 |  setcar rplaca
1242 |  setcdr rplacd)
1243 | 
1244 | (defun > (x y) (< y x))
1245 | (defun >= (x y) (not (< x y)))
1246 | (defun <= (x y) (not (< y x)))
1247 | (defun /= (x y) (not (= x y)))
1248 | 
1249 | (defun equal (x y)
1250 |   (cond ((atom x) (eql x y))
1251 |         ((atom y) nil)
1252 |         ((equal (car x) (car y)) (equal (cdr x) (cdr y)))))
1253 | 
1254 | (defmacro if (test then &rest else)
1255 |   \`(cond (,test ,then)
1256 |          ,@(cond (else \`((t ,@else))))))
1257 | 
1258 | (defmacro when (test &rest body)
1259 |   \`(cond (,test ,@body)))
1260 | 
1261 | (defmacro let (args &rest body)
1262 |   ((lambda (vars vals)
1263 |      (defun vars (x)
1264 |        (cond (x (cons (if (atom (car x))
1265 |                           (car x)
1266 |                         (caar x))
1267 |                       (vars (cdr x))))))
1268 |      (defun vals (x)
1269 |        (cond (x (cons (if (atom (car x))
1270 |                           nil
1271 |                         (cadar x))
1272 |                       (vals (cdr x))))))
1273 |      \`((lambda ,(vars args) ,@body) ,@(vals args)))
1274 |    nil nil))
1275 | 
1276 | (defmacro letrec (args &rest body)      ; (letrec ((v e) ...) body...)
1277 |   (let (vars setqs)
1278 |     (defun vars (x)
1279 |       (cond (x (cons (caar x)
1280 |                      (vars (cdr x))))))
1281 |     (defun sets (x)
1282 |       (cond (x (cons \`(setq ,(caar x) ,(cadar x))
1283 |                      (sets (cdr x))))))
1284 |     \`(let ,(vars args) ,@(sets args) ,@body)))
1285 | 
1286 | (defun _append (x y)
1287 |   (if (null x)
1288 |       y
1289 |     (cons (car x) (_append (cdr x) y))))
1290 | (defmacro append (x &rest y)
1291 |   (if (null y)
1292 |       x
1293 |     \`(_append ,x (append ,@y))))
1294 | 
1295 | (defmacro and (x &rest y)
1296 |   (if (null y)
1297 |       x
1298 |     \`(cond (,x (and ,@y)))))
1299 | 
1300 | (defun mapcar (f x)
1301 |   (and x (cons (f (car x)) (mapcar f (cdr x)))))
1302 | 
1303 | (defmacro or (x &rest y)
1304 |   (if (null y)
1305 |       x
1306 |     \`(cond (,x)
1307 |            ((or ,@y)))))
1308 | 
1309 | (defun listp (x)
1310 |   (or (null x) (consp x)))    ; NB (listp (lambda (x) (+ x 1))) => nil
1311 | 
1312 | (defun memq (key x)
1313 |   (cond ((null x) nil)
1314 |         ((eq key (car x)) x)
1315 |         (t (memq key (cdr x)))))
1316 | 
1317 | (defun member (key x)
1318 |   (cond ((null x) nil)
1319 |         ((equal key (car x)) x)
1320 |         (t (member key (cdr x)))))
1321 | 
1322 | (defun assq (key alist)
1323 |   (cond (alist (let ((e (car alist)))
1324 |                  (if (and (consp e) (eq key (car e)))
1325 |                      e
1326 |                    (assq key (cdr alist)))))))
1327 | 
1328 | (defun assoc (key alist)
1329 |   (cond (alist (let ((e (car alist)))
1330 |                  (if (and (consp e) (equal key (car e)))
1331 |                      e
1332 |                    (assoc key (cdr alist)))))))
1333 | 
1334 | (defun _nreverse (x prev)
1335 |   (let ((next (cdr x)))
1336 |     (setcdr x prev)
1337 |     (if (null next)
1338 |         x
1339 |       (_nreverse next x))))
1340 | (defun nreverse (list)        ; (nreverse (quote (a b c d))) => (d c b a))
1341 |   (cond (list (_nreverse list nil))))
1342 | 
1343 | (defun last (list)
1344 |   (if (atom (cdr list))
1345 |       list
1346 |     (last (cdr list))))
1347 | 
1348 | (defun nconc (&rest lists)
1349 |   (if (null (cdr lists))
1350 |       (car lists)
1351 |     (if (null (car lists))
1352 |         (apply nconc (cdr lists))
1353 |       (setcdr (last (car lists))
1354 |               (apply nconc (cdr lists)))
1355 |       (car lists))))
1356 | 
1357 | (defmacro while (test &rest body)
1358 |   (let ((loop (gensym)))
1359 |     \`(letrec ((,loop (lambda () (cond (,test ,@body (,loop))))))
1360 |        (,loop))))
1361 | 
1362 | (defmacro dolist (spec &rest body) ; (dolist (name list [result]) body...)
1363 |   (let ((name (car spec))
1364 |         (list (gensym)))
1365 |     \`(let (,name
1366 |            (,list ,(cadr spec)))
1367 |        (while ,list
1368 |          (setq ,name (car ,list))
1369 |          ,@body
1370 |          (setq ,list (cdr ,list)))
1371 |        ,@(if (cddr spec)
1372 |              \`((setq ,name nil)
1373 |                ,(caddr spec))))))
1374 | 
1375 | (defmacro dotimes (spec &rest body) ; (dotimes (name count [result]) body...)
1376 |   (let ((name (car spec))
1377 |         (count (gensym)))
1378 |     \`(let ((,name 0)
1379 |            (,count ,(cadr spec)))
1380 |        (while (< ,name ,count)
1381 |          ,@body
1382 |          (setq ,name (+ ,name 1)))
1383 |        ,@(if (cddr spec)
1384 |              \`(,(caddr spec))))))
1385 | `;
1386 | 
1387 | //----------------------------------------------------------------------
1388 | // Main procedures for Deno and Node.js (not used on browsers)
1389 | 
1390 | declare function require(name: string): any; // A hack for Node.js
1391 | declare let process: any;                    // A hack for Node.js
1392 | 
1393 | if (typeof Deno !== 'undefined') {
1394 |     // For Deno
1395 |     const decoder = new TextDecoder(); // from utf-8 bytes
1396 |     const buf = new Uint8Array(8000);
1397 |     readLine = async function(): Promise {
1398 |         const n = await Deno.stdin.read(buf);
1399 |         if (n == null)
1400 |             return null;        // EOF
1401 |         return decoder.decode(buf.subarray(0, n));
1402 |     };
1403 | 
1404 |     const encoder = new TextEncoder(); // to utf-8 bytes
1405 |     write = (s: string) => {
1406 |         const bb = encoder.encode(s);
1407 |         for (let n = 0; n < bb.length; )
1408 |             n += Deno.stdout.writeSync(bb.subarray(n))
1409 |     };
1410 | 
1411 |     exit = Deno.exit;
1412 | 
1413 |     const main = async function() {
1414 |         const interp = new Interp();
1415 |         run(interp, prelude);
1416 | 
1417 |         let repl = undefined;
1418 |         const args = (Deno.args.length == 0) ? ['-'] : Deno.args;
1419 |         try {
1420 |             // Run the REPL on the first '-' argument.
1421 |             // Run each script file for other arguments.
1422 |             for (const fileName of args) {
1423 |                 if (fileName === '-') {
1424 |                     if (repl === undefined) {
1425 |                         repl = new REPL();
1426 |                         await repl.readEvalPrintLoop(interp);
1427 |                     }
1428 |                 } else {
1429 |                     const text = Deno.readTextFileSync(fileName);
1430 |                     run(interp, text);
1431 |                 }
1432 |             }
1433 |         } catch (ex) {
1434 |             console.log(ex);
1435 |             exit(1);
1436 |         }
1437 |     };
1438 |     main();
1439 | } else if (typeof process === 'object') {
1440 |     // For Node.js
1441 |     let readLine_atFirst = true;
1442 |     let readLine_resolve: ((line: string | null) => void) | null = null;
1443 |     readLine = () => new Promise((resolve, _reject) => {
1444 |         if (readLine_atFirst) {
1445 |             process.stdin.setEncoding('utf8');
1446 |             process.stdin.on('data', (line: string) => {
1447 |                 if (readLine_resolve !== null) {
1448 |                     readLine_resolve(line);
1449 |                     readLine_resolve = null;
1450 |                 }
1451 |             });
1452 |             process.stdin.on('end', () => {
1453 |                 if (readLine_resolve !== null) {
1454 |                     readLine_resolve(null);
1455 |                     readLine_resolve = null;
1456 |                 }
1457 |             });
1458 |             readLine_atFirst = false;
1459 |         }
1460 |         readLine_resolve = resolve;
1461 |     });
1462 | 
1463 |     write = (s: string) => process.stdout.write(s);
1464 |     exit = process.exit;
1465 | 
1466 |     const main = async function() {
1467 |         const interp = new Interp();
1468 |         run(interp, prelude);
1469 | 
1470 |         let repl = undefined;
1471 |         let fs = undefined;
1472 |         let argv = process.argv as string[];
1473 |         if (argv.length <= 2)
1474 |             argv = ['', '', '-'];
1475 |         try {
1476 |             for (let i = 2; i < argv.length; i++) {
1477 |                 const fileName = argv[i];
1478 |                 if (fileName == '-') {
1479 |                     if (repl === undefined) {
1480 |                         repl = new REPL();
1481 |                         await repl.readEvalPrintLoop(interp);
1482 |                     }
1483 |                 } else {
1484 |                     fs = fs || require('fs');
1485 |                     const text = fs.readFileSync(fileName, 'utf8');
1486 |                     run(interp, text);
1487 |                 }
1488 |             }
1489 |         } catch (ex) {
1490 |             console.log(ex);
1491 |             exit(1);
1492 |         }
1493 |     };
1494 |     main();
1495 | }
1496 | 


--------------------------------------------------------------------------------