UNPKG

intrear

Version:

Next-gen way to create your own programming language!

1,383 lines (1,344 loc) 36.6 kB
// src/interpreter.ts function compareTypes(a, b) { if (typeof a === "string" && typeof b === "string") { return a === b; } if (typeof a === "object" && typeof b === "object") { if (a.kind === "pointer" && b.kind === "pointer") { return compareTypes(a.to, b.to); } if (a.kind === "function" && b.kind === "function") { if (a.paramTypes.length !== b.paramTypes.length) return false; for (let i = 0;i < a.paramTypes.length; i++) { if (!compareTypes(a.paramTypes[i], b.paramTypes[i])) return false; } return compareTypes(a.returnType, b.returnType); } if (a.kind === "array" && b.kind === "array") { return compareTypes(a.elementType, b.elementType); } if (a.kind === "object" && b.kind === "object") { const aKeys = Object.keys(a.properties); const bKeys = Object.keys(b.properties); if (aKeys.length !== bKeys.length) return false; for (let key of aKeys) { if (!(key in b.properties) || !compareTypes(a.properties[key], b.properties[key])) { return false; } } return true; } } return false; } function typeToString(t) { if (typeof t === "string") return t; if (t.kind === "function") { const params = t.paramTypes.map(typeToString).join(", "); return `(${params}) => ${typeToString(t.returnType)}`; } if (t.kind === "array") { return `Array<${typeToString(t.elementType)}>`; } if (t.kind === "object") { const props = Object.entries(t.properties).map(([key, type]) => `${key}: ${typeToString(type)}`).join(", "); return `{ ${props} }`; } return "unknown"; } class TypeEnvironment { types = {}; parent; constructor(parent) { this.parent = parent; } getType(name) { if (name in this.types) { return this.types[name]; } if (this.parent) { return this.parent.getType(name); } throw new Error(`Undefined variable (type): ${name}`); } setType(name, type) { this.types[name] = type; } createChild() { return new TypeEnvironment(this); } } class ExecutionContext { variables = {}; parent; breakSignal = false; continueSignal = false; constructor(parent) { this.parent = parent; this.injectBuiltIns(); } injectBuiltIns() { const builtIns = { print: (...args) => console.log(...args), printSelf: () => console.log(this), typeOf: (val) => Array.isArray(val) ? "array" : typeof val, now: () => Date.now(), random: () => Math.random(), isNaN: (x) => isNaN(x), abs: (x) => Math.abs(x), sqrt: (x) => Math.sqrt(x), floor: (x) => Math.floor(x), ceil: (x) => Math.ceil(x), fetch: async (url) => { const res = await fetch(url); return await res.text(); }, temporaryRm: (x) => { const saved = this.getVariable(x); delete this.variables[x]; return () => { this.setVariable(x, saved); }; } }; const keys = Object.keys(builtIns); keys.forEach((key) => { this.setVariable(key, builtIns[key]); }); } getVariable(name) { if (name in this.variables) return this.variables[name]; if (this.parent) return this.parent.getVariable(name); return; } setVariable(name, value) { this.variables[name] = value; } createChildContext() { return new ExecutionContext(this); } } class ASTNode { } var CustomASTNode = (exec, type) => { return new class extends ASTNode { constructor() { super(); } execute(context) { return exec(context); } inferType(env) { if (typeof type === "function") { return type(env); } if (typeof type === "string") { return type; } return type; } }; }; function memoize(fn) { const cache = new Map; return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }; } class VariableDeclarationNode extends ASTNode { varType; name; expression; constructor(varType, name, expression) { super(); this.varType = varType; this.name = name; this.expression = expression; } execute(context) { let val; if (this.varType === "function") { if (this.expression instanceof FunctionLiteralNode || this.expression instanceof ArrowFunctionNode) { val = this.expression.toFunction(context); } else { const result = this.expression.execute(context); if (typeof result !== "function") { throw new Error(`Variable '${this.name}' expected a function, got ${typeof result}`); } val = result; } } else if (this.varType === "pointer") { const result = this.expression.execute(context); if (!result?.__isPtr) { throw new Error(`Expected pointer, got ${typeof result}`); } val = result; } else { val = this.expression.execute(context); const typeChecks = { number: [(v) => typeof v === "number", "number"], string: [(v) => typeof v === "string", "string"], boolean: [(v) => typeof v === "boolean", "boolean"], undefined: [(v) => v === undefined, "undefined"], null: [(v) => v === null, "null"], bigint: [(v) => typeof v === "bigint", "bigint"], any: [(v) => true, "any"], array: [(v) => Array.isArray(v), "array"], object: [ (v) => v !== null && typeof v === "object" && !Array.isArray(v), "object" ] }; const validator = typeChecks[this.varType]; if (!validator) { throw new Error(`Unsupported variable type: ${this.varType}`); } const [checkFn, expected] = validator; if (!checkFn(val)) { const got = val === null ? "null" : Array.isArray(val) ? "array" : typeof val; throw new Error(`Variable '${this.name}' expected ${expected}, got ${got}`); } } context.setVariable(this.name, val); return val; } inferType(env) { if (this.varType === "function") { const actualType = this.expression.inferType(env); if (typeof actualType === "object" && actualType.kind === "function") { env.setType(this.name, actualType); return actualType; } else { throw new Error(`Type mismatch in declaration of '${this.name}': expected function type, got ${typeToString(actualType)}`); } } else if (this.varType === "pointer") { const actualType = this.expression.inferType(env); if (typeof actualType === "object" && actualType.kind === "pointer") { env.setType(this.name, actualType); return actualType; } throw new Error(`Expected pointer type, got ${typeToString(actualType)}`); } else { const actualType = this.expression.inferType(env); const typeMap = { number: "number", string: "string", boolean: "boolean", null: "null", undefined: "undefined", bigint: "bigint", symbol: "symbol", void: "void", any: "any", array: actualType, object: actualType }; const expected = typeMap[this.varType]; if (!expected) { throw new Error(`Unsupported variable type: ${this.varType}`); } if (!compareTypes(actualType, expected)) { throw new Error(`Type mismatch in declaration of '${this.name}': expected ${typeToString(expected)}, got ${typeToString(actualType)}`); } env.setType(this.name, actualType); return actualType; } } } class AssignmentNode extends ASTNode { name; expression; constructor(name, expression) { super(); this.name = name; this.expression = expression; } execute(context) { const value = this.expression.execute(context); context.setVariable(this.name, value); return value; } inferType(env) { const currentType = env.getType(this.name); const newType = this.expression.inferType(env); if (!compareTypes(currentType, newType)) { throw new Error(`Type mismatch in assignment to '${this.name}': expected ${JSON.stringify(currentType)}, got ${JSON.stringify(newType)}`); } return currentType; } } class ReturnSignal { value; constructor(value) { this.value = value; } } class BreakSignal { } class ContinueSignal { } class ReturnNode extends ASTNode { expression; constructor(expression) { super(); this.expression = expression; } execute(ctx) { const v = this.expression.execute(ctx); throw new ReturnSignal(v); } inferType(env) { return this.expression.inferType(env); } } class BreakNode extends ASTNode { execute(ctx) { throw new BreakSignal; } inferType(env) { return "void"; } } class ContinueNode extends ASTNode { execute(ctx) { throw new ContinueSignal; } inferType(env) { return "void"; } } class ErrorNode extends ASTNode { message; constructor(message) { super(); this.message = message; } execute(context) { const msg = this.message.execute(context); throw new Error(String(msg)); } inferType(env) { return "void"; } } class FunctionLiteralNode extends ASTNode { name; params; body; declaredParamTypes; declaredReturnType; pure; constructor(name, params, body, declaredParamTypes, declaredReturnType, pure = false) { super(); this.name = name; this.params = params; this.body = body; this.declaredParamTypes = declaredParamTypes; this.declaredReturnType = declaredReturnType; this.pure = pure; } execute(context) { throw new Error("Function literal must be transformed into a callable via toFunction()."); } toFunction(outerContext) { let fn = (...args) => { const localCtx = outerContext.createChildContext(); if (args.length !== this.params.length) throw new Error("Argument count mismatch."); this.params.forEach((p, i) => localCtx.setVariable(p, args[i])); try { for (const stmt of this.body) { stmt.execute(localCtx); } return; } catch (e) { if (e instanceof ReturnSignal) { return e.value; } throw e; } }; if (this.pure) fn = memoize(fn); return fn; } inferType(env) { const localEnv = env.createChild(); const paramTypes = []; (this.declaredParamTypes || []).forEach((pt, i) => { localEnv.setType(this.params[i], pt); paramTypes.push(pt); }); let bodyType = "void"; for (const stmt of this.body) { bodyType = stmt.inferType(localEnv); } if (this.declaredReturnType && !compareTypes(bodyType, this.declaredReturnType)) { throw new Error(`Return type mismatch: expected ${typeToString(this.declaredReturnType)}, got ${typeToString(bodyType)}`); } if (this.name) { env.setType(this.name, { kind: "function", paramTypes, returnType: this.declaredReturnType || bodyType }); } return { kind: "function", paramTypes, returnType: this.declaredReturnType || bodyType }; } } class ArrowFunctionNode extends ASTNode { paramNames; body; constructor(paramNames, body) { super(); this.paramNames = paramNames; this.body = body; } execute(context) { throw new Error("Arrow function must be transformed into a callable via toFunction()."); } toFunction(context) { return (...args) => { const childCtx = context.createChildContext(); this.paramNames.forEach((name, i) => { childCtx.setVariable(name, args[i]); }); return this.body.execute(childCtx); }; } inferType(env) { const childEnv = env.createChild(); const paramTypes = []; this.paramNames.forEach((name) => { childEnv.setType(name, "any"); paramTypes.push("any"); }); const returnType = this.body.inferType(childEnv); return { kind: "function", paramTypes, returnType }; } } class FunctionCallNode extends ASTNode { functionName; args; constructor(functionName, args) { super(); this.functionName = functionName; this.args = args; } execute(context) { const value = context.getVariable(this.functionName); if (typeof value !== "function") { throw new Error(`'${this.functionName}' is not callable`); } const evaluated = this.args.map((arg) => arg instanceof FunctionLiteralNode || arg instanceof ArrowFunctionNode ? arg.toFunction(context) : arg.execute(context)); return value(...evaluated); } inferType(env) { const fnType = env.getType(this.functionName); if (typeof fnType === "object" && fnType.kind === "function") { if (fnType.paramTypes.length !== this.args.length) { throw new Error("Argument count mismatch in call"); } this.args.forEach((arg, i) => { const at = arg.inferType(env); if (!compareTypes(at, fnType.paramTypes[i])) { throw new Error(`Arg type mismatch at position ${i}: expected ${typeToString(fnType.paramTypes[i])} got ${typeToString(at)}`); } }); return fnType.returnType; } throw new Error(`'${this.functionName}' is not a function`); } } class LiteralNode extends ASTNode { value; constructor(value) { super(); this.value = value; } execute(context) { return this.value; } inferType(env) { const v = this.value; if (v === null) return "null"; const t = typeof v; if (t === "number") return "number"; if (t === "string") return "string"; if (t === "boolean") return "boolean"; if (t === "undefined") return "undefined"; if (t === "bigint") return "bigint"; if (t === "symbol") return "symbol"; return "any"; } } class VariableReferenceNode extends ASTNode { name; constructor(name) { super(); this.name = name; } execute(context) { return context.getVariable(this.name); } inferType(env) { return env.getType(this.name); } toReference(ctx) { return { __isPtr: true, get: () => ctx.getVariable(this.name), set: (v) => ctx.setVariable(this.name, v) }; } } class OperatorNode extends ASTNode { operator; operands; constructor(operator, operands) { super(); this.operator = operator; this.operands = operands; } execute(context) { const leftVal = this.operands[0].execute(context); const rightVal = this.operands[1].execute(context); switch (this.operator) { case "+": return leftVal + rightVal; case "-": return leftVal - rightVal; case "*": return leftVal * rightVal; case "/": return leftVal / rightVal; case "//": return Math.floor(leftVal / rightVal); case "^": return leftVal ** rightVal; case "==": return leftVal === rightVal; case "!==": return leftVal !== rightVal; case "<": return leftVal < rightVal; case "<=": return leftVal <= rightVal; case ">": return leftVal > rightVal; case ">=": return leftVal >= rightVal; case "&&": return Boolean(leftVal) && Boolean(rightVal); case "||": return Boolean(leftVal) || Boolean(rightVal); case "concat": return Array.isArray(leftVal) && Array.isArray(rightVal) ? leftVal.concat(rightVal) : (() => { throw new Error(`Operator 'concat' requires two arrays.`); })(); case "><": return String(leftVal) + String(rightVal); default: throw new Error(`Unknown operator: ${this.operator}`); } } inferType(env) { const leftType = this.operands[0].inferType(env); const rightType = this.operands[1].inferType(env); switch (this.operator) { case "o_plus": case "o_minus": case "o_mul": case "o_div": case "o_idiv": case "o_pow": if (leftType !== "number" || rightType !== "number") { throw new Error(`${this.operator} requires numeric operands.`); } return "number"; case "o_eq": case "o_neq": case "o_lt": case "o_lte": case "o_gt": case "o_gte": if (!compareTypes(leftType, rightType)) { throw new Error(`${this.operator} requires operands of same type.`); } return "boolean"; case "o_and": case "o_or": if (leftType !== "boolean" || rightType !== "boolean") { throw new Error(`${this.operator} requires boolean operands.`); } return "boolean"; case "o_concat": if (typeof leftType === "object" && typeof rightType === "object") { if (leftType.kind !== "array" || rightType.kind !== "array") { throw new Error(`Operator 'concat' requires both operands to be arrays.`); } const elemType = compareTypes(leftType.elementType, rightType.elementType) ? leftType.elementType : "any"; return { kind: "array", elementType: elemType }; } else { throw new Error(`Operator 'concat' requires both operands to be arrays.`); } default: throw new Error(`Unknown operator in type inference: ${this.operator}`); } } } class MethodCallNode extends ASTNode { target; methodName; args; constructor(target, methodName, args) { super(); this.target = target; this.methodName = methodName; this.args = args; } execute(context) { let result = this.target.execute(context); if (result == null) throw new Error(`Cannot call method '${this.methodName}' on ${result}`); const evaluatedArgs = this.args.map((arg) => arg.execute(context)); const targetType = typeof result; if (targetType === "string") { switch (this.methodName) { case "length": return result.length; case "toUpperCase": return result.toUpperCase(); case "toLowerCase": return result.toLowerCase(); case "slice": return result.slice(...evaluatedArgs); case "parseInt": return parseInt(result); case "parseFloat": return parseFloat(result); default: throw new Error(`Unknown string method: ${this.methodName}`); } } else if (Array.isArray(result)) { switch (this.methodName) { case "length": return result.length; case "push": return result.push(...evaluatedArgs); case "pop": return result.pop(); case "map": return result.map(evaluatedArgs[0]); case "filter": return result.filter(evaluatedArgs[0]); default: throw new Error(`Unknown array method: ${this.methodName}`); } } else if (targetType === "number") { switch (this.methodName) { case "toString": return result.toString(); default: throw new Error(`Unknown number method: ${this.methodName}`); } } const fn = result[this.methodName]; if (typeof fn === "function") return fn.apply(result, evaluatedArgs); throw new Error(`'${this.methodName}' is not a method on ${result}`); } inferType(env) { const targetType = this.target.inferType(env); if (targetType === "string") { switch (this.methodName) { case "length": return "number"; case "toUpperCase": case "toLowerCase": case "slice": return "string"; case "parseInt": case "parseFloat": return "number"; } } if (typeof targetType === "object" && targetType.kind === "array") { switch (this.methodName) { case "length": return "number"; case "push": return "number"; case "pop": return targetType.elementType; case "map": case "filter": return { kind: "array", elementType: "any" }; } } return "any"; } } class ParameterNode extends ASTNode { name; constructor(name) { super(); this.name = name; } execute(context) { return this.name; } inferType(env) { return env.getType(this.name); } } class ArrayLiteralNode extends ASTNode { elements; constructor(elements) { super(); this.elements = elements; } execute(context) { return this.elements.map((el) => el.execute(context)); } inferType(env) { if (this.elements.length === 0) return { kind: "array", elementType: "any" }; let elementType = this.elements[0].inferType(env); for (let i = 1;i < this.elements.length; i++) { const currentType = this.elements[i].inferType(env); if (!compareTypes(elementType, currentType)) { elementType = "any"; break; } } return { kind: "array", elementType }; } } class IndexAssignmentNode extends ASTNode { target; index; value; constructor(target, index, value) { super(); this.target = target; this.index = index; this.value = value; } execute(context) { const arr = this.target.execute(context); const i = this.index.execute(context); const val = this.value.execute(context); if (!Array.isArray(arr)) throw new Error("Target is not an array"); if (typeof i !== "number") throw new Error("Index must be a number"); arr[i] = val; return val; } inferType(env) { const arrType = this.target.inferType(env); const indexType = this.index.inferType(env); const valType = this.value.inferType(env); if (indexType !== "number") throw new Error("Index must be a number"); if (typeof arrType === "object" && arrType.kind === "array") { if (!compareTypes(arrType.elementType, valType)) { throw new Error("Assigned value type does not match array element type"); } return valType; } throw new Error("Target must be an array"); } } class IndexAccessNode extends ASTNode { array; index; constructor(array, index) { super(); this.array = array; this.index = index; } execute(context) { const arr = this.array.execute(context); const i = this.index.execute(context); if (!Array.isArray(arr)) throw new Error("Target is not an array"); return arr[i]; } inferType(env) { const arrType = this.array.inferType(env); if (typeof arrType === "object" && arrType.kind === "array") { return arrType.elementType; } return "any"; } toReference(context) { const arr = this.array.execute(context); const i = this.index.execute(context); if (!Array.isArray(arr)) throw new Error("Target is not an array"); return { __isPtr: true, get: () => arr[i], set: (v) => { arr[i] = v; } }; } } class ObjectLiteralNode extends ASTNode { properties; constructor(properties) { super(); this.properties = properties; } execute(context) { const result = {}; for (const key in this.properties) { result[key] = this.properties[key].execute(context); } return result; } inferType(env) { const propTypes = {}; for (const key in this.properties) { propTypes[key] = this.properties[key].inferType(env); } return { kind: "object", properties: propTypes }; } } class PropertyAccessNode extends ASTNode { object; property; constructor(object, property) { super(); this.object = object; this.property = property; } execute(context) { const obj = this.object.execute(context); if (obj == null) throw new Error("Cannot access property of null/undefined"); const key = typeof this.property === "string" ? this.property : this.property.execute(context); return obj[key]; } inferType(env) { const objType = this.object.inferType(env); if (typeof objType === "object" && objType.kind === "object") { if (typeof this.property === "string") { return objType.properties[this.property] ?? "any"; } } return "any"; } toReference(context) { const obj = this.object.execute(context); if (obj == null) throw new Error("Cannot get reference of property on null/undefined"); const key = typeof this.property === "string" ? this.property : this.property.execute(context); return { __isPtr: true, get: () => obj[key], set: (v) => { obj[key] = v; } }; } } class BlockNode extends ASTNode { statements; constructor(statements) { super(); this.statements = statements; } execute(context) { const childContext = context.createChildContext(); let returnValue; for (const stmt of this.statements) { returnValue = stmt.execute(childContext); } return returnValue; } inferType(env) { const childEnv = env.createChild(); let type = "void"; for (const stmt of this.statements) { type = stmt.inferType(childEnv); } return type; } } class IfNode extends ASTNode { condition; thenBranch; elseBranch; constructor(condition, thenBranch, elseBranch) { super(); this.condition = condition; this.thenBranch = thenBranch; this.elseBranch = elseBranch; } execute(context) { const cond = this.condition.execute(context); if (typeof cond !== "boolean") throw new Error("Condition must be boolean"); const branch = cond ? this.thenBranch : this.elseBranch ?? []; let result; const childCtx = context.createChildContext(); for (const stmt of branch) { result = stmt.execute(childCtx); } return result; } inferType(env) { const condType = this.condition.inferType(env); if (condType !== "boolean") throw new Error("Condition must be boolean"); const thenEnv = env.createChild(); const elseEnv = env.createChild(); this.thenBranch.forEach((stmt) => stmt.inferType(thenEnv)); this.elseBranch?.forEach((stmt) => stmt.inferType(elseEnv)); return "void"; } } class SignalNode extends ASTNode { signal; constructor(signal) { super(); this.signal = signal; } execute(context) { if (this.signal === "break") { context.breakSignal = true; } else if (this.signal === "continue") { context.continueSignal = true; } } inferType(env) { return "void"; } } class WhileNode extends ASTNode { condition; body; constructor(condition, body) { super(); this.condition = condition; this.body = body; } execute(context) { let result; while (true) { const cond = this.condition.execute(context); if (typeof cond !== "boolean") throw new Error("Condition must be boolean"); if (!cond) break; const loopCtx = context.createChildContext(); try { for (const stmt of this.body) { result = stmt.execute(context); } } catch (e) { if (e instanceof ContinueSignal) { continue; } if (e instanceof BreakSignal) { break; } throw e; } if (loopCtx.breakSignal) return result; } return result; } inferType(env) { const condType = this.condition.inferType(env); if (condType !== "boolean") throw new Error("Condition must be boolean"); const bodyEnv = env.createChild(); this.body.forEach((stmt) => stmt.inferType(bodyEnv)); return "void"; } } class ForNode extends ASTNode { init; condition; update; body; constructor(init, condition, update, body) { super(); this.init = init; this.condition = condition; this.update = update; this.body = body; } execute(context) { this.init.execute(context); let result; while (true) { const cond = this.condition.execute(context); if (typeof cond !== "boolean") throw new Error("Condition must be boolean"); if (!cond) break; const loopCtx = context.createChildContext(); try { for (const stmt of this.body) { result = stmt.execute(loopCtx); } this.update.execute(context); } catch (e) { if (e instanceof ContinueSignal) { this.update.execute(context); continue; } if (e instanceof BreakSignal) { break; } throw e; } } return result; } inferType(env) { this.init.inferType(env); const condType = this.condition.inferType(env); if (condType !== "boolean") throw new Error("Condition must be boolean"); this.update.inferType(env); const bodyEnv = env.createChild(); this.body.forEach((stmt) => stmt.inferType(bodyEnv)); return "void"; } } class SwitchNode extends ASTNode { expression; cases; defaultCase; constructor(expression, cases, defaultCase) { super(); this.expression = expression; this.cases = cases; this.defaultCase = defaultCase; } execute(context) { const value = this.expression.execute(context); let result; for (const caseBlock of this.cases) { if (value === caseBlock.match.execute(context)) { const childCtx = context.createChildContext(); for (const stmt of caseBlock.body) { result = stmt.execute(childCtx); } return result; } } if (this.defaultCase) { const childCtx = context.createChildContext(); for (const stmt of this.defaultCase) { result = stmt.execute(childCtx); } } return result; } inferType(env) { const exprType = this.expression.inferType(env); for (const { match, body } of this.cases) { const matchType = match.inferType(env); if (!compareTypes(exprType, matchType)) { throw new Error("Switch case type mismatch with expression"); } const caseEnv = env.createChild(); body.forEach((stmt) => stmt.inferType(caseEnv)); } if (this.defaultCase) { const defaultEnv = env.createChild(); this.defaultCase.forEach((stmt) => stmt.inferType(defaultEnv)); } return "void"; } } class DoWhileNode extends ASTNode { body; condition; constructor(body, condition) { super(); this.body = body; this.condition = condition; } execute(context) { let result; do { const childCtx = context.createChildContext(); try { for (const stmt of this.body) { result = stmt.execute(childCtx); } } catch (e) { if (e instanceof ContinueSignal) { continue; } if (e instanceof BreakSignal) { break; } throw e; } if (childCtx.breakSignal) break; } while (this.condition.execute(context)); return result; } inferType(env) { const childEnv = env.createChild(); for (const stmt of this.body) stmt.inferType(childEnv); const condType = this.condition.inferType(env); if (condType !== "boolean") throw new Error("Condition must be boolean"); return "void"; } } class ForEachNode extends ASTNode { itemName; iterable; body; constructor(itemName, iterable, body) { super(); this.itemName = itemName; this.iterable = iterable; this.body = body; } execute(context) { const array = this.iterable.execute(context); if (!Array.isArray(array)) throw new Error("Target is not iterable"); let result; for (const item of array) { const loopCtx = context.createChildContext(); loopCtx.setVariable(this.itemName, item); try { for (const stmt of this.body) { result = stmt.execute(loopCtx); } } catch (e) { if (e instanceof ContinueSignal) { continue; } if (e instanceof BreakSignal) { break; } throw e; } } return result; } inferType(env) { const iterableType = this.iterable.inferType(env); if (typeof iterableType !== "object" || iterableType.kind !== "array") { throw new Error("Target of ForEach must be an array"); } const childEnv = env.createChild(); childEnv.setType(this.itemName, iterableType.elementType); for (const stmt of this.body) stmt.inferType(childEnv); return "void"; } } class TryCatchNode extends ASTNode { tryBlock; catchVar; catchBlock; constructor(tryBlock, catchVar, catchBlock) { super(); this.tryBlock = tryBlock; this.catchVar = catchVar; this.catchBlock = catchBlock; } execute(context) { const tryCtx = context.createChildContext(); try { let result; for (const stmt of this.tryBlock) { result = stmt.execute(tryCtx); } return result; } catch (err) { if (err instanceof ReturnSignal || err instanceof BreakSignal || err instanceof ContinueSignal) { throw err; } const catchCtx = context.createChildContext(); catchCtx.setVariable(this.catchVar, err); let catchResult; for (const stmt of this.catchBlock) { catchResult = stmt.execute(catchCtx); } return catchResult; } } inferType(env) { const tryEnv = env.createChild(); const catchEnv = env.createChild(); this.tryBlock.forEach((stmt) => stmt.inferType(tryEnv)); catchEnv.setType(this.catchVar, "any"); this.catchBlock.forEach((stmt) => stmt.inferType(catchEnv)); return "void"; } } class AddressOfNode extends ASTNode { target; constructor(target) { super(); this.target = target; } execute(ctx) { if (typeof this.target.toReference === "function") { return this.target.toReference(ctx); } throw new Error(`Cannot take address of non‐lvalue: ${this.target}`); } inferType(env) { const t = this.target.inferType(env); return { kind: "pointer", to: t }; } } class DereferenceNode extends ASTNode { ptrExpr; constructor(ptrExpr) { super(); this.ptrExpr = ptrExpr; } execute(ctx) { const ptr = this.ptrExpr.execute(ctx); if (!ptr?.__isPtr) { throw new Error(`Cannot dereference non‐pointer: ${ptr}`); } return ptr.ctx.getVariable(ptr.name); } inferType(env) { const ptrType = this.ptrExpr.inferType(env); if (typeof ptrType === "object" && ptrType.kind === "pointer") { return ptrType.to; } throw new Error(`Type error: tried to dereference non‐pointer ${typeToString(ptrType)}`); } } class PointerAssignmentNode extends ASTNode { ptrExpr; valueExpr; constructor(ptrExpr, valueExpr) { super(); this.ptrExpr = ptrExpr; this.valueExpr = valueExpr; } execute(ctx) { const ref = this.ptrExpr.execute(ctx); if (!ref.__isPtr) throw new Error(`Not a pointer`); const val = this.valueExpr.execute(ctx); ref.set(val); return val; } inferType(env) { const ptrT = this.ptrExpr.inferType(env); if (typeof ptrT === "object" && ptrT.kind === "pointer") { const vT = this.valueExpr.inferType(env); if (!compareTypes(ptrT.to, vT)) { throw new Error(`Type mismatch in pointer assignment`); } return vT; } throw new Error(`Not pointer type`); } } class Interpreter { nodes; constructor(Nodes) { this.nodes = Nodes; } execute() { const context = new ExecutionContext; this.nodes.forEach((node) => { node.execute(context); }); return context; } } var App = (nodes, contextFn) => { const context = new Interpreter(nodes).execute(); if (contextFn) return contextFn(context); }; export { typeToString, compareTypes, WhileNode, VariableReferenceNode, VariableDeclarationNode, TypeEnvironment, TryCatchNode, SwitchNode, SignalNode, ReturnSignal, ReturnNode, PropertyAccessNode, PointerAssignmentNode, ParameterNode, OperatorNode, ObjectLiteralNode, MethodCallNode, LiteralNode, Interpreter, IndexAssignmentNode, IndexAccessNode, IfNode, FunctionLiteralNode, FunctionCallNode, ForNode, ForEachNode, ExecutionContext, ErrorNode, DoWhileNode, DereferenceNode, CustomASTNode, ContinueSignal, ContinueNode, BreakSignal, BreakNode, BlockNode, AssignmentNode, ArrowFunctionNode, ArrayLiteralNode, App, AddressOfNode, ASTNode };