scraggy
Version:
A safe JavaScript expression evaluator
1,428 lines (1,418 loc) • 63.9 kB
JavaScript
// 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;
}