UNPKG

scraggy

Version:

A safe JavaScript expression evaluator

1,428 lines (1,418 loc) 63.9 kB
// src/evaluator/index.ts import * as acorn from "acorn"; // src/evaluator/functions.ts var functionMap = /* @__PURE__ */ new WeakMap(); function createWrappedFunction(runtimeFunc) { const wrapper = async (...args) => { return runtimeFunc.call(null, args); }; functionMap.set(wrapper, runtimeFunc); return wrapper; } function getRuntimeFunction(func) { return functionMap.get(func); } // src/memory.ts var MemoryTracker = class _MemoryTracker { static instance; activeScopes = /* @__PURE__ */ new Set(); activeFunctions = /* @__PURE__ */ new Set(); constructor() { } static getInstance() { if (!_MemoryTracker.instance) { _MemoryTracker.instance = new _MemoryTracker(); } return _MemoryTracker.instance; } trackScope(scope) { this.activeScopes.add(scope); } untrackScope(scope) { this.activeScopes.delete(scope); } trackFunction(func) { this.activeFunctions.add(func); } untrackFunction(func) { this.activeFunctions.delete(func); } getStats() { return { activeScopes: this.activeScopes.size, activeFunctions: this.activeFunctions.size }; } reset() { this.activeScopes.clear(); this.activeFunctions.clear(); } }; // src/runtime.ts var ReturnValue = class _ReturnValue extends Error { constructor(value) { super("ReturnValue"); this.value = value; Object.setPrototypeOf(this, _ReturnValue.prototype); } }; var BreakValue = class _BreakValue extends Error { constructor(label) { super("BreakValue"); this.label = label; Object.setPrototypeOf(this, _BreakValue.prototype); } }; var ContinueValue = class _ContinueValue extends Error { constructor(label) { super("ContinueValue"); this.label = label; Object.setPrototypeOf(this, _ContinueValue.prototype); } }; var RuntimeFunction = class { constructor(params, body, scope, evaluator, isAsync = false) { this.params = params; this.body = body; this.scope = scope; this.evaluator = evaluator; this.isAsync = isAsync; this.scope.addRef(); this.ownerScope = scope; MemoryTracker.getInstance().trackFunction(this); this.ownerScope.trackFunction(this); } ownerScope; destroyed = false; destroy() { if (this.destroyed) return; this.destroyed = true; this.scope.release(); MemoryTracker.getInstance().untrackFunction(this); this.ownerScope.untrackFunction(this); } /** * Process a destructuring pattern for function parameters * @param pattern The destructuring pattern (ObjectPattern or ArrayPattern) * @param value The value to destructure * @param scope The scope to define variables in */ async processDestructuringParameter(pattern, value, scope) { if (pattern.type === "ObjectPattern") { if (value === null || typeof value !== "object") { throw new TypeError("Cannot destructure non-object in function parameter"); } for (const property of pattern.properties) { if (property.type === "RestElement") { if (property.argument.type !== "Identifier") { throw new Error("Rest element must be an identifier in object destructuring"); } const restObj = { ...value }; for (const otherProp of pattern.properties) { if (otherProp !== property && otherProp.type === "Property") { const key = otherProp.key.type === "Identifier" ? otherProp.key.name : otherProp.key.type === "Literal" ? String(otherProp.key.value) : void 0; if (key) { delete restObj[key]; } } } scope.define(property.argument.name, restObj); } else if (property.type === "Property") { let key; if (property.key.type === "Identifier") { key = property.key.name; } else if (property.key.type === "Literal") { key = String(property.key.value); } else { throw new Error("Unsupported property key type in object destructuring"); } if (property.value.type === "Identifier") { scope.define(property.value.name, value[key]); } else if (property.value.type === "ObjectPattern" || property.value.type === "ArrayPattern") { await this.processDestructuringParameter(property.value, value[key], scope); } else { throw new Error("Unsupported property value type in object destructuring"); } } } } else if (pattern.type === "ArrayPattern") { if (!Array.isArray(value)) { throw new TypeError("Cannot destructure non-array in function parameter"); } for (let i = 0; i < pattern.elements.length; i++) { const element = pattern.elements[i]; if (!element) continue; if (element.type === "Identifier") { scope.define(element.name, value[i]); } else if (element.type === "RestElement") { if (element.argument.type !== "Identifier") { throw new Error("Rest element must be an identifier in array destructuring"); } const restValue = value.slice(i); scope.define(element.argument.name, restValue); break; } else if (element.type === "ObjectPattern" || element.type === "ArrayPattern") { await this.processDestructuringParameter(element, value[i], scope); } else { throw new Error("Unsupported element type in array destructuring"); } } } else { throw new Error(`Unsupported destructuring pattern type: ${pattern.type}`); } } async call(thisArg, args) { if (this.destroyed) { throw new Error("Cannot call destroyed function"); } const functionScope = new Scope(this.scope); try { if (thisArg !== null && thisArg !== void 0) { functionScope.define("this", thisArg); } for (let i = 0; i < this.params.length; i++) { const param = this.params[i]; if (param.isRest) { const restArgs = args.slice(i); functionScope.define(param.name, restArgs); break; } else if (param.isDestructuring && param.destructuringPattern) { await this.processDestructuringParameter( param.destructuringPattern, args[i], functionScope ); } else { functionScope.define(param.name, args[i]); } } try { const result = await this.evaluator(this.body, functionScope); return this.isAsync ? await result : result; } catch (e) { if (e instanceof ReturnValue) { const value = e.value; return this.isAsync ? await value : value; } throw e; } } finally { functionScope.release(); } } }; // src/scope.ts var Scope = class { variables; parent; children; refCount; functions; constructor(parentScope = null, initial = {}) { this.variables = new Map(Object.entries(initial)); this.parent = parentScope; this.children = /* @__PURE__ */ new Set(); this.functions = /* @__PURE__ */ new Set(); this.refCount = 1; if (parentScope) { parentScope.addChild(this); } MemoryTracker.getInstance().trackScope(this); } getParent() { return this.parent; } defineFromObject(scope) { for (const key in scope) { this.define(key, scope[key]); } } trackFunction(func) { this.functions.add(func); } untrackFunction(func) { this.functions.delete(func); } addRef() { this.refCount++; } release() { this.refCount--; if (this.refCount === 0) { this.cleanup(); } } addChild(child) { this.children.add(child); this.addRef(); } removeChild(child) { if (this.children.delete(child)) { this.release(); } } cleanup() { for (const value of this.variables.values()) { if (value instanceof RuntimeFunction) { value.destroy(); } else if (value && typeof value === "function") { const runtimeFunc = getRuntimeFunction(value); if (runtimeFunc) { runtimeFunc.destroy(); } } } this.variables.clear(); for (const func of this.functions) { func.destroy(); } this.functions.clear(); if (this.parent) { this.parent.removeChild(this); } for (const child of this.children) { child.release(); } this.children.clear(); MemoryTracker.getInstance().untrackScope(this); } define(name, value) { const oldValue = this.variables.get(name); if (oldValue instanceof RuntimeFunction) { oldValue.destroy(); } else if (oldValue && typeof oldValue === "function") { const runtimeFunc = getRuntimeFunction(oldValue); if (runtimeFunc) { runtimeFunc.destroy(); } } this.variables.set(name, value); if (value instanceof RuntimeFunction) { this.trackFunction(value); } else if (value && typeof value === "function") { const runtimeFunc = getRuntimeFunction(value); if (runtimeFunc) { this.trackFunction(runtimeFunc); } } } assign(name, value) { if (this.variables.has(name)) { const oldValue = this.variables.get(name); if (oldValue instanceof RuntimeFunction) { oldValue.destroy(); } else if (oldValue && typeof oldValue === "function") { const runtimeFunc = getRuntimeFunction(oldValue); if (runtimeFunc) { runtimeFunc.destroy(); } } this.variables.set(name, value); if (value instanceof RuntimeFunction) { this.trackFunction(value); } else if (value && typeof value === "function") { const runtimeFunc = getRuntimeFunction(value); if (runtimeFunc) { this.trackFunction(runtimeFunc); } } return true; } if (this.parent) { return this.parent.assign(name, value); } return false; } lookup(name) { if (this.variables.has(name)) { return this.variables.get(name); } if (this.parent) { return this.parent.lookup(name); } return void 0; } getVariables() { return this.variables; } }; // src/evaluator/classes.ts async function evaluateClassDefinition(node, scope, evaluateNode2, currentClassContext, setCurrentClassContext) { let superClass = null; if (node.superClass) { superClass = await evaluateNode2(node.superClass, scope); if (typeof superClass !== "function") { throw new TypeError("Class extends value is not a constructor"); } } const createClass = (constructorFn) => { let constructor; if (constructorFn) { constructor = function(...args) { return constructorFn.apply(this, args); }; } else { if (superClass) { constructor = function(...args) { setCurrentClassContext({ thisObj: this, superClass }); try { superClass.apply(this, args); } finally { setCurrentClassContext(null); } }; } else { constructor = function() { }; } } if (superClass) { Object.setPrototypeOf(constructor.prototype, superClass.prototype); Object.setPrototypeOf(constructor, superClass); } return constructor; }; let constructorMethod = null; const staticMethods = {}; const instanceMethods = {}; for (const element of node.body.body) { if (element.type !== "MethodDefinition") { throw new Error(`Unsupported class element type: ${element.type}`); } if (element.key.type === "PrivateIdentifier") { throw new Error("Private class elements are not supported"); } let methodName; if (element.key.type === "Identifier") { methodName = element.key.name; } else if (element.key.type === "Literal") { methodName = String(element.key.value); } else { throw new Error(`Unsupported method key type: ${element.key.type}`); } if (element.value.type !== "FunctionExpression") { throw new Error(`Class methods must be function expressions, got ${element.value.type}`); } const methodParams = element.value.params.map((param, index) => { if (param.type === "Identifier") { return { name: param.name, isRest: false, isDestructuring: false }; } else if (param.type === "RestElement" && param.argument.type === "Identifier") { return { name: param.argument.name, isRest: true, isDestructuring: false }; } else if (param.type === "ObjectPattern") { return { name: `arg${index}`, isRest: false, isDestructuring: true, destructuringPattern: param }; } else if (param.type === "ArrayPattern") { return { name: `arg${index}`, isRest: false, isDestructuring: true, destructuringPattern: param }; } else { throw new Error(`Unsupported parameter type in class method: ${param.type}`); } }); const isConstructor = element.kind === "constructor"; const isAsync = element.value.async; if (isConstructor) { const runtimeFunc = new RuntimeFunction( methodParams, element.value.body, scope, evaluateNode2, isAsync ); constructorMethod = function(...args) { const methodScope = new Scope(scope); if (this === void 0 || this === null) { throw new Error('Constructor called without a valid "this" context'); } methodScope.define("this", this); methodScope.define("super", superClass); setCurrentClassContext({ thisObj: this, superClass }); try { const result = runtimeFunc.call(this, args); if (result !== null && typeof result === "object") { return result; } return this; } finally { setCurrentClassContext(null); methodScope.release(); } }; } else { const runtimeFunc = new RuntimeFunction( methodParams, element.value.body, scope, evaluateNode2, isAsync ); const methodFunction = function(...args) { if (this === void 0) { throw new Error('Method called without a proper "this" binding'); } const methodScope = new Scope(scope); methodScope.define("this", this); methodScope.define("super", superClass); setCurrentClassContext({ thisObj: this, superClass }); try { return runtimeFunc.call(this, args); } finally { setCurrentClassContext(null); methodScope.release(); } }; if (element.static) { staticMethods[methodName] = methodFunction; } else { instanceMethods[methodName] = methodFunction; } } } const classConstructor = createClass(constructorMethod); for (const [name, method] of Object.entries(instanceMethods)) { classConstructor.prototype[name] = method; } Object.assign(classConstructor, staticMethods); return classConstructor; } // src/evaluator/utils.ts function ensure(value, check, errorMessage) { if (!check(value)) { throw new Error(errorMessage); } return value; } function formatError(script, error, location) { const lines = script.split("\n"); if (!location) { if (error instanceof SyntaxError) { const locMatch = error.message.match(/\((\d+):(\d+)\)/); if (locMatch) { location = { line: parseInt(locMatch[1], 10), column: parseInt(locMatch[2], 10) }; } else { return error; } } else { return error; } } const lineNumber = location.line; const columnNumber = location.column; let formattedError = `${error.name}: ${error.message} `; const startLine = Math.max(0, lineNumber - 2); const endLine = Math.min(lines.length - 1, lineNumber); for (let i = startLine; i <= endLine; i++) { const isErrorLine = i === lineNumber - 1; const lineNum = String(i + 1).padStart(4, " "); const line = i < lines.length ? lines[i] : ""; formattedError += `${lineNum} | ${line} `; if (isErrorLine) { const caretPadding = " ".repeat(columnNumber + 7); formattedError += `${caretPadding}^ here `; } } const ErrorConstructor = error.constructor; try { return new ErrorConstructor(formattedError); } catch (e) { return new Error(formattedError); } } function isModuleDeclaration(statement) { return statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" || statement.type === "ExportAllDeclaration"; } // src/evaluator/expressions.ts async function evaluateBinaryExpression(node, scope, evaluateNode2) { const [leftValue, rightValue] = await Promise.all([ evaluateNode2( ensure( node.left, (val) => val.type !== "PrivateIdentifier", "PrivateIdentifier is not supported for the left of a binary expression" ), scope ), evaluateNode2(node.right, scope) ]); switch (node.operator) { case "+": return leftValue + rightValue; case "-": return leftValue - rightValue; case "*": return leftValue * rightValue; case "/": return leftValue / rightValue; case "%": return leftValue % rightValue; case "**": return leftValue ** rightValue; case "&": return leftValue & rightValue; case "|": return leftValue | rightValue; case "^": return leftValue ^ rightValue; case "<<": return leftValue << rightValue; case ">>": return leftValue >> rightValue; case ">>>": return leftValue >>> rightValue; case "==": return leftValue == rightValue; case "!=": return leftValue != rightValue; case "===": return leftValue === rightValue; case "!==": return leftValue !== rightValue; case "<": return leftValue < rightValue; case "<=": return leftValue <= rightValue; case ">": return leftValue > rightValue; case ">=": return leftValue >= rightValue; default: throw new Error(`Unsupported binary operator: ${node.operator}`); } } async function evaluateUnaryExpression(node, scope, evaluateNode2) { const argument = await evaluateNode2(node.argument, scope); switch (node.operator) { case "+": return +argument; case "-": return -argument; case "!": return !argument; case "~": return ~argument; case "typeof": return typeof argument; default: throw new Error(`Unsupported unary operator: ${node.operator}`); } } async function evaluateMemberExpression(node, scope, evaluateNode2, currentClassContext) { if (node.object.type === "Super") { if (!currentClassContext) { throw new Error("Super reference is not properly bound to a class method"); } const { thisObj, superClass } = currentClassContext; if (!superClass) { throw new Error("Cannot use super in a class with no superclass"); } if (node.computed) { const propertyExpr = ensure( node.property, (val) => val.type !== "PrivateIdentifier", "PrivateIdentifier is not supported in computed MemberExpression" ); const property = await evaluateNode2(propertyExpr, scope); const method = Object.getPrototypeOf(thisObj.constructor.prototype)[property]; if (typeof method === "function") { return method.bind(thisObj); } return method; } else { if (node.property.type !== "Identifier") { throw new Error("Unsupported property type in Super MemberExpression"); } const propName = node.property.name; const method = Object.getPrototypeOf(thisObj.constructor.prototype)[propName]; if (typeof method === "function") { return method.bind(thisObj); } return method; } } else { const objectExpr = node.object; const object = await evaluateNode2(objectExpr, scope); if (node.computed) { const propertyExpr = ensure( node.property, (val) => val.type !== "PrivateIdentifier", "PrivateIdentifier is not supported in computed MemberExpression" ); const property = await evaluateNode2(propertyExpr, scope); const propValue = object[property]; if (typeof propValue === "function" && !propValue.hasOwnProperty("prototype") && object !== null && object !== void 0) { return propValue.bind(object); } return propValue; } else { if (node.property.type !== "Identifier") { throw new Error("Unsupported property type in MemberExpression"); } if (object === void 0 || object === null) { throw new TypeError( `Cannot read property '${node.property.name}' of ${object === void 0 ? "undefined" : "null"}` ); } const propName = node.property.name; let propValue = object[propName]; if (typeof propValue === "function" && object !== null && object !== void 0) { const boundMethod = propValue.bind(object); return boundMethod; } return propValue; } } } async function evaluateObjectExpression(node, scope, evaluateNode2) { const result = {}; for (const prop of node.properties) { if (prop.type === "Property") { let key; if (prop.computed) { key = await evaluateNode2(prop.key, scope); } else if (prop.key.type === "Identifier") { key = prop.key.name; } else if (prop.key.type === "Literal") { key = String(prop.key.value); } else { throw new Error("Unsupported object property key type: " + prop.key.type); } let value; if (prop.shorthand && prop.key.type === "Identifier") { value = scope.lookup(prop.key.name); } else { value = await evaluateNode2(prop.value, scope); } if (prop.method && prop.value.type === "FunctionExpression") { } if (typeof key === "symbol") { result[key] = value; } else { result[key] = value; } } else { const spreadValue = await evaluateNode2(prop.argument, scope); if (spreadValue !== null && typeof spreadValue === "object") { Object.assign(result, spreadValue); } else { throw new Error("Spread element in object must be an object"); } } } return result; } async function evaluateArrayExpression(node, scope, evaluateNode2) { const result = []; for (const element of node.elements) { if (!element) { result.push(void 0); } else if (element.type === "SpreadElement") { const spreadElements = await evaluateNode2(element.argument, scope); if (Array.isArray(spreadElements)) { result.push(...spreadElements); } else { throw new Error("Spread element is not iterable"); } } else { const value = await evaluateNode2(element, scope); if (Array.isArray(value) && element.type === "ArrayExpression") { result.push(value); } else { result.push(value); } } } return result; } async function evaluateLogicalExpression(node, scope, evaluateNode2) { const leftValue = await evaluateNode2(node.left, scope); switch (node.operator) { case "&&": return leftValue ? await evaluateNode2(node.right, scope) : leftValue; case "||": return leftValue ? leftValue : await evaluateNode2(node.right, scope); case "??": return leftValue !== null && leftValue !== void 0 ? leftValue : await evaluateNode2(node.right, scope); default: throw new Error(`Unsupported logical operator: ${node.operator}`); } } // src/evaluator/nodes.ts var labelStack = []; function getCurrentLabel() { return labelStack.length > 0 ? labelStack[labelStack.length - 1] : void 0; } async function evaluateNode(node, scope, currentClassContext, setCurrentClassContext) { const evaluate2 = (n, s) => { return evaluateNode(n, s, currentClassContext, setCurrentClassContext); }; switch (node.type) { case "ExpressionStatement": return evaluate2(node.expression, scope); case "UpdateExpression": { const argument = node.argument; if (argument.type === "Identifier") { const name = argument.name; let value = scope.lookup(name); if (value === void 0 && !scope.lookup(name)) { throw new Error(`Reference Error: ${name} is not defined`); } if (node.prefix) { value = node.operator === "++" ? value + 1 : value - 1; scope.assign(name, value); return value; } else { const oldValue = value; value = node.operator === "++" ? value + 1 : value - 1; scope.assign(name, value); return oldValue; } } else if (argument.type === "MemberExpression") { const obj = await evaluate2(argument.object, scope); const prop = argument.computed ? await evaluate2(argument.property, scope) : argument.property.name; if (obj === void 0 || obj === null) { throw new TypeError(`Cannot update property '${prop}' of ${obj}`); } let value = obj[prop]; if (node.prefix) { value = node.operator === "++" ? value + 1 : value - 1; obj[prop] = value; return value; } else { const oldValue = value; value = node.operator === "++" ? value + 1 : value - 1; obj[prop] = value; return oldValue; } } else { throw new Error(`Unsupported update expression argument: ${argument.type}`); } } case "BlockStatement": { const blockScope = new Scope(scope); try { let blockResult; for (const statement of node.body) { blockResult = await evaluate2(statement, blockScope); } return blockResult; } finally { blockScope.release(); } } case "AwaitExpression": { const awaitValue = await evaluate2(node.argument, scope); return await awaitValue; } case "VariableDeclaration": for (const declarator of node.declarations) { const initValue = declarator.init ? await evaluate2(declarator.init, scope) : void 0; if (declarator.id.type === "Identifier") { scope.define(declarator.id.name, initValue); } else if (declarator.id.type === "ObjectPattern") { if (initValue === null || typeof initValue !== "object") { throw new TypeError("Cannot destructure non-object"); } for (const property of declarator.id.properties) { if (property.type === "RestElement") { if (property.argument.type !== "Identifier") { throw new Error("Rest element must be an identifier in object destructuring"); } const restObj = { ...initValue }; for (const otherProp of declarator.id.properties) { if (otherProp !== property && otherProp.type === "Property") { const key = otherProp.key.type === "Identifier" ? otherProp.key.name : otherProp.key.type === "Literal" ? String(otherProp.key.value) : void 0; if (key) { delete restObj[key]; } } } scope.define(property.argument.name, restObj); } else if (property.type === "Property") { let key; let value; if (property.key.type === "Identifier") { key = property.key.name; } else if (property.key.type === "Literal") { key = String(property.key.value); } else { throw new Error("Unsupported property key type in object destructuring"); } if (property.value.type === "Identifier") { value = initValue[key]; scope.define(property.value.name, value); } else if (property.value.type === "ObjectPattern") { const nestedObj = initValue[key]; if (nestedObj === null || typeof nestedObj !== "object") { throw new TypeError(`Cannot destructure non-object property ${key}`); } for (const nestedProp of property.value.properties) { if (nestedProp.type !== "Property" || nestedProp.value.type !== "Identifier") { throw new Error( "Nested object destructuring with non-identifier values not supported" ); } let nestedKey; if (nestedProp.key.type === "Identifier") { nestedKey = nestedProp.key.name; } else if (nestedProp.key.type === "Literal") { nestedKey = String(nestedProp.key.value); } else { throw new Error("Unsupported property key type in nested object destructuring"); } scope.define(nestedProp.value.name, nestedObj[nestedKey]); } } else if (property.value.type === "ArrayPattern") { const nestedArr = initValue[key]; if (!Array.isArray(nestedArr)) { throw new TypeError(`Cannot destructure non-array property ${key}`); } for (let i = 0; i < property.value.elements.length; i++) { const element = property.value.elements[i]; if (!element) continue; if (element.type === "Identifier") { scope.define(element.name, nestedArr[i]); } else { throw new Error( "Nested array destructuring with non-identifier elements not supported" ); } } } else { throw new Error("Unsupported property value type in object destructuring"); } } } } else if (declarator.id.type === "ArrayPattern") { if (!Array.isArray(initValue)) { throw new TypeError("Cannot destructure non-array"); } for (let i = 0; i < declarator.id.elements.length; i++) { const element = declarator.id.elements[i]; if (!element) continue; if (element.type === "Identifier") { scope.define(element.name, initValue[i]); } else if (element.type === "RestElement") { if (element.argument.type !== "Identifier") { throw new Error("Rest element must be an identifier in array destructuring"); } const restValue = initValue.slice(i); scope.define(element.argument.name, restValue); break; } else if (element.type === "ObjectPattern") { const nestedObj = initValue[i]; if (nestedObj === null || typeof nestedObj !== "object") { throw new TypeError(`Cannot destructure non-object at index ${i}`); } for (const prop of element.properties) { if (prop.type !== "Property" || prop.value.type !== "Identifier") { throw new Error( "Nested object destructuring with non-identifier values not supported" ); } let key; if (prop.key.type === "Identifier") { key = prop.key.name; } else if (prop.key.type === "Literal") { key = String(prop.key.value); } else { throw new Error("Unsupported property key type in nested object destructuring"); } scope.define(prop.value.name, nestedObj[key]); } } else if (element.type === "ArrayPattern") { const nestedArr = initValue[i]; if (!Array.isArray(nestedArr)) { throw new TypeError(`Cannot destructure non-array at index ${i}`); } for (let j = 0; j < element.elements.length; j++) { const nestedElement = element.elements[j]; if (!nestedElement) continue; if (nestedElement.type === "Identifier") { scope.define(nestedElement.name, nestedArr[j]); } else { throw new Error("Deeply nested array destructuring not supported"); } } } } } else { throw new Error(`Unsupported variable declaration pattern: ${declarator.id.type}`); } } return void 0; case "FunctionDeclaration": { if (!node.id) throw new Error("Function declaration must have a name"); const funcName = node.id.name; const funcParams = node.params.map((param, index) => { if (param.type === "Identifier") { return { name: param.name, isRest: false, isDestructuring: false }; } else if (param.type === "RestElement" && param.argument.type === "Identifier") { return { name: param.argument.name, isRest: true, isDestructuring: false }; } else if (param.type === "ObjectPattern") { return { name: `arg${index}`, isRest: false, isDestructuring: true, destructuringPattern: param }; } else if (param.type === "ArrayPattern") { return { name: `arg${index}`, isRest: false, isDestructuring: true, destructuringPattern: param }; } else { throw new Error(`Unsupported parameter type: ${param.type}`); } }); const isAsync = node.async; const evalFn = (n, s) => evaluate2(n, s); const runtimeFunc = new RuntimeFunction(funcParams, node.body, scope, evalFn, isAsync); const func = createWrappedFunction(runtimeFunc); scope.define(funcName, func); return void 0; } case "ReturnStatement": { const returnValue = node.argument ? await evaluate2(node.argument, scope) : void 0; throw new ReturnValue(returnValue); } case "BreakStatement": { const labelName = node.label?.name; if (labelName && !labelStack.includes(labelName)) { throw new Error(`Undefined label: ${labelName}`); } throw new BreakValue(labelName); } case "ContinueStatement": { const labelName = node.label?.name; if (labelName && !labelStack.includes(labelName)) { throw new Error(`Undefined label: ${labelName}`); } throw new ContinueValue(labelName); } case "ThrowStatement": { const value = await evaluate2(node.argument, scope); throw value instanceof Error ? value : new Error(String(value)); } case "IfStatement": { const test = await evaluate2(node.test, scope); if (test) { return evaluate2(node.consequent, scope); } else if (node.alternate) { return evaluate2(node.alternate, scope); } return void 0; } case "WhileStatement": { let whileResult; const label = getCurrentLabel(); try { while (await evaluate2(node.test, scope)) { try { whileResult = await evaluate2(node.body, scope); } catch (e) { if (e instanceof ContinueValue) { if (!e.label || e.label === label) { continue; } throw e; } throw e; } } return whileResult; } catch (e) { if (e instanceof BreakValue) { if (!e.label || e.label === label) { return whileResult; } throw e; } throw e; } } case "TryStatement": { try { return await evaluate2(node.block, scope); } catch (error) { if (node.handler) { const catchScope = new Scope(scope); if (node.handler.param?.type === "Identifier") { catchScope.define(node.handler.param.name, error); } return await evaluate2(node.handler.body, catchScope); } throw error; } finally { if (node.finalizer) { await evaluate2(node.finalizer, scope); } } } case "SwitchStatement": { const discriminant = await evaluate2(node.discriminant, scope); const switchScope = new Scope(scope); try { let result; let matched = false; let fallthrough = false; for (let i = 0; i < node.cases.length; i++) { const caseClause = node.cases[i]; if (!caseClause.test) { if (!matched && !fallthrough) { fallthrough = true; } } else { if (!fallthrough) { const testValue = await evaluate2(caseClause.test, switchScope); if (discriminant === testValue) { matched = true; fallthrough = true; } } } if (fallthrough) { try { for (const statement of caseClause.consequent) { result = await evaluate2(statement, switchScope); } } catch (e) { if (e instanceof BreakValue && !e.label) { return result; } throw e; } } } return result; } finally { switchScope.release(); } } case "NewExpression": { const constructor = await evaluate2(node.callee, scope); const flatArgs = []; for (const arg of node.arguments) { if (arg.type === "SpreadElement") { const spreadArg = await evaluate2(arg.argument, scope); if (Array.isArray(spreadArg)) { flatArgs.push(...spreadArg); } else { throw new Error("Spread argument must be an array"); } } else { flatArgs.push(await evaluate2(arg, scope)); } } if (typeof constructor !== "function") { throw new TypeError("Constructor must be a function"); } const instance = Object.create(constructor.prototype); const result = constructor.apply(instance, flatArgs); return result !== null && typeof result === "object" ? result : instance; } case "ArrowFunctionExpression": { const arrowParams = node.params.map((param, index) => { if (param.type === "Identifier") { return { name: param.name, isRest: false, isDestructuring: false }; } else if (param.type === "RestElement" && param.argument.type === "Identifier") { return { name: param.argument.name, isRest: true, isDestructuring: false }; } else if (param.type === "ObjectPattern") { return { name: `arg${index}`, isRest: false, isDestructuring: true, destructuringPattern: param }; } else if (param.type === "ArrayPattern") { return { name: `arg${index}`, isRest: false, isDestructuring: true, destructuringPattern: param }; } else { throw new Error(`Unsupported parameter type: ${param.type}`); } }); const evalFn = (n, s) => evaluate2(n, s); const runtimeFunc = new RuntimeFunction(arrowParams, node.body, scope, evalFn, node.async); return createWrappedFunction(runtimeFunc); } case "AssignmentExpression": { if (node.operator !== "=") { if (node.left.type === "Identifier") { const leftValue = scope.lookup(node.left.name); if (leftValue === void 0 && !scope.lookup(node.left.name)) { throw new Error(`Reference Error: ${node.left.name} is not defined`); } const rightValue = await evaluate2(node.right, scope); let result; switch (node.operator) { case "+=": result = leftValue + rightValue; break; case "-=": result = leftValue - rightValue; break; case "*=": result = leftValue * rightValue; break; case "/=": result = leftValue / rightValue; break; case "%=": result = leftValue % rightValue; break; case "**=": result = leftValue ** rightValue; break; case "&=": result = leftValue & rightValue; break; case "|=": result = leftValue | rightValue; break; case "^=": result = leftValue ^ rightValue; break; case "<<=": result = leftValue << rightValue; break; case ">>=": result = leftValue >> rightValue; break; case ">>>=": result = leftValue >>> rightValue; break; default: throw new Error(`Unsupported compound assignment operator: ${node.operator}`); } if (!scope.assign(node.left.name, result)) { throw new Error(`Cannot assign to undefined variable ${node.left.name}`); } return result; } else if (node.left.type === "MemberExpression") { if (node.left.object.type === "Super") { throw new Error("Super is not supported for compound AssignmentExpression"); } if (node.left.property.type === "PrivateIdentifier") { throw new Error("Private identifiers are not supported for AssignmentExpression"); } const obj = await evaluate2(node.left.object, scope); const prop = node.left.computed ? await evaluate2(node.left.property, scope) : ensure( node.left.property, (val) => val.type === "Identifier", "Expected an identifier in non-computed MemberExpression" ).name; if (obj === void 0 || obj === null) { throw new TypeError( `Cannot read property '${prop}' of ${obj === void 0 ? "undefined" : "null"}` ); } const leftValue = obj[prop]; const rightValue = await evaluate2(node.right, scope); let result; switch (node.operator) { case "+=": result = leftValue + rightValue; break; case "-=": result = leftValue - rightValue; break; case "*=": result = leftValue * rightValue; break; case "/=": result = leftValue / rightValue; break; case "%=": result = leftValue % rightValue; break; case "**=": result = leftValue ** rightValue; break; case "&=": result = leftValue & rightValue; break; case "|=": result = leftValue | rightValue; break; case "^=": result = leftValue ^ rightValue; break; case "<<=": result = leftValue << rightValue; break; case ">>=": result = leftValue >> rightValue; break; case ">>>=": result = leftValue >>> rightValue; break; default: throw new Error(`Unsupported compound assignment operator: ${node.operator}`); } obj[prop] = result; return result; } else { throw new Error("Compound assignment not supported for this target type"); } } else { if (node.left.type === "Identifier") { const assignValue = await evaluate2(node.right, scope); if (!scope.assign(node.left.name, assignValue)) { throw new Error(`Cannot assign to undefined variable ${node.left.name}`); } return assignValue; } else if (node.left.type === "MemberExpression") { const obj = await evaluate2( ensure( node.left.object, (val) => val.type !== "Super", "Super is not supported for AssignmentExpression" ), scope ); if (node.left.property.type === "PrivateIdentifier") { throw new Error("Private identifiers are not supported for AssignmentExpression"); } const prop = node.left.computed ? await evaluate2(node.left.property, scope) : ensure( node.left.property, (val) => val.type === "Identifier", "Expected an identifier in non-computed MemberExpression" ).name; const memberValue = await evaluate2(node.right, scope); if (obj === void 0 || obj === null) { throw new TypeError(`Cannot set property '${prop}' of ${obj}`); } obj[prop] = memberValue; return memberValue; } else if (node.left.type === "ObjectPattern") { const rightValue = await evaluate2(node.right, scope); if (rightValue === null || typeof rightValue !== "object") { throw new TypeError("Cannot destructure non-object in assignment"); } for (const property of node.left.properties) { if (property.type === "RestElement") { if (property.argument.type !== "Identifier") { throw new Error( "Rest element must be an identifier in object destructuring assignment" ); } const restObj = { ...rightValue }; for (const otherProp of node.left.properties) { if (otherProp !== property && otherProp.type === "Property") { const key = otherProp.key.type === "Identifier" ? otherProp.key.name : otherProp.key.type === "Literal" ? String(otherProp.key.value) : void 0; if (key) { delete restObj[key]; } } } if (!scope.assign(property.argument.name, restObj)) { throw new Error(`Cannot assign to undefined variable ${property.argument.name}`); } } else if (property.type === "Property") { let key; if (property.key.type === "Identifier") { key = property.key.name; } else if (property.key.type === "Literal") { key = String(property.key.value); } else { throw new Error("Unsupported property key type in object destructuring assignment"); } if (property.value.type === "Identifier") { const value = rightValue[key]; if (!scope.assign(property.value.name, value)) { throw new Error(`Cannot assign to undefined variable ${property.value.name}`); } } else { throw new Error("Nested destructuring in assignment expressions not supported"); } } } return rightValue; } else if (node.left.type === "ArrayPattern") { const rightValue = await evaluate2(node.right, scope); if (!Array.isArray(rightValue)) { throw new TypeError("Cannot destructure non-array in assignment"); } for (let i = 0; i < node.left.elements.length; i++) { const element = node.left.elements[i]; if (!element) continue; if (element.type === "Identifier") { if (!scope.assign(element.name, rightValue[i])) { throw new Error(`Cannot assign to undefined variable ${element.name}`); } } else if (element.type === "RestElement") { if (element.argument.type !== "Identifier") { throw new Error( "Rest element must be an identifier in array destructuring assignment" ); } const restValue = rightValue.slice(i); if (!scope.assign(element.argument.name, restValue)) { throw new Error(`Cannot assign to undefined variable ${element.argument.name}`); } break; } else { throw new Error("Nested destructuring in assignment expressions not supported"); } } return rightValue; } else { throw new Error(`Unsupported assignment target type: ${node.left.type}`); } } } case "Literal": return node.value; case "Identifier": { if (node.name === "undefined") return void 0; const identValue = scope.lookup(node.name); if (identValue === void 0 && !scope.lookup(node.name)) { throw new Error(`Reference Error: ${node.name} is not defined`); } return identValue; } case "ThisExpression": { const thisValue = scope.lookup("this"); if (thisValue === void 0) { if (currentClassContext) { return currentClassContext.thisObj; }