Skip to content

Commit

Permalink
add evaluated type system (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcho21 authored Dec 22, 2023
1 parent 8642df4 commit e89852e
Show file tree
Hide file tree
Showing 8 changed files with 512 additions and 146 deletions.
31 changes: 17 additions & 14 deletions src/evaluator/environment/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
import type { Evaluated } from "../evaluated";
import Environment from "./";

describe("set()", () => {
it("set name and value", () => {
const env = new Environment();
const varName = "foo";
const varValue = {} as Evaluated;

expect(() => env.set("foo", 42)).not.toThrow();
expect(() => env.set(varName, varValue)).not.toThrow();
});
});

describe("get()", () => {
it("get value after setting the value", () => {
const env = new Environment();
const varName = "foo";
const varValue = {} as Evaluated;

env.set("foo", 42);
const value = env.get("foo");
env.set(varName, varValue);

expect(value).toBe(42);
expect(env.get(varName)).toBe(varValue);
});

it("get null if not found", () => {
const env = new Environment();
const varNameNotSet = "foo";

const value = env.get("foo");

expect(value).toBe(null);
expect(env.get(varNameNotSet)).toBe(null);
});
});

describe("linked environment", () => {
it("set super environment and get via sub environment", () => {
const varNameInSuper = "foo";
const varValueInSuper = {} as Evaluated;

const superEnv = new Environment();
superEnv.set("foo", 42);
superEnv.set(varNameInSuper, varValueInSuper);
const subEnv = new Environment(superEnv);

const value = subEnv.get("foo");

expect(value).toBe(42);
expect(subEnv.get(varNameInSuper)).toBe(varValueInSuper);
});

it("get null if not found even in super environment", () => {
const superEnv = new Environment();
const subEnv = new Environment(superEnv);

const value = subEnv.get("foo");

expect(value).toBe(null);
const varNameSetNowhere = "foo";
expect(subEnv.get(varNameSetNowhere)).toBe(null);
});
});
12 changes: 6 additions & 6 deletions src/evaluator/environment/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Evaluated } from "../evaluated";

export interface EnvironmentType {
get: (name: string) => unknown;
set: (name: string, value: any) => unknown;
get: (name: string) => Evaluated | null;
set: (name: string, value: Evaluated) => void;
}

export default class Environment implements EnvironmentType {
Expand All @@ -12,7 +14,7 @@ export default class Environment implements EnvironmentType {
this.table = new Map<string, any>;
}

get(name: string): unknown {
get(name: string): Evaluated | null {
// return if found in current environment
const fetched = this.table.get(name);
if (fetched !== undefined) {
Expand All @@ -26,9 +28,7 @@ export default class Environment implements EnvironmentType {
return this.superEnvironment.get(name);
}

set(name: string, value: any): unknown {
set(name: string, value: Evaluated): void {
this.table.set(name, value);

return value;
}
}
82 changes: 82 additions & 0 deletions src/evaluator/evaluated/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
makeEvaluatedNumber,
makeEvaluatedString,
makeEvaluatedBoolean,
makeEvaluatedFunction,
makeEvaluatedEmpty,
} from "./";
import type {
FunctionExpression
} from "../../parser/syntax-tree";
import type Environment from "../environment";

describe("makeEvaluatedNumber()", () => {
it("make number value", () => {
const evaluated = makeEvaluatedNumber(42);

expect(evaluated.type).toBe("number");
expect(evaluated.value).toBe(42);
expect(evaluated.representation).toBe("42");
});
});

describe("makeEvaluatedString()", () => {
it("make nonempty string value", () => {
const evaluated = makeEvaluatedString("foo bar");

expect(evaluated.type).toBe("string");
expect(evaluated.value).toBe("foo bar");
expect(evaluated.representation).toBe("'foo bar'");
});

it("make empty string value", () => {
const evaluated = makeEvaluatedString("");

expect(evaluated.type).toBe("string");
expect(evaluated.value).toBe("");
expect(evaluated.representation).toBe("''");
});
});

describe("makeEvaluatedBoolean()", () => {
it("make true boolean value", () => {
const evaluated = makeEvaluatedBoolean(true);

expect(evaluated.type).toBe("boolean");
expect(evaluated.value).toBe(true);
expect(evaluated.representation).toBe("참");
});

it("make false boolean value", () => {
const evaluated = makeEvaluatedBoolean(false);

expect(evaluated.type).toBe("boolean");
expect(evaluated.value).toBe(false);
expect(evaluated.representation).toBe("거짓");
});
});

describe("makeEvaluatedFunction()", () => {
it("make function value", () => {
const parametersMock = [] as FunctionExpression["parameter"];
const bodyMock = {} as FunctionExpression["body"];
const environmentMock = {} as Environment;

const evaluated = makeEvaluatedFunction(parametersMock, bodyMock, environmentMock);

expect(evaluated.type).toBe("function");
expect(evaluated.parameters).toBe(parametersMock);
expect(evaluated.body).toBe(bodyMock);
expect(evaluated.environment).toBe(environmentMock);
expect(evaluated.representation).toBe("(함수)");
});
});

describe("makeEvaluatedEmpty()", () => {
it("make empty value", () => {
const evaluated = makeEvaluatedEmpty();

expect(evaluated.type).toBe("empty");
expect(evaluated.representation).toBe("(비어있음)");
});
});
95 changes: 95 additions & 0 deletions src/evaluator/evaluated/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type {
FunctionExpression,
} from "../../parser/syntax-tree";
import type Environment from "../environment";

interface EvaluatedBase {
readonly type: string;
readonly representation: string;
}

export interface EvaluatedNumber extends EvaluatedBase {
readonly type: "number";
readonly value: number;
}

export interface EvaluatedString extends EvaluatedBase {
readonly type: "string";
readonly value: string;
}

export interface EvaluatedBoolean extends EvaluatedBase {
readonly type: "boolean";
readonly value: boolean;
}

export interface EvaluatedFunction extends EvaluatedBase {
readonly type: "function";
readonly parameters: FunctionExpression["parameter"];
readonly body: FunctionExpression["body"];
readonly environment: Environment;
}

// 'empty' represents the result of running statement (e.g., branching) in REPL
export interface EvaluatedEmpty extends EvaluatedBase {
readonly type: "empty";
}

export type Evaluated =
EvaluatedPrimitive |
EvaluatedFunction |
EvaluatedEmpty;
export type EvaluatedPrimitive =
EvaluatedNumber |
EvaluatedString |
EvaluatedBoolean;

export type MakeEvaluatedNumber = (value: number) => EvaluatedNumber;
export const makeEvaluatedNumber: MakeEvaluatedNumber = value => ({
type: "number",
value,
get representation() {
return `${value}`;
},
});

export type MakeEvaluatedString = (value: string) => EvaluatedString;
export const makeEvaluatedString: MakeEvaluatedString = value => ({
type: "string",
value,
get representation() {
return `'${value}'`;
},
});

export type MakeEvaluatedBoolean = (value: boolean) => EvaluatedBoolean;
export const makeEvaluatedBoolean: MakeEvaluatedBoolean = value => ({
type: "boolean",
value,
get representation() {
return value ? "참" : "거짓";
},
});

export type MakeEvaluatedFunction = (
parameters: FunctionExpression["parameter"],
body: FunctionExpression["body"],
environment: Environment
) => EvaluatedFunction;
export const makeEvaluatedFunction: MakeEvaluatedFunction = (parameters, body, environment) => ({
type: "function",
parameters,
body,
environment,
get representation() {
return "(함수)";
},
});

export type MakeEvaluatedEmpty = () => EvaluatedEmpty;
export const makeEvaluatedEmpty: MakeEvaluatedEmpty = () => ({
type: "empty",
get representation() {
return "(비어있음)";
},
});
Loading

0 comments on commit e89852e

Please sign in to comment.