import { Reader, ReaderMacros } from './reader.js'; import { DataTypes, Cons, Function, Nil, True, SpecialForm, Macro, Number, String, Symbol } from './datatypes.js'; import { Printer } from './printer.js'; import { Evaluator } from './evaluator.js'; import { Env } from './env.js'; export class Stream { onwrite = null; constructor(data = '') { this.data = Array.from(data); } readChar() { return this.data.shift(); } peekChar() { return this.data[0]; } pushData(data) { this.data.push(...Array.from(data)); if (data && this.onwrite) { this.onwrite(data); } } readData() { return this.data.splice(0).join(""); } } export class Repl { stdin = new Stream(); stdout = new Stream(); env = new Env( null, { 'STANDARD-INPUT-STREAM': this.stdin, 'STANDARD-OUTPUT-STREAM': this.stdout, 'NIL': new Nil(), 'T': new True(), '+': new Function(async operands => new Number(await Cons.reduce(operands, (rslt, i) => rslt + i.value, 0))), '-': new Function(async operands => new Number( operands.cdr.type === DataTypes.Nil ? -operands.car.value : await Cons.reduce(operands.cdr, (rslt, i) => rslt - i.value, operands.car.value))), '/': new Function(async operands => new Number(await operands.reduce((rslt, i) => rslt / i.value, 1))), '*': new Function(async operands => new Number(await operands.reduce((rslt, i) => rslt * i.value, 1))), '%': new Function(operands => new Number(operands.car.value % operands.cdr.car.value)), 'NOT': new Function(operands => operands.car.type === DataTypes.Nil ? new True() : new Nil()), '=': new Function(operands => this.listCompare(operands, (a, b) => a === b)), '<': new Function(operands => this.listCompare(operands, (a, b) => a < b)), '<=': new Function(operands => this.listCompare(operands, (a, b) => a <= b)), '>': new Function(operands => this.listCompare(operands, (a, b) => a > b)), '>=': new Function(operands => this.listCompare(operands, (a, b) => a >= b)), 'PRINT': new Function(operands => { Printer.print(operands.car, this.env); return operands.car; }), 'PRINC': new Function(operands => { Printer.princ(operands.car, this.env); return operands.car; }), 'CONS': new Function(operands => new Cons(operands.car, operands.cdr.car)), 'LIST': new Function(operands => operands), 'CAR': new Function(operands => { if (operands.car.type === DataTypes.Nil) { return new Nil(); } else if (operands.car.type === DataTypes.Cons) { return operands.car.car; } return new String('Can not car on not cons'); }), 'CDR': new Function(operands => { if (operands.car.type === DataTypes.Nil) { return new Nil(); } else if (operands.car.type === DataTypes.Cons) { return operands.car.cdr } return new String('Can not cdr on not cons'); }), 'LET': new SpecialForm(async (sexp, env, macros) => { env = await Cons.reduce( sexp.car, async (prev, o) => { let env = new Env(prev, {[o.car.value]: new Nil()}); Env.set(env, o.car.value, await Evaluator.eval(o.cdr.car, env, macros)); return env; }, env); return await Cons.reduce(sexp.cdr, async (p, o) => await Evaluator.eval(o, env, macros), null)}), 'SET': new SpecialForm(async (sexp, env, macros) => { const value = await Evaluator.eval(sexp.cdr.car, env, macros); Env.set(env, sexp.car.value, value); return value }), 'DEF': new SpecialForm(async (sexp, env, macros) => { Env.set(this.env, sexp.car.value, await Evaluator.eval(sexp.cdr.car, env, macros)); return sexp.car }), 'FN': new SpecialForm((sexp, env, macros) => { const outer = env; return new Function(async (operands) => { env = new Env(env, sexp.car.type === DataTypes.Cons ? await Cons.reduce( Cons.zip(sexp.car, operands), (prev, o) => ({...prev, [o.car.value]: o.cdr}), {}) : {}); return await Cons.reduce(sexp.cdr, async (_, o) => await Evaluator.eval(o, env, macros), null); }, sexp, outer); }), 'AND': new SpecialForm(async (sexp, env, macros) => { let last = new Nil(); const f = async o => { const cur = await Evaluator.eval(o.car, env, macros); if (cur.type === DataTypes.Nil) { return last; } last = cur; if (o.cdr.type === DataTypes.Nil) { return cur; } return await f(o.cdr); }; return await f(sexp); }), 'OR': new SpecialForm(async (sexp, env, macros) => { let o = sexp; const f = async o => { const cur = await Evaluator.eval(o.car, env, macros); if (!(cur === DataTypes.Nil)) { return cur; } if (o.cdr === DataTypes.Nil) { return new Nil(); } return await f(o.cdr); }; return await f(sexp); }), 'IF': new SpecialForm(async (sexp, env, macros) => { const cur = await Evaluator.eval(sexp.car, env, macros); return !(cur.type === DataTypes.Nil) ? await Evaluator.eval(sexp.cdr.car, env, macros) : await Evaluator.eval(sexp.cdr.cdr.car, env, macros); }), 'QUASIQUOTE': new SpecialForm((sexp, env, macros) => { return this.unquote(sexp.car, env, macros); }), 'QUOTE': new SpecialForm((sexp, env, macros) => { return sexp.car; }), 'EVAL': new SpecialForm(async (sexp, env, macros) => { return await Evaluator.eval( await Evaluator.eval(sexp.car, env, macros), env, macros); }), 'EXPAND': new SpecialForm((sexp, env, macros) => { return Evaluator.expand(sexp.car, macros); }), 'SAVE': new Function(() => { const converter = (key, val) => { if ( typeof val === 'function' || val && val.constructor === RegExp) { return String(val); } else { return val; } }; window.localStorage.setItem( 'image', JSON.stringify( repl_env.data, converter, 2) ); return new Nil(); }), 'LOAD': new Function(() => { const image = window.localStorage.getItem('image') const data = JSON.parse(image); const keys = Object.keys(data); for (let key of keys) { const atom = data[key]; repl_env.set(key, eval(atom)); } return new Nil(); }), 'GET-TIME': new Function(() => { return new Number(Date.now()); }), 'WAIT': new Function(operands => { return new Promise(resolve => setTimeout(_ => resolve(new Nil()), operands.car.value)) }), 'DO': new Function(operands => Cons.last(operands)), }); macros = new Env( null, { 'MACRO': new SpecialForm((sexp, env) => this.macro(sexp)), 'DEFN': new Macro( operands => new Cons(new Symbol('DEF'), new Cons(operands.car, new Cons(new Cons(new Symbol('FN'), operands.cdr))))), 'DEFMACRO': new SpecialForm((sexp, env) => { Env.set(this.macros, sexp.car.value, this.macro(sexp.cdr)); return new Cons(new Symbol("QUOTE"), new Cons(sexp.car)); }), } ); async unquote(sexp, env, macros) { if (sexp.type === DataTypes.Cons) { if (sexp.car.type === DataTypes.Symbol && sexp.car.value == 'UNQUOTE') { return await Evaluator.eval(sexp.cdr.car, env, macros); } else if (sexp.car.type === DataTypes.Cons && sexp.car.car.type === DataTypes.Symbol && sexp.car.car.value == 'UNQUOTE-SPLICING') { return await Evaluator.eval(sexp.car.cdr.car, env, macros); } return new Cons( await this.unquote(sexp.car, env, macros), await this.unquote(sexp.cdr, env, macros)); } return sexp; } listCompare(list, comparer) { let compare = (last, list) => { const result = comparer(last, list.car.value); if (!result || list.cdr.type === DataTypes.Nil) { return result; } return compare(list.car.value, list.cdr); } return compare(list.car.value, list.cdr) ? new True() : new Nil(); } // Return a new Promise always. macro(sexp) { return new Macro(async (operands) => { let args = sexp.car; let body = sexp.cdr; let env = new Env(this.env, args.type === DataTypes.Cons ? await Cons.reduce(Cons.zip(args, operands), (prev, o) => ({...prev, [o.car.value]: o.cdr}), {}) : {}); return await Cons.reduce( body, async (_, o) => await Evaluator.eval(o, env, this.macros), null); }); } // READER constructor(loadFromFile, loadTest, read) { this.stdin.onwrite = () => this.readEvalPrint(); Env.set(this.env, 'READ', new Function(async () => { const rep = this.stdin.onwrite; return new Promise(resolve => { read(); this.stdin.onwrite = (a) => { console.log(a); this.stdin.onwrite = rep; resolve(Reader.read(this.readerMacros, this.stdin)); }; }); })); Env.set(this.env, 'LOAD-FROM-FILE', new Function(async () => { this.stdin.pushData(await loadFromFile()); return new Nil(); })); Env.set(this.env, 'LOAD-TEST', new Function(async () => { this.stdin.pushData(await loadTest()); return new Nil(); })); this.readerMacros = new ReaderMacros(); // List read macro ReaderMacros.setMacroCharacter(this.readerMacros, '(', (c, stream) => { let cons; let list; let obj; ReaderMacros.setMacroCharacter(this.readerMacros, ')', (c, stream) => { ReaderMacros.popMacroCharacter(this.readerMacros, c); return null; }); while ((obj = Reader.read(this.readerMacros, stream))) { // list.push(obj); if (!cons) { cons = new Cons(obj); list = cons; } else { cons = cons.cdr = new Cons(obj); } } if (list && list.car) { return list; } return new Nil(); }); // String read macro ReaderMacros.setMacroCharacter(this.readerMacros, '"', (c, stream) => { let char = null, lastc = null, data = ''; while(char = stream.readChar()) { if (char === '"' && lastc !== '\\') { return new String(data); } data += char; lastc = char; } return null; }); // Quote read macro ReaderMacros.setMacroCharacter(this.readerMacros, '\'', (c, stream) => { return new Cons(new Symbol('QUOTE'), new Cons(Reader.read(this.readerMacros, stream))); }); // Quasiquote read macro ReaderMacros.setMacroCharacter(this.readerMacros, '`', (c, stream) => { return new Cons(new Symbol('QUASIQUOTE'), new Cons(Reader.read(this.readerMacros, stream))); }); // unquote read macro ReaderMacros.setMacroCharacter(this.readerMacros, ',', (c, stream) => { return new Cons(new Symbol(stream.peekChar() == '@' ? stream.readChar() && 'UNQUOTE-SPLICING' : 'UNQUOTE'), new Cons(Reader.read(this.readerMacros, stream))); }); } async readEvalPrint() { if (!this.stdin.peekChar()) { return; } const sexp = Reader.read(this.readerMacros, this.stdin); if (sexp) { let evaluated; try { evaluated = await Evaluator.eval( sexp, this.env, this.macros); } catch (e) { console.error(e); evaluated = new String(e); } Printer.print(new String(""), this.env); Printer.prin1(evaluated, this.env); Printer.print(new String(""), this.env); } await this.readEvalPrint(); } }