Implemented step 2.
This commit is contained in:
1 parent
97f047e10a
commit
df9e91ebd5
4 files changed
+149
-51
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import {assertEquals} from "https://deno.land/std@0.224.0/assert/assert_equals.ts";
|
||||
import {AST, FunctionToken, printAST, readAST} from "./reader-printer.ts";
|
||||
|
||||
type MalEnv = Map<string, FunctionToken>;
|
||||
|
||||
function functionToken(fn: (...args: any[]) => any): FunctionToken {
|
||||
return { type: "function", value: fn };
|
||||
}
|
||||
|
||||
function isBoolean(v: unknown): v is boolean {
|
||||
return v === true || v === false;
|
||||
}
|
||||
|
||||
function isNumber(v: unknown): v is number {
|
||||
return typeof v === "number" && isFinite(v);
|
||||
}
|
||||
|
||||
function isString(v: unknown): v is string {
|
||||
return typeof v === "string" || v instanceof String;
|
||||
}
|
||||
|
||||
class REPL {
|
||||
private readonly env: MalEnv;
|
||||
|
||||
constructor(env: MalEnv) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
static mathRepl(): REPL {
|
||||
const mathFunctions = new Map<string, FunctionToken>();
|
||||
mathFunctions.set("+", functionToken((a, b) => a + b));
|
||||
mathFunctions.set("-", functionToken((a, b) => a - b));
|
||||
mathFunctions.set("*", functionToken((a, b) => a * b));
|
||||
mathFunctions.set("/", functionToken((a, b) => a / b));
|
||||
|
||||
return new REPL(mathFunctions);
|
||||
}
|
||||
|
||||
read(source: string): AST {
|
||||
return readAST(source);
|
||||
}
|
||||
|
||||
cast(value: unknown): AST {
|
||||
if (value === true || value === false) {
|
||||
return { type: "boolean", value: value };
|
||||
}
|
||||
if (isBoolean(value)) {
|
||||
return { type: "boolean", value: value };
|
||||
}
|
||||
if (isString(value)) {
|
||||
return { type: "string", value: value };
|
||||
}
|
||||
if (isNumber(value)) {
|
||||
return { type: "number", value: value };
|
||||
}
|
||||
throw new Error(`Unknown return type from functon: ${value}`);
|
||||
}
|
||||
|
||||
eval_(ast: AST): AST {
|
||||
if (ast.type === "list") {
|
||||
const fn = this.eval_(ast.value[0]) as FunctionToken;
|
||||
const ret = fn.value(
|
||||
...ast.value.slice(1).map((n) => this.eval_(n).value),
|
||||
);
|
||||
return this.cast(ret);
|
||||
}
|
||||
if (ast.type === "sym") {
|
||||
const ret = this.env.get(ast.value);
|
||||
if (ret === undefined) {
|
||||
throw new Error(`Unknown reference ${ast.value}`);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
print(form: AST): string {
|
||||
return printAST(form);
|
||||
}
|
||||
|
||||
rep(source: string): string {
|
||||
return this.print(this.eval_(this.read(source)));
|
||||
}
|
||||
}
|
||||
|
||||
Deno.test("basic", function (): void {
|
||||
const repl = REPL.mathRepl();
|
||||
assertEquals(repl.rep("(+ 1 1)"), "2");
|
||||
assertEquals(repl.rep("(+ 2 (* 3 4))"), "14");
|
||||
});
|
||||
Reference in new issue
Block a user