Reorg
This commit is contained in:
parent
d45207342c
commit
cd84d74ec7
12 changed files with 581 additions and 569 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// AI GENERATED
|
||||
import { Expr, Pattern, ProductPattern, Literal, FieldAssignment, FieldPattern } from '../value';
|
||||
import { Expr, FieldAssignment, FieldPattern, Literal, Pattern, ProductPattern } from "../expr";
|
||||
|
||||
// AI GENERATED
|
||||
export function exprToString(expr: Expr): string {
|
||||
switch (expr.tag) {
|
||||
case "literal":
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { parse, ParseError } from '../parser/parser';
|
|||
import { SourceText, renderSpan, sourceText } from '../parser/source_text';
|
||||
import { exprToString } from '../debug/expr_show';
|
||||
import { valueToString } from '../debug/value_show';
|
||||
import { eval_start, Program } from '../value';
|
||||
import { Program } from '../program';
|
||||
import { eval_start } from '../eval/evaluator';
|
||||
|
||||
// ANSI Color Codes
|
||||
const C = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// AI GENERATED
|
||||
import { Value, Env, Closure, EnvFrame } from '../value';
|
||||
import { Closure, Value, Env, EnvFrame } from '../eval/value';
|
||||
import { exprToString, productPatternToString } from './expr_show';
|
||||
|
||||
export function valueToString(val: Value): string {
|
||||
|
|
|
|||
37
src/lang/eval/error.ts
Normal file
37
src/lang/eval/error.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { FunctionName, Pattern, VariableName } from "../expr"
|
||||
import { Closure, Value, ValueTag } from "./value"
|
||||
|
||||
export type RuntimeError =
|
||||
| { tag: "FunctionLookupFailure", name: FunctionName }
|
||||
| { tag: "FunctionCallArityMismatch", name: FunctionName, expected: number, actual: number }
|
||||
| { tag: "ClosureApplicationArityMismatch", closure: Closure, expected: number, actual: number }
|
||||
| { tag: "VariableLookupFailure", name: VariableName }
|
||||
// | { tag: "CellLookupFailure", name: CellName }
|
||||
| { tag: "UnableToFindMatchingPattern", value: Value }
|
||||
| { tag: "TypeMismatch", expected: ValueTag, received: Value }
|
||||
| { tag: "DuplicateVariableNamesInPattern", pattern: Pattern, duplicates: VariableName[] }
|
||||
// | { tag: "DuplicateVariableNamesInProductPattern", pattern: ProductPattern, duplicates: VariableName[] }
|
||||
|
||||
export type Result<T> =
|
||||
| { tag: "ok", value: T }
|
||||
| { tag: "error", error: RuntimeError }
|
||||
|
||||
export namespace Result {
|
||||
export function ok<T>(value: T): Result<T> { return { tag: "ok", value } }
|
||||
export function error<T>(error: RuntimeError): Result<T> { return { tag: "error", error } }
|
||||
}
|
||||
|
||||
// This is an internal type - use it in all internal evaluation functions.
|
||||
export type ThrownRuntimeError = {
|
||||
kind: "RuntimeError",
|
||||
error: RuntimeError
|
||||
}
|
||||
|
||||
export namespace ThrownRuntimeError {
|
||||
// use as follows
|
||||
// `throw ThrownRuntimeError.error(e)`
|
||||
export function error(error: RuntimeError): ThrownRuntimeError {
|
||||
return { kind: "RuntimeError", error };
|
||||
}
|
||||
}
|
||||
|
||||
174
src/lang/eval/evaluator.ts
Normal file
174
src/lang/eval/evaluator.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { Expr, ExprBinding, FieldName, ProductPattern } from "../expr"
|
||||
import { Closure, Env, EnvFrame, Value } from "./value"
|
||||
import { FunctionDefinition, Program, UserFunctionDefinition } from "../program";
|
||||
import { Result, RuntimeError, ThrownRuntimeError } from "./error";
|
||||
import { match_pattern, match_product_pattern, match_product_pattern_mut } from "./pattern_match";
|
||||
|
||||
export function eval_start(program: Program, e: Expr): Result<Value> {
|
||||
try {
|
||||
return Result.ok(eval_expr(program, Env.nil(), e));
|
||||
} catch (err) {
|
||||
if (typeof err === "object" && (err as any).kind === "RuntimeError") {
|
||||
return Result.error((err as ThrownRuntimeError).error);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function eval_expr(program: Program, env: Env, e: Expr): Value {
|
||||
switch (e.tag) {
|
||||
case "literal":
|
||||
switch (e.literal.tag) {
|
||||
case "number":
|
||||
return Value.number(e.literal.value);
|
||||
case "string":
|
||||
return Value.string(e.literal.value);
|
||||
}
|
||||
case "tag":
|
||||
return Value.tag(e.tag_name);
|
||||
case "tagged":
|
||||
return Value.tagged(e.tag_name, eval_expr(program, env, e.expr));
|
||||
case "tuple":
|
||||
return Value.tuple(eval_sequence(program, env, e.exprs));
|
||||
case "record":
|
||||
const fields = new Map<FieldName, Value>();
|
||||
for (const field of e.fields) {
|
||||
const value = eval_expr(program, env, field.expr);
|
||||
fields.set(field.name, value);
|
||||
}
|
||||
return Value.record(fields);
|
||||
case "lambda":
|
||||
return Value.closure({
|
||||
env,
|
||||
parameters: e.parameters,
|
||||
body: e.body,
|
||||
});
|
||||
case "var_use":
|
||||
return Env.lookup(env, e.name);
|
||||
case "call":
|
||||
const fn = Program.lookup_function(program, e.name);
|
||||
const fn_args = eval_sequence(program, env, e.args);
|
||||
return call_function(program, fn, fn_args);
|
||||
case "apply":
|
||||
const closure = force_closure(eval_expr(program, env, e.callee));
|
||||
const closure_args = eval_sequence(program, env, e.args);
|
||||
return apply_closure(program, closure, closure_args);
|
||||
case "let":
|
||||
const new_env = eval_bindings(program, env, e.bindings);
|
||||
return eval_expr(program, new_env, e.body);
|
||||
case "match":
|
||||
const match_val = eval_expr(program, env, e.arg);
|
||||
for (const branch of e.branches) {
|
||||
const res = match_pattern(branch.pattern, match_val);
|
||||
if (res.tag === "match") {
|
||||
return eval_expr(program, Env.push_frame(env, res.frame), branch.body);
|
||||
}
|
||||
}
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "UnableToFindMatchingPattern",
|
||||
value: match_val,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function eval_bindings(program: Program, env: Env, bindings: ExprBinding[]): Env {
|
||||
// note that `let { x = 123, y = x + 1 ... } is allowed. Ofcourse later bindings can't be referenced by earlier bindings (i.e. no recursion).
|
||||
let cur_env = env;
|
||||
for (const { pattern: var_name, expr } of bindings) {
|
||||
const value = eval_expr(program, cur_env, expr);
|
||||
const res = match_product_pattern(var_name, value);
|
||||
if (res.tag === "failure") {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "UnableToFindMatchingPattern",
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
cur_env = Env.push_frame(cur_env, res.frame);
|
||||
}
|
||||
}
|
||||
return cur_env;
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function eval_sequence(program: Program, env: Env, args: Expr[]): Value[] {
|
||||
return args.map(arg => eval_expr(program, env, arg));
|
||||
}
|
||||
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function call_function(program: Program, fn_def: FunctionDefinition, args: Value[]): Value {
|
||||
switch (fn_def.tag) {
|
||||
case "user":
|
||||
return call_user_function(program, fn_def.def, args);
|
||||
case "primitive":
|
||||
return fn_def.def.implementation(args);
|
||||
}
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function call_user_function(program: Program, fn_def: UserFunctionDefinition, args: Value[]): Value {
|
||||
const frame = bind_arguments_to_parameters(
|
||||
fn_def.parameters,
|
||||
args,
|
||||
(expected, actual) => ({ tag: "FunctionCallArityMismatch", name: fn_def.name, expected, actual })
|
||||
);
|
||||
|
||||
return eval_expr(program, Env.push_frame(Env.nil(), frame), fn_def.body);
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function apply_closure(program: Program, closure: Closure, args: Value[]): Value {
|
||||
const frame = bind_arguments_to_parameters(
|
||||
closure.parameters,
|
||||
args,
|
||||
(expected, actual) => ({ tag: "ClosureApplicationArityMismatch", closure, expected, actual })
|
||||
);
|
||||
|
||||
return eval_expr(program, Env.push_frame(closure.env, frame), closure.body);
|
||||
}
|
||||
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function force_closure(value: Value): Closure {
|
||||
if (value.tag !== "closure") {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "TypeMismatch",
|
||||
expected: "closure",
|
||||
received: value,
|
||||
});
|
||||
}
|
||||
return value.closure;
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function bind_arguments_to_parameters(
|
||||
patterns: ProductPattern[],
|
||||
values: Value[],
|
||||
onArityMismatchError: (expected: number, actual: number) => RuntimeError
|
||||
): EnvFrame {
|
||||
const expected = patterns.length;
|
||||
const actual = values.length;
|
||||
|
||||
if (expected !== actual) {
|
||||
throw ThrownRuntimeError.error(onArityMismatchError(expected, actual));
|
||||
}
|
||||
|
||||
const frame: EnvFrame = new Map();
|
||||
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
const pattern = patterns[i];
|
||||
const value = values[i];
|
||||
const res = match_product_pattern_mut(frame, pattern, value);
|
||||
if (res.tag === "failure") {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "UnableToFindMatchingPattern",
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
67
src/lang/eval/pattern_match.ts
Normal file
67
src/lang/eval/pattern_match.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { Pattern, ProductPattern, VariableName } from "../expr";
|
||||
import { EnvFrame, Value } from "./value";
|
||||
|
||||
// A pattern match will result either in a succesfull match with a new EnvFrame
|
||||
type PatternMatchingResult =
|
||||
| { tag: "match", frame: EnvFrame }
|
||||
| { tag: "failure", pattern: Pattern, value: Value }
|
||||
|
||||
export function match_pattern(pattern: Pattern, value: Value): PatternMatchingResult {
|
||||
const frame = new Map<VariableName, Value>();
|
||||
return match_pattern_mut(frame, pattern, value);
|
||||
}
|
||||
|
||||
export function match_pattern_mut(frame: EnvFrame, pattern: Pattern, value: Value): PatternMatchingResult {
|
||||
switch (pattern.tag) {
|
||||
case "tag":
|
||||
if (value.tag === "tag" && value.tag_name === pattern.tag_name) {
|
||||
return { tag: "match", frame }
|
||||
} else {
|
||||
return { tag: "failure", pattern, value }
|
||||
}
|
||||
case "tagged":
|
||||
if (value.tag === "tagged" && value.tag_name === pattern.tag_name) {
|
||||
return match_pattern_mut(frame, pattern.pattern, value.value);
|
||||
} else {
|
||||
return { tag: "failure", pattern, value };
|
||||
}
|
||||
default:
|
||||
return match_product_pattern_mut(frame, pattern, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function match_product_pattern(pattern: ProductPattern, value: Value): PatternMatchingResult {
|
||||
const frame = new Map<VariableName, Value>();
|
||||
return match_product_pattern_mut(frame, pattern, value);
|
||||
}
|
||||
|
||||
export function match_product_pattern_mut(frame: EnvFrame, pattern: ProductPattern, value: Value): PatternMatchingResult {
|
||||
switch (pattern.tag) {
|
||||
case "any":
|
||||
frame.set(pattern.name, value);
|
||||
return { tag: "match", frame };
|
||||
case "tuple":
|
||||
if (value.tag !== "tuple" || pattern.patterns.length !== value.values.length) return { tag: "failure", pattern, value };
|
||||
for (let i = 0; i < pattern.patterns.length; i++) {
|
||||
const res = match_product_pattern_mut(frame, pattern.patterns[i], value.values[i]);
|
||||
if (res.tag === "failure") return res;
|
||||
}
|
||||
return { tag: "match", frame };
|
||||
case "record":
|
||||
if (value.tag !== "record") return { tag: "failure", pattern, value };
|
||||
|
||||
for (const { fieldName, pattern: p } of pattern.fields) {
|
||||
const field_value = value.fields.get(fieldName);
|
||||
if (field_value === undefined) {
|
||||
return { tag: "failure", pattern, value };
|
||||
} else {
|
||||
const res = match_product_pattern_mut(frame, p, field_value);
|
||||
if (res.tag === "failure") {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { tag: "match", frame };
|
||||
}
|
||||
}
|
||||
|
||||
73
src/lang/eval/value.ts
Normal file
73
src/lang/eval/value.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
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 frame_insert_mut(frame: EnvFrame, var_name: VariableName, value: Value) {
|
||||
frame.set(var_name, value);
|
||||
}
|
||||
}
|
||||
|
||||
86
src/lang/expr.ts
Normal file
86
src/lang/expr.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// === Identifiers ===
|
||||
export type VariableName = string
|
||||
export type FunctionName = string
|
||||
// type CellName = string
|
||||
export type Tag = string
|
||||
export type FieldName = string
|
||||
|
||||
// === Expr ===
|
||||
export type Expr =
|
||||
| { tag: "literal", literal: Literal }
|
||||
| { tag: "var_use", name: VariableName }
|
||||
// | { tag: "cell_ref", name: CellName }
|
||||
| { tag: "call", name: FunctionName, args: Expr[] }
|
||||
| { tag: "let", bindings: ExprBinding[], body: Expr }
|
||||
| { tag: "tag", tag_name: Tag }
|
||||
| { tag: "tagged", tag_name: Tag, expr: Expr }
|
||||
| { tag: "tuple", exprs: Expr[] }
|
||||
| { tag: "record", fields: FieldAssignment[] }
|
||||
| { tag: "match", arg: Expr, branches: MatchBranch[] }
|
||||
| { tag: "lambda", parameters: ProductPattern[], body: Expr }
|
||||
| { tag: "apply", callee: Expr, args: Expr[] }
|
||||
|
||||
export type Literal =
|
||||
| { tag: "number", value: number }
|
||||
| { tag: "string", value: string }
|
||||
|
||||
export type ExprBinding = {
|
||||
pattern: ProductPattern,
|
||||
expr: Expr,
|
||||
}
|
||||
|
||||
export type MatchBranch = {
|
||||
pattern: Pattern,
|
||||
body: Expr,
|
||||
}
|
||||
|
||||
export type FieldAssignment = { name: FieldName, expr: Expr };
|
||||
|
||||
// === Pattern ===
|
||||
export type ProductPattern =
|
||||
| { tag: "any", name: VariableName }
|
||||
| { tag: "tuple", patterns: ProductPattern[] }
|
||||
| { tag: "record", fields: FieldPattern[] }
|
||||
|
||||
export type FieldPattern = { fieldName: FieldName, pattern: ProductPattern };
|
||||
|
||||
export type Pattern =
|
||||
| ProductPattern
|
||||
| { tag: "tag", tag_name: Tag }
|
||||
| { tag: "tagged", tag_name: Tag, pattern: Pattern }
|
||||
|
||||
|
||||
// === Constructors ===
|
||||
export namespace Expr {
|
||||
const literal = (literal: Literal): Expr => ({ tag: "literal", literal });
|
||||
export const number = (value: number): Expr => literal({ tag: "number", value });
|
||||
export const string = (value: string): Expr => literal({ tag: "string", value });
|
||||
export const call = (name: FunctionName, args: Expr[]): Expr => ({ tag: "call", name, args, });
|
||||
export const tag = (tag_name: Tag): Expr => ({ tag: "tag", tag_name, });
|
||||
export const tagged = (tag_name: Tag, expr: Expr): Expr => ({ tag: "tagged", tag_name, expr, });
|
||||
export const tuple = (exprs: Expr[]): Expr => ({ tag: "tuple", exprs });
|
||||
export const record = (fields: FieldAssignment[]): Expr => ({ tag: "record", fields });
|
||||
export const match = (arg: Expr, branches: MatchBranch[]): Expr => ({ tag: "match", arg, branches, });
|
||||
export const var_use = (name: VariableName): Expr => ({ tag: "var_use", name, });
|
||||
export const let_ = (bindings: ExprBinding[], body: Expr): Expr => ({ tag: "let", bindings, body, });
|
||||
export const apply = (callee: Expr, args: Expr[]): Expr => ({ tag: "apply", callee, args, });
|
||||
export const lambda = (parameters: ProductPattern[], body: Expr): Expr => ({ tag: "lambda", parameters, body, });
|
||||
|
||||
export const matchBranch = (pattern: Pattern, expr: Expr): MatchBranch => ({ pattern, body: expr });
|
||||
export const exprBinding = (pattern: ProductPattern, expr: Expr): ExprBinding => ({ pattern, expr });
|
||||
export const fieldAssignment = (name: FieldName, expr: Expr): FieldAssignment => ({ name, expr });
|
||||
}
|
||||
|
||||
export namespace ProductPattern {
|
||||
export const any = (name: VariableName): ProductPattern => ({ tag: "any", name });
|
||||
export const tuple = (patterns: ProductPattern[]): ProductPattern => ({ tag: "tuple", patterns });
|
||||
export const record = (fields: FieldPattern[]): ProductPattern => ({ tag: "record", fields });
|
||||
|
||||
export const fieldPattern = (fieldName: FieldName, pattern: ProductPattern): FieldPattern => ({ fieldName, pattern });
|
||||
}
|
||||
|
||||
export namespace Pattern {
|
||||
export const tag = (tag_name: Tag): Pattern => ({ tag: "tag", tag_name });
|
||||
export const tagged = (tag_name: Tag, pattern: Pattern): Pattern => ({ tag: "tagged", tag_name, pattern });
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { Expr, ExprBinding, FieldAssignment, FieldPattern, MatchBranch, Pattern, ProductPattern } from '../value';
|
||||
import { Cursor } from './cursor';
|
||||
import { ExprScanError, exprStart, ExprStartToken, IdentifierKind, identifierScanner, isNextTokenExprStart, isNextTokenProductPatternStart, patternStart, PatternStartToken, skipWhitespaceAndComments } from './scanner';
|
||||
import { char, CodePoint, SourceText, Span } from './source_text';
|
||||
import { Result } from '../result';
|
||||
import { Expr, ExprBinding, FieldAssignment, FieldPattern, MatchBranch, Pattern, ProductPattern } from '../expr';
|
||||
|
||||
// CONVENTION: Every parser is responsible to consume whitespace/comments at the end.
|
||||
// Every parser is not responsible for cleaning up whitespace/comments at the start - only the final `parse` that's exposed to the public.
|
||||
|
|
@ -465,3 +465,4 @@ export function parse(source: SourceText): Result<Expr, ParseError> {
|
|||
return Result.error(e as ParseError);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { CARRIAGE_RETURN, char, NEW_LINE } from './source_text';
|
||||
import type { Span, CodePoint } from './source_text';
|
||||
import { isDigit, isWhitespace, scanNumber, scanString } from './cursor';
|
||||
|
|
|
|||
137
src/lang/program.ts
Normal file
137
src/lang/program.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { ThrownRuntimeError } from "./eval/error";
|
||||
import { Value } from "./eval/value";
|
||||
import { Expr, FunctionName, ProductPattern } from "./expr";
|
||||
|
||||
export type Timestamp = number;
|
||||
|
||||
export type Program = {
|
||||
function_definitions: Map<FunctionName, FunctionDefinition>,
|
||||
function_definition_order: FunctionName[],
|
||||
// TODO: Perhaps include the story and the environment?
|
||||
// story should be a list of currently viewed bindings
|
||||
// environment should be like the store... maybe call it store! It should map names to values and perhaps expressions that generated the value...
|
||||
// like a reactive cell. This is the analogue of the tiddler.
|
||||
// store: Map<CellName, Cell>
|
||||
};
|
||||
|
||||
// type Cell = {
|
||||
// name: CellName,
|
||||
// expression: Expr,
|
||||
// cached_value?: Value,
|
||||
// status: CellStatus
|
||||
// // TODO: Dependencies? Not sure about this yet...
|
||||
// // Operational Semantics of Cells is gonna be thought up much later.
|
||||
// // dependencies?: Set<CellName>,
|
||||
// }
|
||||
|
||||
// type CellStatus =
|
||||
// | "clean"
|
||||
// | "dirty"
|
||||
// | "error"
|
||||
|
||||
export type FunctionDefinition =
|
||||
| { tag: "user", def: UserFunctionDefinition }
|
||||
| { tag: "primitive", def: PrimitiveFunctionDefinition }
|
||||
|
||||
export type UserFunctionDefinition = {
|
||||
// Raw user input (authoritative)
|
||||
name: FunctionName,
|
||||
raw_parameters: string;
|
||||
raw_body: string;
|
||||
|
||||
// parsed
|
||||
parameters: ProductPattern[],
|
||||
body: Expr,
|
||||
|
||||
|
||||
// metadata
|
||||
created_at: Timestamp;
|
||||
last_modified_at: Timestamp;
|
||||
}
|
||||
|
||||
export type PrimitiveFunctionDefinition = {
|
||||
name: FunctionName,
|
||||
implementation: (args: Value[]) => Value,
|
||||
}
|
||||
|
||||
export namespace Program {
|
||||
|
||||
type Error =
|
||||
| { tag: "DuplicateFunctionName", name: FunctionName }
|
||||
| { tag: "FunctionNotFound", name: FunctionName };
|
||||
|
||||
type Result<T> =
|
||||
| { tag: "ok", value: T }
|
||||
| { tag: "error", error: Error };
|
||||
// | { tag: "ParseError", message: string } // TODO
|
||||
|
||||
export namespace Result {
|
||||
export function ok<T>(value: T): Result<T> { return { tag: "ok", value } }
|
||||
export function error<T>(error: Error): Result<T> { return { tag: "error", error } }
|
||||
}
|
||||
|
||||
export function makeEmpty(): Program {
|
||||
return {
|
||||
function_definitions: new Map(),
|
||||
function_definition_order: [],
|
||||
};
|
||||
}
|
||||
// TODO: Primitive functions like +, -, *, div, <, <=, ==, mod
|
||||
|
||||
// TODO: function to create initial program (with the above primitive functions otherwise empty)
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
export function lookup_function(program: Program, name: FunctionName): FunctionDefinition {
|
||||
const fn = program.function_definitions.get(name);
|
||||
if (!fn) {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "FunctionLookupFailure",
|
||||
name,
|
||||
});
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
export type CreateFunction = {
|
||||
raw_name: string,
|
||||
raw_parameters: string,
|
||||
raw_body: string,
|
||||
}
|
||||
|
||||
export type UpdateFunction = {
|
||||
raw_name?: string,
|
||||
raw_parameters?: string,
|
||||
raw_body?: string,
|
||||
}
|
||||
|
||||
export function add_user_function(program: Program, description: CreateFunction): Result<void> {
|
||||
// TODO:
|
||||
// - parsing/validation
|
||||
// - raw_name (check if function already exists)
|
||||
// - raw_parameters
|
||||
// - raw_body
|
||||
// - compute timestamp for now
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
// TODO: What about result type? Should it on deletion return the original data of the function, and if there's a failure, how detailed should it be?
|
||||
export function delete_user_function(program: Program, name: FunctionName): Result<void> {
|
||||
// TODO:
|
||||
// - see if the user function exists
|
||||
// - if it does, delete it
|
||||
// - if it doesn't ???
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
export function update_user_function(program: Program, name: FunctionName): Result<void> {
|
||||
// TODO:
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
export function get_user_function(program: Program, name: FunctionName): Result<UserFunctionDefinition> {
|
||||
// TODO:
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,563 +0,0 @@
|
|||
|
||||
// === Identifiers ===
|
||||
export type VariableName = string
|
||||
export type FunctionName = string
|
||||
// type CellName = string
|
||||
export type Tag = string
|
||||
export type FieldName = string
|
||||
|
||||
// === Program ===
|
||||
export type Timestamp = number;
|
||||
|
||||
export type Program = {
|
||||
function_definitions: Map<FunctionName, FunctionDefinition>,
|
||||
function_definition_order: FunctionName[],
|
||||
// TODO: Perhaps include the story and the environment?
|
||||
// story should be a list of currently viewed bindings
|
||||
// environment should be like the store... maybe call it store! It should map names to values and perhaps expressions that generated the value...
|
||||
// like a reactive cell. This is the analogue of the tiddler.
|
||||
// store: Map<CellName, Cell>
|
||||
};
|
||||
|
||||
// type Cell = {
|
||||
// name: CellName,
|
||||
// expression: Expr,
|
||||
// cached_value?: Value,
|
||||
// status: CellStatus
|
||||
// // TODO: Dependencies? Not sure about this yet...
|
||||
// // Operational Semantics of Cells is gonna be thought up much later.
|
||||
// // dependencies?: Set<CellName>,
|
||||
// }
|
||||
|
||||
// type CellStatus =
|
||||
// | "clean"
|
||||
// | "dirty"
|
||||
// | "error"
|
||||
|
||||
export type FunctionDefinition =
|
||||
| { tag: "user", def: UserFunctionDefinition }
|
||||
| { tag: "primitive", def: PrimitiveFunctionDefinition }
|
||||
|
||||
export type UserFunctionDefinition = {
|
||||
// Raw user input (authoritative)
|
||||
name: FunctionName,
|
||||
raw_parameters: string;
|
||||
raw_body: string;
|
||||
|
||||
// parsed
|
||||
parameters: ProductPattern[],
|
||||
body: Expr,
|
||||
|
||||
|
||||
// metadata
|
||||
created_at: Timestamp;
|
||||
last_modified_at: Timestamp;
|
||||
}
|
||||
|
||||
export type PrimitiveFunctionDefinition = {
|
||||
name: FunctionName,
|
||||
implementation: (args: Value[]) => Value,
|
||||
}
|
||||
|
||||
export namespace Program {
|
||||
|
||||
type Error =
|
||||
| { tag: "DuplicateFunctionName", name: FunctionName }
|
||||
| { tag: "FunctionNotFound", name: FunctionName };
|
||||
|
||||
type Result<T> =
|
||||
| { tag: "ok", value: T }
|
||||
| { tag: "error", error: Error };
|
||||
// | { tag: "ParseError", message: string } // TODO
|
||||
|
||||
export namespace Result {
|
||||
export function ok<T>(value: T): Result<T> { return { tag: "ok", value } }
|
||||
export function error<T>(error: Error): Result<T> { return { tag: "error", error } }
|
||||
}
|
||||
|
||||
export function makeEmpty(): Program {
|
||||
return {
|
||||
function_definitions: new Map(),
|
||||
function_definition_order: [],
|
||||
};
|
||||
}
|
||||
// TODO: Primitive functions like +, -, *, div, <, <=, ==, mod
|
||||
|
||||
// TODO: function to create initial program (with the above primitive functions otherwise empty)
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
export function lookup_function(program: Program, name: FunctionName): FunctionDefinition {
|
||||
const fn = program.function_definitions.get(name);
|
||||
if (!fn) {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "FunctionLookupFailure",
|
||||
name,
|
||||
});
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
export type CreateFunction = {
|
||||
raw_name: string,
|
||||
raw_parameters: string,
|
||||
raw_body: string,
|
||||
}
|
||||
|
||||
export type UpdateFunction = {
|
||||
raw_name?: string,
|
||||
raw_parameters?: string,
|
||||
raw_body?: string,
|
||||
}
|
||||
|
||||
export function add_user_function(program: Program, description: CreateFunction): Result<void> {
|
||||
// TODO:
|
||||
// - parsing/validation
|
||||
// - raw_name (check if function already exists)
|
||||
// - raw_parameters
|
||||
// - raw_body
|
||||
// - compute timestamp for now
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
// TODO: What about result type? Should it on deletion return the original data of the function, and if there's a failure, how detailed should it be?
|
||||
export function delete_user_function(program: Program, name: FunctionName): Result<void> {
|
||||
// TODO:
|
||||
// - see if the user function exists
|
||||
// - if it does, delete it
|
||||
// - if it doesn't ???
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
export function update_user_function(program: Program, name: FunctionName): Result<void> {
|
||||
// TODO:
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
export function get_user_function(program: Program, name: FunctionName): Result<UserFunctionDefinition> {
|
||||
// TODO:
|
||||
return (0 as any);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// === Expressions ===
|
||||
|
||||
export type Expr =
|
||||
| { tag: "literal", literal: Literal }
|
||||
| { tag: "var_use", name: VariableName }
|
||||
// | { tag: "cell_ref", name: CellName }
|
||||
| { tag: "call", name: FunctionName, args: Expr[] }
|
||||
| { tag: "let", bindings: ExprBinding[], body: Expr }
|
||||
| { tag: "tag", tag_name: Tag }
|
||||
| { tag: "tagged", tag_name: Tag, expr: Expr }
|
||||
| { tag: "tuple", exprs: Expr[] }
|
||||
| { tag: "record", fields: FieldAssignment[] }
|
||||
| { tag: "match", arg: Expr, branches: MatchBranch[] }
|
||||
| { tag: "lambda", parameters: ProductPattern[], body: Expr }
|
||||
| { tag: "apply", callee: Expr, args: Expr[] }
|
||||
|
||||
export type Literal =
|
||||
| { tag: "number", value: number }
|
||||
| { tag: "string", value: string }
|
||||
|
||||
export type ExprBinding = {
|
||||
pattern: ProductPattern,
|
||||
expr: Expr,
|
||||
}
|
||||
|
||||
export type MatchBranch = {
|
||||
pattern: Pattern,
|
||||
body: Expr,
|
||||
}
|
||||
|
||||
export type FieldAssignment = { name: FieldName, expr: Expr };
|
||||
|
||||
export type ProductPattern =
|
||||
| { tag: "any", name: VariableName }
|
||||
| { tag: "tuple", patterns: ProductPattern[] }
|
||||
| { tag: "record", fields: FieldPattern[] }
|
||||
|
||||
export type FieldPattern = { fieldName: FieldName, pattern: ProductPattern };
|
||||
|
||||
export type Pattern =
|
||||
| ProductPattern
|
||||
| { tag: "tag", tag_name: Tag }
|
||||
| { tag: "tagged", tag_name: Tag, pattern: Pattern }
|
||||
|
||||
|
||||
// === Values ===
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
// === Constructors ===
|
||||
export namespace Expr {
|
||||
const literal = (literal: Literal): Expr => ({ tag: "literal", literal });
|
||||
export const number = (value: number): Expr => literal({ tag: "number", value });
|
||||
export const string = (value: string): Expr => literal({ tag: "string", value });
|
||||
export const call = (name: FunctionName, args: Expr[]): Expr => ({ tag: "call", name, args, });
|
||||
export const tag = (tag_name: Tag): Expr => ({ tag: "tag", tag_name, });
|
||||
export const tagged = (tag_name: Tag, expr: Expr): Expr => ({ tag: "tagged", tag_name, expr, });
|
||||
export const tuple = (exprs: Expr[]): Expr => ({ tag: "tuple", exprs });
|
||||
export const record = (fields: FieldAssignment[]): Expr => ({ tag: "record", fields });
|
||||
export const match = (arg: Expr, branches: MatchBranch[]): Expr => ({ tag: "match", arg, branches, });
|
||||
export const var_use = (name: VariableName): Expr => ({ tag: "var_use", name, });
|
||||
export const let_ = (bindings: ExprBinding[], body: Expr): Expr => ({ tag: "let", bindings, body, });
|
||||
export const apply = (callee: Expr, args: Expr[]): Expr => ({ tag: "apply", callee, args, });
|
||||
export const lambda = (parameters: ProductPattern[], body: Expr): Expr => ({ tag: "lambda", parameters, body, });
|
||||
|
||||
export const matchBranch = (pattern: Pattern, expr: Expr): MatchBranch => ({ pattern, body: expr });
|
||||
export const exprBinding = (pattern: ProductPattern, expr: Expr): ExprBinding => ({ pattern, expr });
|
||||
export const fieldAssignment = (name: FieldName, expr: Expr): FieldAssignment => ({ name, 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 ProductPattern {
|
||||
export const any = (name: VariableName): ProductPattern => ({ tag: "any", name });
|
||||
export const tuple = (patterns: ProductPattern[]): ProductPattern => ({ tag: "tuple", patterns });
|
||||
export const record = (fields: FieldPattern[]): ProductPattern => ({ tag: "record", fields });
|
||||
|
||||
export const fieldPattern = (fieldName: FieldName, pattern: ProductPattern): FieldPattern => ({ fieldName, pattern });
|
||||
}
|
||||
|
||||
export namespace Pattern {
|
||||
export const tag = (tag_name: Tag): Pattern => ({ tag: "tag", tag_name });
|
||||
export const tagged = (tag_name: Tag, pattern: Pattern): Pattern => ({ tag: "tagged", tag_name, pattern });
|
||||
}
|
||||
|
||||
// ===Errors===
|
||||
type RuntimeError =
|
||||
| { tag: "FunctionLookupFailure", name: FunctionName }
|
||||
| { tag: "FunctionCallArityMismatch", name: FunctionName, expected: number, actual: number }
|
||||
| { tag: "ClosureApplicationArityMismatch", closure: Closure, expected: number, actual: number }
|
||||
| { tag: "VariableLookupFailure", name: VariableName }
|
||||
// | { tag: "CellLookupFailure", name: CellName }
|
||||
| { tag: "UnableToFindMatchingPattern", value: Value }
|
||||
| { tag: "TypeMismatch", expected: ValueTag, received: Value }
|
||||
| { tag: "DuplicateVariableNamesInPattern", pattern: Pattern, duplicates: VariableName[] }
|
||||
// | { tag: "DuplicateVariableNamesInProductPattern", pattern: ProductPattern, duplicates: VariableName[] }
|
||||
|
||||
type Result<T> =
|
||||
| { tag: "ok", value: T }
|
||||
| { tag: "error", error: RuntimeError }
|
||||
|
||||
export namespace Result {
|
||||
export function ok<T>(value: T): Result<T> { return { tag: "ok", value } }
|
||||
export function error<T>(error: RuntimeError): Result<T> { return { tag: "error", error } }
|
||||
}
|
||||
|
||||
// This is an internal type - use it in all internal evaluation functions.
|
||||
type ThrownRuntimeError = {
|
||||
kind: "RuntimeError",
|
||||
error: RuntimeError
|
||||
}
|
||||
|
||||
namespace ThrownRuntimeError {
|
||||
// use as follows
|
||||
// `throw ThrownRuntimeError.error(e)`
|
||||
export function error(error: RuntimeError): ThrownRuntimeError {
|
||||
return { kind: "RuntimeError", error };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===Evaluation===
|
||||
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 frame_insert_mut(frame: EnvFrame, var_name: VariableName, value: Value) {
|
||||
frame.set(var_name, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function eval_start(program: Program, e: Expr): Result<Value> {
|
||||
try {
|
||||
return Result.ok(eval_expr(program, Env.nil(), e));
|
||||
} catch (err) {
|
||||
if (typeof err === "object" && (err as any).kind === "RuntimeError") {
|
||||
return Result.error((err as ThrownRuntimeError).error);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function eval_expr(program: Program, env: Env, e: Expr): Value {
|
||||
switch (e.tag) {
|
||||
case "literal":
|
||||
switch (e.literal.tag) {
|
||||
case "number":
|
||||
return Value.number(e.literal.value);
|
||||
case "string":
|
||||
return Value.string(e.literal.value);
|
||||
}
|
||||
case "tag":
|
||||
return Value.tag(e.tag_name);
|
||||
case "tagged":
|
||||
return Value.tagged(e.tag_name, eval_expr(program, env, e.expr));
|
||||
case "tuple":
|
||||
return Value.tuple(eval_sequence(program, env, e.exprs));
|
||||
case "record":
|
||||
const fields = new Map<FieldName, Value>();
|
||||
for (const field of e.fields) {
|
||||
const value = eval_expr(program, env, field.expr);
|
||||
fields.set(field.name, value);
|
||||
}
|
||||
return Value.record(fields);
|
||||
case "lambda":
|
||||
return Value.closure({
|
||||
env,
|
||||
parameters: e.parameters,
|
||||
body: e.body,
|
||||
});
|
||||
case "var_use":
|
||||
return Env.lookup(env, e.name);
|
||||
case "call":
|
||||
const fn = Program.lookup_function(program, e.name);
|
||||
const fn_args = eval_sequence(program, env, e.args);
|
||||
return call_function(program, fn, fn_args);
|
||||
case "apply":
|
||||
const closure = force_closure(eval_expr(program, env, e.callee));
|
||||
const closure_args = eval_sequence(program, env, e.args);
|
||||
return apply_closure(program, closure, closure_args);
|
||||
case "let":
|
||||
const new_env = eval_bindings(program, env, e.bindings);
|
||||
return eval_expr(program, new_env, e.body);
|
||||
case "match":
|
||||
const match_val = eval_expr(program, env, e.arg);
|
||||
for (const branch of e.branches) {
|
||||
const res = match_pattern(branch.pattern, match_val);
|
||||
if (res.tag === "match") {
|
||||
return eval_expr(program, Env.push_frame(env, res.frame), branch.body);
|
||||
}
|
||||
}
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "UnableToFindMatchingPattern",
|
||||
value: match_val,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function eval_bindings(program: Program, env: Env, bindings: ExprBinding[]): Env {
|
||||
// note that `let { x = 123, y = x + 1 ... } is allowed. Ofcourse later bindings can't be referenced by earlier bindings (i.e. no recursion).
|
||||
let cur_env = env;
|
||||
for (const { pattern: var_name, expr } of bindings) {
|
||||
const value = eval_expr(program, cur_env, expr);
|
||||
const res = match_product_pattern(var_name, value);
|
||||
if (res.tag === "failure") {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "UnableToFindMatchingPattern",
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
cur_env = Env.push_frame(cur_env, res.frame);
|
||||
}
|
||||
}
|
||||
return cur_env;
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function eval_sequence(program: Program, env: Env, args: Expr[]): Value[] {
|
||||
return args.map(arg => eval_expr(program, env, arg));
|
||||
}
|
||||
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function call_function(program: Program, fn_def: FunctionDefinition, args: Value[]): Value {
|
||||
switch (fn_def.tag) {
|
||||
case "user":
|
||||
return call_user_function(program, fn_def.def, args);
|
||||
case "primitive":
|
||||
return fn_def.def.implementation(args);
|
||||
}
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function call_user_function(program: Program, fn_def: UserFunctionDefinition, args: Value[]): Value {
|
||||
const frame = bind_arguments_to_parameters(
|
||||
fn_def.parameters,
|
||||
args,
|
||||
(expected, actual) => ({ tag: "FunctionCallArityMismatch", name: fn_def.name, expected, actual })
|
||||
);
|
||||
|
||||
return eval_expr(program, Env.push_frame(Env.nil(), frame), fn_def.body);
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function apply_closure(program: Program, closure: Closure, args: Value[]): Value {
|
||||
const frame = bind_arguments_to_parameters(
|
||||
closure.parameters,
|
||||
args,
|
||||
(expected, actual) => ({ tag: "ClosureApplicationArityMismatch", closure, expected, actual })
|
||||
);
|
||||
|
||||
return eval_expr(program, Env.push_frame(closure.env, frame), closure.body);
|
||||
}
|
||||
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function force_closure(value: Value): Closure {
|
||||
if (value.tag !== "closure") {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "TypeMismatch",
|
||||
expected: "closure",
|
||||
received: value,
|
||||
});
|
||||
}
|
||||
return value.closure;
|
||||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`
|
||||
function bind_arguments_to_parameters(
|
||||
patterns: ProductPattern[],
|
||||
values: Value[],
|
||||
onArityMismatchError: (expected: number, actual: number) => RuntimeError
|
||||
): EnvFrame {
|
||||
const expected = patterns.length;
|
||||
const actual = values.length;
|
||||
|
||||
if (expected !== actual) {
|
||||
throw ThrownRuntimeError.error(onArityMismatchError(expected, actual));
|
||||
}
|
||||
|
||||
const frame: EnvFrame = new Map();
|
||||
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
const pattern = patterns[i];
|
||||
const value = values[i];
|
||||
const res = match_product_pattern_mut(frame, pattern, value);
|
||||
if (res.tag === "failure") {
|
||||
throw ThrownRuntimeError.error({
|
||||
tag: "UnableToFindMatchingPattern",
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
// === Pattern Matching ===
|
||||
// A pattern match will result either in a succesfull match with a new EnvFrame
|
||||
type PatternMatchingResult =
|
||||
| { tag: "match", frame: EnvFrame }
|
||||
| { tag: "failure", pattern: Pattern, value: Value }
|
||||
|
||||
function match_pattern(pattern: Pattern, value: Value): PatternMatchingResult {
|
||||
const frame = new Map<VariableName, Value>();
|
||||
return match_pattern_mut(frame, pattern, value);
|
||||
}
|
||||
|
||||
function match_pattern_mut(frame: EnvFrame, pattern: Pattern, value: Value): PatternMatchingResult {
|
||||
switch (pattern.tag) {
|
||||
case "tag":
|
||||
if (value.tag === "tag" && value.tag_name === pattern.tag_name) {
|
||||
return { tag: "match", frame }
|
||||
} else {
|
||||
return { tag: "failure", pattern, value }
|
||||
}
|
||||
case "tagged":
|
||||
if (value.tag === "tagged" && value.tag_name === pattern.tag_name) {
|
||||
return match_pattern_mut(frame, pattern.pattern, value.value);
|
||||
} else {
|
||||
return { tag: "failure", pattern, value };
|
||||
}
|
||||
default:
|
||||
return match_product_pattern_mut(frame, pattern, value);
|
||||
}
|
||||
}
|
||||
|
||||
function match_product_pattern(pattern: ProductPattern, value: Value): PatternMatchingResult {
|
||||
const frame = new Map<VariableName, Value>();
|
||||
return match_product_pattern_mut(frame, pattern, value);
|
||||
}
|
||||
|
||||
function match_product_pattern_mut(frame: EnvFrame, pattern: ProductPattern, value: Value): PatternMatchingResult {
|
||||
switch (pattern.tag) {
|
||||
case "any":
|
||||
frame.set(pattern.name, value);
|
||||
return { tag: "match", frame };
|
||||
case "tuple":
|
||||
if (value.tag !== "tuple" || pattern.patterns.length !== value.values.length) return { tag: "failure", pattern, value };
|
||||
for (let i = 0; i < pattern.patterns.length; i++) {
|
||||
const res = match_product_pattern_mut(frame, pattern.patterns[i], value.values[i]);
|
||||
if (res.tag === "failure") return res;
|
||||
}
|
||||
return { tag: "match", frame };
|
||||
case "record":
|
||||
if (value.tag !== "record") return { tag: "failure", pattern, value };
|
||||
|
||||
for (const { fieldName, pattern: p } of pattern.fields) {
|
||||
const field_value = value.fields.get(fieldName);
|
||||
if (field_value === undefined) {
|
||||
return { tag: "failure", pattern, value };
|
||||
} else {
|
||||
const res = match_product_pattern_mut(frame, p, field_value);
|
||||
if (res.tag === "failure") {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { tag: "match", frame };
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue