2017-02-24 10:30:25 +03:00
|
|
|
import { Env } from "./env";
|
|
|
|
|
2017-02-24 12:28:26 +03:00
|
|
|
export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom;
|
2017-02-22 21:05:01 +03:00
|
|
|
|
2017-02-24 07:21:11 +03:00
|
|
|
export function equals(a: MalType, b: MalType, strict?: boolean): boolean {
|
|
|
|
if (strict && a.constructor !== b.constructor) {
|
|
|
|
return false;
|
|
|
|
} else if (
|
|
|
|
(MalList.is(a) || MalVector.is(a))
|
|
|
|
&& (MalList.is(b) || MalVector.is(b))
|
|
|
|
) {
|
|
|
|
return listEquals(a.list, b.list);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MalNull.is(a) && MalNull.is(b)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
(MalList.is(a) && MalList.is(b))
|
|
|
|
|| (MalVector.is(a) && MalVector.is(b))
|
|
|
|
) {
|
|
|
|
return listEquals(a.list, b.list);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
(MalNumber.is(a) && MalNumber.is(b))
|
|
|
|
|| (MalString.is(a) && MalString.is(b))
|
|
|
|
|| (MalBoolean.is(a) && MalBoolean.is(b))
|
|
|
|
|| (MalSymbol.is(a) && MalSymbol.is(b))
|
|
|
|
|| (MalKeyword.is(a) && MalKeyword.is(b))
|
|
|
|
) {
|
|
|
|
return a.v === b.v;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
function listEquals(a: MalType[], b: MalType[]): boolean {
|
|
|
|
if (a.length !== b.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (let i = 0; i < a.length; i++) {
|
|
|
|
if (!equals(a[i], b[i], strict)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
export class MalList {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalList {
|
|
|
|
return f instanceof MalList;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "list" = "list";
|
|
|
|
|
|
|
|
constructor(public list: MalType[]) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalNumber {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalNumber {
|
|
|
|
return f instanceof MalNumber;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "number" = "number";
|
|
|
|
constructor(public v: number) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalString {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalString {
|
|
|
|
return f instanceof MalString;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "string" = "string";
|
|
|
|
constructor(public v: string) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalNull {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalNull {
|
|
|
|
return f instanceof MalNull;
|
|
|
|
}
|
|
|
|
|
|
|
|
static instance = new MalNull();
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "null" = "null";
|
2017-02-24 07:21:11 +03:00
|
|
|
|
|
|
|
private constructor() { }
|
2017-02-22 21:05:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
export class MalBoolean {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalBoolean {
|
|
|
|
return f instanceof MalBoolean;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "boolean" = "boolean";
|
|
|
|
constructor(public v: boolean) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalSymbol {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalSymbol {
|
|
|
|
return f instanceof MalSymbol;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
static map = new Map<symbol, MalSymbol>();
|
|
|
|
|
|
|
|
static get(name: string): MalSymbol {
|
|
|
|
const sym = Symbol.for(name);
|
|
|
|
let token = this.map.get(sym);
|
|
|
|
if (token) {
|
|
|
|
return token;
|
|
|
|
}
|
|
|
|
token = new MalSymbol(name);
|
|
|
|
this.map.set(sym, token);
|
|
|
|
return token;
|
|
|
|
}
|
|
|
|
|
|
|
|
type: "symbol" = "symbol";
|
|
|
|
|
|
|
|
private constructor(public v: string) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalKeyword {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalKeyword {
|
|
|
|
return f instanceof MalKeyword;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "keyword" = "keyword";
|
|
|
|
constructor(public v: string) {
|
|
|
|
this.v = String.fromCodePoint(0x29E) + this.v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalVector {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalVector {
|
|
|
|
return f instanceof MalVector;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "vector" = "vector";
|
|
|
|
constructor(public list: MalType[]) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class MalHashMap {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalHashMap {
|
|
|
|
return f instanceof MalHashMap;
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:05:01 +03:00
|
|
|
type: "hash-map" = "hash-map";
|
|
|
|
map = new Map<MalType, MalType>();
|
|
|
|
constructor(list: MalType[]) {
|
|
|
|
while (list.length !== 0) {
|
2017-02-24 01:41:49 +03:00
|
|
|
const key = list.shift()!;
|
2017-02-22 21:05:01 +03:00
|
|
|
const value = list.shift();
|
|
|
|
if (value == null) {
|
|
|
|
throw new Error("unexpected hash length");
|
|
|
|
}
|
|
|
|
this.map.set(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-22 23:18:41 +03:00
|
|
|
|
2017-02-24 10:30:25 +03:00
|
|
|
type MalF = (...args: (MalType | undefined)[]) => MalType;
|
|
|
|
|
2017-02-22 23:18:41 +03:00
|
|
|
export class MalFunction {
|
2017-02-24 07:21:11 +03:00
|
|
|
static is(f: MalType): f is MalFunction {
|
2017-02-22 23:18:41 +03:00
|
|
|
return f instanceof MalFunction;
|
|
|
|
}
|
|
|
|
|
2017-02-24 10:30:25 +03:00
|
|
|
static fromLisp(evalSexpr: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction {
|
|
|
|
const f = new MalFunction();
|
2017-02-24 12:28:26 +03:00
|
|
|
f.func = (...args) => evalSexpr(bodyAst, new Env(env, params, checkUndefined(args)));
|
2017-02-24 10:30:25 +03:00
|
|
|
f.env = env;
|
|
|
|
f.params = params;
|
|
|
|
f.ast = bodyAst;
|
|
|
|
|
|
|
|
return f;
|
|
|
|
|
2017-02-24 12:28:26 +03:00
|
|
|
function checkUndefined(args: (MalType | undefined)[]): MalType[] {
|
2017-02-24 10:30:25 +03:00
|
|
|
return args.map(arg => {
|
|
|
|
if (!arg) {
|
|
|
|
throw new Error(`undefined argument`);
|
|
|
|
}
|
|
|
|
return arg;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static fromBootstrap(func: MalF): MalFunction {
|
|
|
|
const f = new MalFunction();
|
|
|
|
f.func = func;
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
2017-02-22 23:18:41 +03:00
|
|
|
type: "function" = "function";
|
2017-02-24 10:30:25 +03:00
|
|
|
|
|
|
|
func: MalF;
|
|
|
|
ast: MalType;
|
|
|
|
env: Env;
|
|
|
|
params: MalSymbol[];
|
|
|
|
|
|
|
|
private constructor() { }
|
|
|
|
|
|
|
|
newEnv(args: MalType[]) {
|
|
|
|
return new Env(this.env, this.params, args);
|
2017-02-22 23:18:41 +03:00
|
|
|
}
|
|
|
|
}
|
2017-02-24 12:28:26 +03:00
|
|
|
|
|
|
|
export class MalAtom {
|
|
|
|
static is(f: MalType): f is MalAtom {
|
|
|
|
return f instanceof MalAtom;
|
|
|
|
}
|
|
|
|
|
|
|
|
type: "atom" = "atom";
|
|
|
|
|
|
|
|
constructor(public v: MalType) {
|
|
|
|
}
|
|
|
|
}
|