mirror of
https://github.com/kanaka/mal.git
synced 2024-09-20 10:07:45 +03:00
TypeScript: step 2
This commit is contained in:
parent
f406f88b1e
commit
83aaf848e1
@ -5,9 +5,10 @@
|
||||
"description": "Make a Lisp (mal) language implemented in TypeScript",
|
||||
"scripts": {
|
||||
"build": "tsfmt -r && tsc -p ./",
|
||||
"test": "npm run build && npm run test:step0 && npm run test:step1",
|
||||
"test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2",
|
||||
"test:step0": "cd .. && make 'test^ts^step0'",
|
||||
"test:step1": "cd .. && make 'test^ts^step1'"
|
||||
"test:step1": "cd .. && make 'test^ts^step1'",
|
||||
"test:step2": "cd .. && make 'test^ts^step2'"
|
||||
},
|
||||
"dependencies": {
|
||||
"ffi": "^2.2.0"
|
||||
|
@ -34,5 +34,7 @@ export function prStr(v: MalType, printReadably = true): string {
|
||||
return "nil";
|
||||
case "keyword":
|
||||
return `:${v.v.substr(1)}`;
|
||||
case "function":
|
||||
throw new Error(`invalid state`);
|
||||
}
|
||||
}
|
||||
|
82
ts/step2_eval.ts
Normal file
82
ts/step2_eval.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { readline } from "./node_readline";
|
||||
|
||||
import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction } from "./types";
|
||||
import { readStr } from "./reader";
|
||||
import { prStr } from "./printer";
|
||||
|
||||
function read(str: string): MalType {
|
||||
return readStr(str);
|
||||
}
|
||||
|
||||
interface MalEnvironment {
|
||||
[key: string]: MalFunction;
|
||||
}
|
||||
|
||||
function evalAST(ast: MalType, env: MalEnvironment): MalType {
|
||||
switch (ast.type) {
|
||||
case "symbol":
|
||||
const f = env[ast.v];
|
||||
if (!f) {
|
||||
throw new Error(`unknown symbol: ${ast.v}`);
|
||||
}
|
||||
return f;
|
||||
case "list":
|
||||
return new MalList(ast.list.map(ast => evalSexp(ast, env)));
|
||||
case "vector":
|
||||
return new MalVector(ast.list.map(ast => evalSexp(ast, env)));
|
||||
case "hash-map":
|
||||
const list: MalType[] = [];
|
||||
for (const [key, value] of ast.map) {
|
||||
list.push(key);
|
||||
list.push(evalSexp(value, env));
|
||||
}
|
||||
return new MalHashMap(list);
|
||||
default:
|
||||
return ast;
|
||||
}
|
||||
}
|
||||
|
||||
function evalSexp(ast: MalType, env: MalEnvironment): MalType {
|
||||
if (ast.type !== "list") {
|
||||
return evalAST(ast, env);
|
||||
}
|
||||
if (ast.list.length === 0) {
|
||||
return ast;
|
||||
}
|
||||
const result = evalAST(ast, env) as MalList;
|
||||
const [f, ...rest] = result.list;
|
||||
if (!MalFunction.instanceOf(f)) {
|
||||
throw new Error(`unexpected token: ${f.type}, expected: function`);
|
||||
}
|
||||
return f.func(...rest);
|
||||
}
|
||||
|
||||
function print(exp: MalType): string {
|
||||
return prStr(exp);
|
||||
}
|
||||
|
||||
const replEnv: MalEnvironment = {
|
||||
"+": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)),
|
||||
"-": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)),
|
||||
"*": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)),
|
||||
"/": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)),
|
||||
};
|
||||
function rep(str: string): string {
|
||||
return print(evalSexp(read(str), replEnv));
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const line = readline("user> ");
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (line === "") {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
console.log(rep(line));
|
||||
} catch (e) {
|
||||
const err: Error = e;
|
||||
console.error(err.message);
|
||||
}
|
||||
}
|
12
ts/types.ts
12
ts/types.ts
@ -1,4 +1,4 @@
|
||||
export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap;
|
||||
export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction;
|
||||
|
||||
export class MalList {
|
||||
type: "list" = "list";
|
||||
@ -76,3 +76,13 @@ export class MalHashMap {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MalFunction {
|
||||
static instanceOf(f: MalType): f is MalFunction {
|
||||
return f instanceof MalFunction;
|
||||
}
|
||||
|
||||
type: "function" = "function";
|
||||
constructor(public func: (...args: (MalType | undefined)[]) => MalType) {
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user