1
1
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:
vvakame 2017-02-23 05:18:41 +09:00
parent f406f88b1e
commit 83aaf848e1
4 changed files with 98 additions and 3 deletions

View File

@ -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"

View File

@ -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
View 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);
}
}

View File

@ -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) {
}
}