Multiple implementations (JS, Wasm, C) of a Lisp.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
11 KiB

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();
}
}