intrear
Version:
Next-gen way to create your own programming language!
1,383 lines (1,344 loc) • 36.6 kB
JavaScript
// 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
};