133 lines
4 KiB
TypeScript
133 lines
4 KiB
TypeScript
import { Expr, FieldName, ProductPattern, Tag, VariableName } from "../expr";
|
|
import { ThrownRuntimeError } from "./error";
|
|
|
|
export type Value =
|
|
| { tag: "string", value: string }
|
|
| { tag: "number", value: number }
|
|
| { tag: "tag", tag_name: Tag }
|
|
| { tag: "tagged", tag_name: Tag, value: Value }
|
|
| { tag: "tuple", values: Value[] }
|
|
| { tag: "record", fields: Map<FieldName, Value> }
|
|
| { tag: "closure", closure: Closure }
|
|
|
|
export type ValueTag =
|
|
| "string"
|
|
| "number"
|
|
| "tag"
|
|
| "tagged"
|
|
| "tuple"
|
|
| "record"
|
|
| "closure"
|
|
|
|
// Used as a Stack of frames. Basically a linked list.
|
|
export type Env =
|
|
| { tag: "nil" }
|
|
| { tag: "frame", frame: EnvFrame, parent: Env }
|
|
|
|
export type EnvFrame = Map<VariableName, Value>;
|
|
|
|
export type Closure = {
|
|
env: Env,
|
|
parameters: ProductPattern[],
|
|
body: Expr,
|
|
}
|
|
|
|
export namespace Value {
|
|
export const string = (value: string): Value => ({ tag: "string", value });
|
|
export const number = (value: number): Value => ({ tag: "number", value });
|
|
export const tag = (tag_name: Tag): Value => ({ tag: "tag", tag_name });
|
|
export const tagged = (tag_name: Tag, value: Value): Value => ({ tag: "tagged", tag_name, value });
|
|
export const tuple = (values: Value[]): Value => ({ tag: "tuple", values });
|
|
export const record = (fields: Map<FieldName, Value>): Value => ({ tag: "record", fields });
|
|
export const closure = (closure: Closure): Value => ({ tag: "closure", closure });
|
|
}
|
|
|
|
export namespace Env {
|
|
export function nil(): Env {
|
|
return { tag: "nil" };
|
|
}
|
|
|
|
export function push_frame(env: Env, frame: EnvFrame): Env {
|
|
return { tag: "frame", frame, parent: env };
|
|
}
|
|
|
|
// may throw `ThrownRuntimeError`
|
|
export function lookup(env: Env, var_name: VariableName): Value {
|
|
let cur = env;
|
|
while (cur.tag !== "nil") {
|
|
if (cur.frame.has(var_name)) {
|
|
return cur.frame.get(var_name)!;
|
|
}
|
|
cur = cur.parent;
|
|
}
|
|
throw ThrownRuntimeError.error({
|
|
tag: "VariableLookupFailure",
|
|
name: var_name,
|
|
});
|
|
}
|
|
|
|
export function nil_frame(): EnvFrame {
|
|
return new Map();
|
|
}
|
|
|
|
export function frame_insert_mut(frame: EnvFrame, var_name: VariableName, value: Value) {
|
|
frame.set(var_name, value);
|
|
}
|
|
}
|
|
|
|
export function equals(v1: Value, v2: Value): boolean {
|
|
if (v1 === v2) return true; // Reference equality optimization
|
|
if (v1.tag !== v2.tag) return false;
|
|
switch (v1.tag) {
|
|
case "number":
|
|
return v1.value === (v2 as Extract<Value, { tag: "number" }>).value;
|
|
case "string":
|
|
return v1.value === (v2 as Extract<Value, { tag: "string" }>).value;
|
|
case "tag":
|
|
return v1.tag_name === (v2 as Extract<Value, { tag: "tag" }>).tag_name;
|
|
case "tagged": {
|
|
const other = v2 as Extract<Value, { tag: "tagged" }>;
|
|
return v1.tag_name === other.tag_name && equals(v1.value, other.value);
|
|
}
|
|
case "tuple": {
|
|
const other = v2 as Extract<Value, { tag: "tuple" }>;
|
|
if (v1.values.length !== other.values.length) return false;
|
|
for (let i = 0; i < v1.values.length; i++) {
|
|
if (!equals(v1.values[i], other.values[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
case "record": {
|
|
const other = v2 as Extract<Value, { tag: "record" }>;
|
|
if (v1.fields.size !== other.fields.size) return false;
|
|
for (const [key, val1] of v1.fields) {
|
|
const val2 = other.fields.get(key);
|
|
if (val2 === undefined || !equals(val1, val2)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
case "closure":
|
|
// Philosophical/Mathematical barrier: throw error as requested
|
|
throw ThrownRuntimeError.error({
|
|
tag: "ClosureEqualityComparison",
|
|
value0: v1.closure,
|
|
value1: v2,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Canonical bools are:
|
|
// - True is `#T`
|
|
// - False is `#F`
|
|
// TODO: This is not a great design. Probably introducing completely new values would be better.
|
|
export function forceBool(value: Value): boolean {
|
|
if (value.tag === "tag") {
|
|
if (value.tag_name === "T") return true;
|
|
if (value.tag_name === "F") return false;
|
|
}
|
|
throw ThrownRuntimeError.error({
|
|
tag: "NotABoolean",
|
|
value
|
|
});
|
|
}
|
|
|