mirror of
https://github.com/kanaka/mal.git
synced 2024-09-20 10:07:45 +03:00
TypeScript: step 3
This commit is contained in:
parent
76e06b96f4
commit
86fa880669
39
ts/env.ts
39
ts/env.ts
@ -0,0 +1,39 @@
|
||||
import { MalType, MalSymbol } from "./types";
|
||||
|
||||
export class Env {
|
||||
data: Map<MalSymbol, MalType>;
|
||||
|
||||
constructor(public outer?: Env) {
|
||||
this.data = new Map();
|
||||
}
|
||||
|
||||
set(key: MalSymbol, value: MalType): MalType {
|
||||
this.data.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
find(key: MalSymbol): Env | undefined {
|
||||
if (this.data.has(key)) {
|
||||
return this;
|
||||
}
|
||||
if (this.outer) {
|
||||
return this.outer.find(key);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
get(key: MalSymbol): MalType {
|
||||
const env = this.find(key);
|
||||
if (!env) {
|
||||
throw new Error(`${key.v} not found`);
|
||||
}
|
||||
|
||||
const v = env.data.get(key);
|
||||
if (!v) {
|
||||
throw new Error(`${key.v} is not exists`);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
@ -5,10 +5,11 @@
|
||||
"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 && npm run test:step2",
|
||||
"test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3",
|
||||
"test:step0": "cd .. && make 'test^ts^step0'",
|
||||
"test:step1": "cd .. && make 'test^ts^step1'",
|
||||
"test:step2": "cd .. && make 'test^ts^step2'"
|
||||
"test:step2": "cd .. && make 'test^ts^step2'",
|
||||
"test:step3": "cd .. && make 'test^ts^step3'"
|
||||
},
|
||||
"dependencies": {
|
||||
"ffi": "^2.2.0"
|
||||
|
113
ts/step3_env.ts
Normal file
113
ts/step3_env.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { readline } from "./node_readline";
|
||||
|
||||
import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types";
|
||||
import { Env } from "./env";
|
||||
import { readStr } from "./reader";
|
||||
import { prStr } from "./printer";
|
||||
|
||||
function read(str: string): MalType {
|
||||
return readStr(str);
|
||||
}
|
||||
|
||||
function evalAST(ast: MalType, env: Env): MalType {
|
||||
switch (ast.type) {
|
||||
case "symbol":
|
||||
const f = env.get(ast);
|
||||
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: Env): MalType {
|
||||
if (ast.type !== "list") {
|
||||
return evalAST(ast, env);
|
||||
}
|
||||
if (ast.list.length === 0) {
|
||||
return ast;
|
||||
}
|
||||
const first = ast.list[0];
|
||||
switch (first.type) {
|
||||
case "symbol":
|
||||
switch (first.v) {
|
||||
case "def!": {
|
||||
const [, key, value] = ast.list;
|
||||
if (key instanceof MalSymbol === false) {
|
||||
throw new Error(`unexpected toke type: ${key.type}, expected: symbol`);
|
||||
}
|
||||
if (!value) {
|
||||
throw new Error(`unexpected syntax`);
|
||||
}
|
||||
return env.set(key as MalSymbol, evalSexp(value, env))
|
||||
}
|
||||
case "let*": {
|
||||
let letEnv = new Env(env);
|
||||
const pairs = ast.list[1];
|
||||
if (pairs instanceof MalList === false && pairs instanceof MalVector === false) {
|
||||
throw new Error(`unexpected toke type: ${pairs.type}, expected: list or vector`);
|
||||
}
|
||||
const list = (pairs as (MalList | MalVector)).list;
|
||||
for (let i = 0; i < list.length; i += 2) {
|
||||
const key = list[i];
|
||||
const value = list[i + 1];
|
||||
if (!key || !value) {
|
||||
throw new Error(`unexpected syntax`);
|
||||
}
|
||||
|
||||
letEnv.set(key as MalSymbol, evalSexp(value, letEnv));
|
||||
}
|
||||
return evalSexp(ast.list[2], letEnv);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = new Env();
|
||||
replEnv.set(MalSymbol.get("+"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)));
|
||||
replEnv.set(MalSymbol.get("-"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)));
|
||||
replEnv.set(MalSymbol.get("*"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)));
|
||||
replEnv.set(MalSymbol.get("/"), 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user