UNPKG

@prism-lang/core

Version:

A programming language for uncertainty

1,146 lines 142 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Runtime = exports.Interpreter = exports.Environment = exports.PromiseValue = exports.FunctionValue = exports.ObjectValue = exports.ArrayValue = exports.ConfidenceValue = exports.UndefinedValue = exports.NullValue = exports.BooleanValue = exports.StringValue = exports.NumberValue = exports.Value = exports.ReturnException = exports.LoopControlError = exports.RuntimeError = void 0; exports.createRuntime = createRuntime; const ast_1 = require("./ast"); const confidence_1 = require("./confidence"); const context_1 = require("./context"); const llm_types_1 = require("./llm-types"); class RuntimeError extends Error { node; line; column; constructor(message, node, location) { // If location is provided, enhance the error message const enhancedMessage = location ? `Error at line ${location.line}, column ${location.column}: ${message}` : message; super(enhancedMessage); this.node = node; this.name = 'RuntimeError'; if (location) { this.line = location.line; this.column = location.column; } } } exports.RuntimeError = RuntimeError; class LoopControlError extends Error { type; constructor(type) { super(type); this.type = type; this.name = 'LoopControlError'; } } exports.LoopControlError = LoopControlError; class ReturnException extends Error { value; constructor(value) { super('return'); this.value = value; this.name = 'ReturnException'; } } exports.ReturnException = ReturnException; class Value { } exports.Value = Value; class NumberValue extends Value { value; type = 'number'; constructor(value) { super(); this.value = value; } equals(other) { return other instanceof NumberValue && other.value === this.value; } isTruthy() { return this.value !== 0; } toString() { return this.value.toString(); } } exports.NumberValue = NumberValue; class StringValue extends Value { value; type = 'string'; constructor(value) { super(); this.value = value; } equals(other) { return other instanceof StringValue && other.value === this.value; } isTruthy() { return this.value.length > 0; } toString() { return this.value; } } exports.StringValue = StringValue; class BooleanValue extends Value { value; type = 'boolean'; constructor(value) { super(); this.value = value; } equals(other) { return other instanceof BooleanValue && other.value === this.value; } isTruthy() { return this.value; } toString() { return this.value.toString(); } } exports.BooleanValue = BooleanValue; class NullValue extends Value { type = 'null'; value = null; constructor() { super(); } equals(other) { return other instanceof NullValue; } isTruthy() { return false; } toString() { return 'null'; } } exports.NullValue = NullValue; class UndefinedValue extends Value { type = 'undefined'; value = undefined; constructor() { super(); } equals(other) { return other instanceof UndefinedValue; } isTruthy() { return false; } toString() { return 'undefined'; } } exports.UndefinedValue = UndefinedValue; class ConfidenceValue extends Value { value; confidence; type = 'confident'; constructor(value, confidence) { super(); this.value = value; this.confidence = confidence; } equals(other) { return other instanceof ConfidenceValue && other.value.equals(this.value) && other.confidence.equals(this.confidence); } isTruthy() { return this.value.isTruthy(); } toString() { return `${this.value.toString()} (~${this.confidence.toString()})`; } } exports.ConfidenceValue = ConfidenceValue; class ArrayValue extends Value { elements; type = 'array'; value; constructor(elements) { super(); this.elements = elements; this.value = elements; } equals(other) { if (!(other instanceof ArrayValue)) return false; if (this.elements.length !== other.elements.length) return false; return this.elements.every((elem, i) => elem.equals(other.elements[i])); } isTruthy() { return true; } toString() { return `[${this.elements.map(e => e.toString()).join(', ')}]`; } } exports.ArrayValue = ArrayValue; class ObjectValue extends Value { properties; type = 'object'; value; constructor(properties) { super(); this.properties = properties; this.value = properties; } equals(other) { if (!(other instanceof ObjectValue)) return false; if (this.properties.size !== other.properties.size) return false; for (const [key, value] of this.properties) { const otherValue = other.properties.get(key); if (!otherValue || !value.equals(otherValue)) return false; } return true; } isTruthy() { return true; } toString() { const props = Array.from(this.properties.entries()) .filter(([_, v]) => !(v instanceof UndefinedValue)) .map(([k, v]) => `${k}: ${v.toString()}`) .join(', '); return props.length > 0 ? `{ ${props} }` : '{}'; } } exports.ObjectValue = ObjectValue; class FunctionValue extends Value { name; value; arity; type = 'function'; constructor(name, value, arity) { super(); this.name = name; this.value = value; this.arity = arity; } equals(other) { return other instanceof FunctionValue && other.name === this.name; } isTruthy() { return true; } toString() { return `[Function: ${this.name}]`; } } exports.FunctionValue = FunctionValue; class PromiseValue extends Value { value; type = 'promise'; constructor(value) { super(); this.value = value; } equals(_other) { return false; // Promises are never equal } isTruthy() { return true; // Promises are always truthy } toString() { return '[Promise]'; } } exports.PromiseValue = PromiseValue; class Environment { parent; variables = new Map(); constructor(parent) { this.parent = parent; } // Define a variable with mutability info (for const/let) define(name, value, mutable = true, declared = false) { if (this.variables.has(name) && this.variables.get(name).declared) { throw new RuntimeError(`Variable '${name}' already declared in this scope`); } this.variables.set(name, { value, mutable, declared }); } get(name) { if (this.variables.has(name)) { return this.variables.get(name).value; } if (this.parent) { return this.parent.get(name); } throw new RuntimeError(`Undefined variable: ${name}`, undefined, undefined); } set(name, value) { if (this.variables.has(name)) { const varInfo = this.variables.get(name); if (!varInfo.mutable) { throw new RuntimeError(`Cannot assign to const variable '${name}'`); } this.variables.set(name, { ...varInfo, value }); return; } if (this.parent) { try { this.parent.get(name); // Check if exists in parent this.parent.set(name, value); return; } catch { // Variable doesn't exist in parent, create in current scope (legacy behavior) } } // Legacy assignment behavior - creates mutable variables this.variables.set(name, { value, mutable: true, declared: false }); } // Method to get all variables in current scope (for context copying) getAllVariables() { const result = new Map(); for (const [name, info] of this.variables) { result.set(name, info.value); } return result; } } exports.Environment = Environment; class Interpreter { environment; contextManager; llmProviders = new Map(); defaultLLMProvider; constructor() { this.environment = new Environment(); this.contextManager = new context_1.ContextManager(); this.setupBuiltins(); } setupBuiltins() { // Add built-in functions this.environment.define('llm', new FunctionValue('llm', async (args) => { if (args.length === 0) { throw new RuntimeError('llm() requires at least one argument'); } const promptValue = args[0]; let promptString; // Handle different value types for the prompt if (promptValue instanceof StringValue) { promptString = promptValue.value; } else if (promptValue instanceof ConfidenceValue && promptValue.value instanceof StringValue) { // Handle confident string values promptString = promptValue.value.value; } else { throw new RuntimeError('llm() first argument must be a string'); } const provider = this.getDefaultLLMProvider(); if (!provider) { throw new RuntimeError('No LLM provider configured'); } try { const request = new llm_types_1.LLMRequest(promptString); const response = await provider.complete(request); return new ConfidenceValue(new StringValue(response.content), new confidence_1.ConfidenceValue(response.confidence)); } catch (error) { throw new RuntimeError(`LLM call failed: ${error.message}`); } })); // Add array built-in functions this.environment.define('map', new FunctionValue('map', async (args) => { if (args.length !== 2) { throw new RuntimeError('map() requires exactly 2 arguments: array and function'); } const arrayArg = args[0]; const fnArg = args[1]; // Extract array from confident value if needed const array = arrayArg instanceof ConfidenceValue ? arrayArg.value : arrayArg; const confidence = arrayArg instanceof ConfidenceValue ? arrayArg.confidence : new confidence_1.ConfidenceValue(1.0); if (!(array instanceof ArrayValue)) { throw new RuntimeError('First argument to map() must be an array'); } if (!(fnArg instanceof FunctionValue)) { throw new RuntimeError('Second argument to map() must be a function'); } const results = []; for (const element of array.elements) { const result = await fnArg.value([element]); results.push(result); } const resultArray = new ArrayValue(results); return arrayArg instanceof ConfidenceValue ? new ConfidenceValue(resultArray, confidence) : resultArray; })); this.environment.define('filter', new FunctionValue('filter', async (args) => { if (args.length !== 2) { throw new RuntimeError('filter() requires exactly 2 arguments: array and predicate'); } const arrayArg = args[0]; const predicateArg = args[1]; // Extract array from confident value if needed const array = arrayArg instanceof ConfidenceValue ? arrayArg.value : arrayArg; const confidence = arrayArg instanceof ConfidenceValue ? arrayArg.confidence : new confidence_1.ConfidenceValue(1.0); if (!(array instanceof ArrayValue)) { throw new RuntimeError('First argument to filter() must be an array'); } if (!(predicateArg instanceof FunctionValue)) { throw new RuntimeError('Second argument to filter() must be a function'); } const results = []; for (const element of array.elements) { const predicateResult = await predicateArg.value([element]); if (predicateResult.isTruthy()) { results.push(element); } } const resultArray = new ArrayValue(results); return arrayArg instanceof ConfidenceValue ? new ConfidenceValue(resultArray, confidence) : resultArray; })); this.environment.define('reduce', new FunctionValue('reduce', async (args) => { if (args.length < 2 || args.length > 3) { throw new RuntimeError('reduce() requires 2 or 3 arguments: array, reducer, and optional initial value'); } const arrayArg = args[0]; const reducerArg = args[1]; const initialValue = args.length === 3 ? args[2] : undefined; // Extract array from confident value if needed const array = arrayArg instanceof ConfidenceValue ? arrayArg.value : arrayArg; const confidence = arrayArg instanceof ConfidenceValue ? arrayArg.confidence : new confidence_1.ConfidenceValue(1.0); if (!(array instanceof ArrayValue)) { throw new RuntimeError('First argument to reduce() must be an array'); } if (!(reducerArg instanceof FunctionValue)) { throw new RuntimeError('Second argument to reduce() must be a function'); } if (array.elements.length === 0 && initialValue === undefined) { throw new RuntimeError('reduce() of empty array with no initial value'); } let accumulator; let startIndex; if (initialValue !== undefined) { accumulator = initialValue; startIndex = 0; } else { accumulator = array.elements[0]; startIndex = 1; } for (let i = startIndex; i < array.elements.length; i++) { // Only pass index if the reducer expects 3 arguments const args = [accumulator, array.elements[i]]; if (reducerArg.arity === 3) { args.push(new NumberValue(i)); } accumulator = await reducerArg.value(args); } // Preserve confidence if the original array was confident return arrayArg instanceof ConfidenceValue && !(accumulator instanceof ConfidenceValue) ? new ConfidenceValue(accumulator, confidence) : accumulator; })); // Built-in max function this.environment.define('max', new FunctionValue('max', async (args) => { if (args.length === 0) { throw new RuntimeError('max() requires at least one argument'); } let maxVal = null; let maxNum = -Infinity; let maxConfidence = null; for (const arg of args) { let value = arg; let confidence = null; if (arg instanceof ConfidenceValue) { value = arg.value; confidence = arg.confidence; } if (!(value instanceof NumberValue)) { throw new RuntimeError(`max() requires numeric arguments, got ${value.type}`); } if (value.value > maxNum) { maxNum = value.value; maxVal = value; maxConfidence = confidence; } } // Return with confidence if any input had confidence if (maxConfidence && maxVal) { return new ConfidenceValue(maxVal, maxConfidence); } return maxVal; })); // Built-in min function this.environment.define('min', new FunctionValue('min', async (args) => { if (args.length === 0) { throw new RuntimeError('min() requires at least one argument'); } let minVal = null; let minNum = Infinity; let minConfidence = null; for (const arg of args) { let value = arg; let confidence = null; if (arg instanceof ConfidenceValue) { value = arg.value; confidence = arg.confidence; } if (!(value instanceof NumberValue)) { throw new RuntimeError(`min() requires numeric arguments, got ${value.type}`); } if (value.value < minNum) { minNum = value.value; minVal = value; minConfidence = confidence; } } // Return with confidence if any input had confidence if (minConfidence && minVal) { return new ConfidenceValue(minVal, minConfidence); } return minVal; })); // Print function - outputs values to console this.environment.define('print', new FunctionValue('print', async (args) => { const output = args.map(arg => { const value = arg instanceof ConfidenceValue ? arg.value : arg; // Handle confidence display if (arg instanceof ConfidenceValue) { return `${value.toString()} ~> ${arg.confidence.value.toFixed(2)}`; } return value.toString(); }).join(' '); console.log(output); return new UndefinedValue(); })); // Console object with log, warn, error methods const consoleObject = new Map(); consoleObject.set('log', new FunctionValue('log', async (args) => { const output = args.map(arg => { const value = arg instanceof ConfidenceValue ? arg.value : arg; if (arg instanceof ConfidenceValue) { return `${value.toString()} ~> ${arg.confidence.value.toFixed(2)}`; } return value.toString(); }).join(' '); console.log(output); return new UndefinedValue(); })); consoleObject.set('warn', new FunctionValue('warn', async (args) => { const output = args.map(arg => { const value = arg instanceof ConfidenceValue ? arg.value : arg; if (arg instanceof ConfidenceValue) { return `${value.toString()} ~> ${arg.confidence.value.toFixed(2)}`; } return value.toString(); }).join(' '); console.warn(output); return new UndefinedValue(); })); consoleObject.set('error', new FunctionValue('error', async (args) => { const output = args.map(arg => { const value = arg instanceof ConfidenceValue ? arg.value : arg; if (arg instanceof ConfidenceValue) { return `${value.toString()} ~> ${arg.confidence.value.toFixed(2)}`; } return value.toString(); }).join(' '); console.error(output); return new UndefinedValue(); })); // Debug function with more detailed output consoleObject.set('debug', new FunctionValue('debug', async (args) => { const formattedArgs = args.map(arg => { const value = arg instanceof ConfidenceValue ? arg.value : arg; if (arg instanceof ConfidenceValue) { return `${value.toString()} ~> ${arg.confidence.value.toFixed(2)}`; } return value.toString(); }).join(' '); console.debug(`[DEBUG] ${formattedArgs}`); return new UndefinedValue(); })); this.environment.define('console', new ObjectValue(consoleObject)); // Parameterized Primitives - Higher-order functions with configuration // confidence() - Creates confidence-parameterized functions this.environment.define('confidence', new FunctionValue('confidence', async (args) => { if (args.length !== 1) { throw new RuntimeError('confidence() requires exactly one argument: threshold'); } const thresholdArg = args[0]; const threshold = thresholdArg instanceof ConfidenceValue ? thresholdArg.value : thresholdArg; if (!(threshold instanceof NumberValue)) { throw new RuntimeError('confidence() threshold must be a number'); } const thresholdVal = threshold.value; if (thresholdVal < 0 || thresholdVal > 1) { throw new RuntimeError('confidence() threshold must be between 0 and 1'); } // Return a function that applies the threshold return new FunctionValue('confidenceThreshold', async (funcArgs) => { if (funcArgs.length !== 1) { throw new RuntimeError('confidence-configured function requires exactly one argument: function'); } const func = funcArgs[0]; if (!(func instanceof FunctionValue)) { throw new RuntimeError('confidence-configured function requires a function argument'); } // Return a new function that applies confidence threshold to results return new FunctionValue('confidenceWrapper', async (innerArgs) => { const result = await func.value(innerArgs); return new ConfidenceValue(result, new confidence_1.ConfidenceValue(thresholdVal)); }); }); })); // threshold() - Creates threshold-filtering functions this.environment.define('threshold', new FunctionValue('threshold', async (args) => { if (args.length !== 1) { throw new RuntimeError('threshold() requires exactly one argument: minimum confidence'); } const thresholdArg = args[0]; const threshold = thresholdArg instanceof ConfidenceValue ? thresholdArg.value : thresholdArg; if (!(threshold instanceof NumberValue)) { throw new RuntimeError('threshold() requires a number'); } const thresholdVal = threshold.value; // Return a function that filters by confidence threshold return new FunctionValue('thresholdFilter', async (filterArgs) => { if (filterArgs.length !== 1) { throw new RuntimeError('threshold filter requires exactly one argument: array'); } const arrayArg = filterArgs[0]; const array = arrayArg instanceof ConfidenceValue ? arrayArg.value : arrayArg; if (!(array instanceof ArrayValue)) { throw new RuntimeError('threshold filter requires an array'); } const filtered = array.elements.filter(element => { if (element instanceof ConfidenceValue) { return element.confidence.value >= thresholdVal; } // Check if element is an object with confident properties if (element instanceof ObjectValue) { for (const [, value] of element.properties) { if (value instanceof ConfidenceValue && value.confidence.value < thresholdVal) { return false; } } } return true; // Non-confident values pass through }); return new ArrayValue(filtered); }); })); // sortBy() - Creates parameterized sorting functions this.environment.define('sortBy', new FunctionValue('sortBy', async (args) => { if (args.length < 1 || args.length > 2) { throw new RuntimeError('sortBy() requires 1-2 arguments: key [, direction]'); } const keyArg = args[0]; const key = keyArg instanceof ConfidenceValue ? keyArg.value : keyArg; if (!(key instanceof StringValue)) { throw new RuntimeError('sortBy() key must be a string'); } const direction = args.length === 2 ? args[1] : new StringValue('asc'); const dirValue = direction instanceof ConfidenceValue ? direction.value : direction; if (!(dirValue instanceof StringValue)) { throw new RuntimeError('sortBy() direction must be a string'); } const isAscending = dirValue.value === 'asc'; const keyName = key.value; // Return a sorting function return new FunctionValue('sorter', async (sortArgs) => { if (sortArgs.length !== 1) { throw new RuntimeError('sortBy sorter requires exactly one argument: array'); } const arrayArg = sortArgs[0]; const array = arrayArg instanceof ConfidenceValue ? arrayArg.value : arrayArg; if (!(array instanceof ArrayValue)) { throw new RuntimeError('sortBy sorter requires an array'); } // Sort the array by the specified key const sorted = [...array.elements].sort((a, b) => { let aVal = a instanceof ConfidenceValue ? a.value : a; let bVal = b instanceof ConfidenceValue ? b.value : b; // Extract property values if (aVal instanceof ObjectValue && bVal instanceof ObjectValue) { const aProp = aVal.properties.get(keyName); const bProp = bVal.properties.get(keyName); if (!aProp || !bProp) { // Handle missing properties by treating them as undefined aVal = aProp ? (aProp instanceof ConfidenceValue ? aProp.value : aProp) : new UndefinedValue(); bVal = bProp ? (bProp instanceof ConfidenceValue ? bProp.value : bProp) : new UndefinedValue(); } else { aVal = aProp instanceof ConfidenceValue ? aProp.value : aProp; bVal = bProp instanceof ConfidenceValue ? bProp.value : bProp; } } // Compare values let comparison = 0; if (aVal instanceof NumberValue && bVal instanceof NumberValue) { comparison = aVal.value - bVal.value; } else if (aVal instanceof StringValue && bVal instanceof StringValue) { comparison = aVal.value.localeCompare(bVal.value); } else { comparison = aVal.toString().localeCompare(bVal.toString()); } return isAscending ? comparison : -comparison; }); return new ConfidenceValue(new ArrayValue(sorted), new confidence_1.ConfidenceValue(0.95)); }); })); // groupBy() - Creates parameterized grouping functions this.environment.define('groupBy', new FunctionValue('groupBy', async (args) => { if (args.length !== 1) { throw new RuntimeError('groupBy() requires exactly one argument: key or function'); } const keyOrFunc = args[0]; // Return a grouping function return new FunctionValue('grouper', async (groupArgs) => { if (groupArgs.length !== 1) { throw new RuntimeError('groupBy grouper requires exactly one argument: array'); } const arrayArg = groupArgs[0]; const array = arrayArg instanceof ConfidenceValue ? arrayArg.value : arrayArg; if (!(array instanceof ArrayValue)) { throw new RuntimeError('groupBy grouper requires an array'); } const groups = new Map(); for (const element of array.elements) { let groupKey; if (keyOrFunc instanceof StringValue) { // Group by property key const obj = element instanceof ConfidenceValue ? element.value : element; if (obj instanceof ObjectValue) { const prop = obj.properties.get(keyOrFunc.value); groupKey = prop ? prop.toString() : 'undefined'; } else { groupKey = obj.toString(); } } else if (keyOrFunc instanceof FunctionValue) { // Group by function result const result = await keyOrFunc.value([element]); groupKey = result.toString(); } else { throw new RuntimeError('groupBy() requires a string key or function'); } if (!groups.has(groupKey)) { groups.set(groupKey, []); } groups.get(groupKey).push(element); } // Convert to object const resultObj = new Map(); for (const [key, values] of groups) { resultObj.set(key, new ArrayValue(values)); } return new ObjectValue(resultObj); }); })); // debounce() - Creates debounced function wrappers this.environment.define('debounce', new FunctionValue('debounce', async (args) => { if (args.length !== 1) { throw new RuntimeError('debounce() requires exactly one argument: delay in milliseconds'); } const delayArg = args[0]; const delay = delayArg instanceof ConfidenceValue ? delayArg.value : delayArg; if (!(delay instanceof NumberValue)) { throw new RuntimeError('debounce() delay must be a number'); } const delayMs = delay.value; // Return a function that creates debounced versions return new FunctionValue('debouncer', async (debounceArgs) => { if (debounceArgs.length !== 1) { throw new RuntimeError('debounce creator requires exactly one argument: function'); } const func = debounceArgs[0]; if (!(func instanceof FunctionValue)) { throw new RuntimeError('debounce creator requires a function argument'); } let timeoutId = null; let lastResult = new UndefinedValue(); // Return debounced function return new FunctionValue('debouncedFunction', async (innerArgs) => { return new Promise((resolve) => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(async () => { try { lastResult = await func.value(innerArgs); resolve(lastResult); } catch (error) { resolve(new UndefinedValue()); } }, delayMs); }); }); }); })); // Promise built-in functions // Promise.resolve() - Creates a resolved promise const promiseObj = new ObjectValue(new Map()); promiseObj.value.set('resolve', new FunctionValue('Promise.resolve', async (args) => { if (args.length !== 1) { throw new RuntimeError('Promise.resolve() requires exactly one argument'); } // Create a promise that immediately resolves to the value return new PromiseValue(Promise.resolve(args[0])); })); // Promise.reject() - Creates a rejected promise promiseObj.value.set('reject', new FunctionValue('Promise.reject', async (args) => { if (args.length !== 1) { throw new RuntimeError('Promise.reject() requires exactly one argument'); } // Create a promise that immediately rejects (wrapped in StringValue for error message) const errorMsg = args[0] instanceof StringValue ? args[0].value : args[0].toString(); return new PromiseValue(Promise.reject(new StringValue(errorMsg))); })); // Promise.all() - Waits for all promises to resolve promiseObj.value.set('all', new FunctionValue('Promise.all', async (args) => { if (args.length !== 1 || !(args[0] instanceof ArrayValue)) { throw new RuntimeError('Promise.all() requires an array of promises'); } const promises = args[0].value; const results = []; for (const promise of promises) { if (promise instanceof PromiseValue) { results.push(await promise.value); } else { // Non-promise values are treated as already resolved results.push(promise); } } return new ArrayValue(results); })); this.environment.define('Promise', promiseObj); // delay() / sleep() - Utility function to create a delay this.environment.define('delay', new FunctionValue('delay', async (args) => { if (args.length !== 1) { throw new RuntimeError('delay() requires exactly one argument: milliseconds'); } const msArg = args[0]; const ms = msArg instanceof ConfidenceValue ? msArg.value : msArg; if (!(ms instanceof NumberValue)) { throw new RuntimeError('delay() requires a number of milliseconds'); } const delayMs = Math.floor(ms.value); // Return a promise that resolves after the delay return new PromiseValue(new Promise(resolve => { setTimeout(() => resolve(new UndefinedValue()), delayMs); })); })); // Alias sleep to delay this.environment.define('sleep', this.environment.get('delay')); } registerLLMProvider(name, provider) { this.llmProviders.set(name, provider); } setDefaultLLMProvider(name) { if (!this.llmProviders.has(name)) { throw new RuntimeError(`LLM provider '${name}' not found`); } this.defaultLLMProvider = name; } getDefaultLLMProviderName() { return this.defaultLLMProvider; } getDefaultLLMProvider() { if (!this.defaultLLMProvider) { return undefined; } return this.llmProviders.get(this.defaultLLMProvider); } async interpret(node) { switch (node.type) { case 'Program': return this.interpretProgram(node); case 'NumberLiteral': return this.interpretNumberLiteral(node); case 'StringLiteral': return this.interpretStringLiteral(node); case 'InterpolatedString': return this.interpretInterpolatedString(node); case 'BooleanLiteral': return this.interpretBooleanLiteral(node); case 'NullLiteral': return this.interpretNullLiteral(node); case 'UndefinedLiteral': return this.interpretUndefinedLiteral(node); case 'IdentifierExpression': return this.interpretIdentifier(node); case 'BinaryExpression': return this.interpretBinaryExpression(node); case 'UnaryExpression': return this.interpretUnaryExpression(node); case 'CallExpression': return this.interpretCallExpression(node); case 'TernaryExpression': return this.interpretTernaryExpression(node); case 'ConfidentTernaryExpression': return this.interpretConfidentTernaryExpression(node); case 'ArrayLiteral': return this.interpretArrayLiteral(node); case 'ObjectLiteral': return this.interpretObjectLiteral(node); case 'PropertyAccess': return this.interpretPropertyAccess(node); case 'OptionalChainAccess': return this.interpretOptionalChainAccess(node); case 'IndexAccess': return this.interpretIndexAccess(node); case 'LambdaExpression': return this.interpretLambdaExpression(node); case 'PlaceholderExpression': throw new RuntimeError('Placeholder (_) can only be used within pipeline expressions', node); case 'ConfidenceExpression': return this.interpretConfidenceExpression(node); case 'AssignmentStatement': return this.interpretAssignmentStatement(node); case 'DestructuringAssignment': return this.interpretDestructuringAssignment(node); case 'AssignmentExpression': return this.interpretAssignmentExpression(node); case 'AwaitExpression': return this.interpretAwaitExpression(node); case 'IfStatement': return this.interpretIfStatement(node); case 'UncertainIfStatement': return this.interpretUncertainIfStatement(node); case 'ContextStatement': return this.interpretContextStatement(node); case 'AgentDeclaration': return this.interpretAgentDeclaration(node); case 'BlockStatement': return this.interpretBlockStatement(node); case 'ExpressionStatement': return this.interpretExpressionStatement(node); case 'ForLoop': return this.interpretForLoop(node); case 'ForInLoop': return this.interpretForInLoop(node); case 'WhileLoop': return this.interpretWhileLoop(node); case 'DoWhileLoop': return this.interpretDoWhileLoop(node); case 'UncertainForLoop': return this.interpretUncertainForLoop(node); case 'UncertainWhileLoop': return this.interpretUncertainWhileLoop(node); case 'BreakStatement': throw new LoopControlError('break'); case 'ContinueStatement': throw new LoopControlError('continue'); case 'FunctionDeclaration': return this.interpretFunctionDeclaration(node); case 'ReturnStatement': throw await this.interpretReturnStatement(node); case 'VariableDeclaration': return this.interpretVariableDeclaration(node); case 'ImportStatement': return this.interpretImportStatement(node); case 'ExportStatement': return this.interpretExportStatement(node); default: throw new RuntimeError(`Unknown node type: ${node.type}`, node); } } async interpretProgram(program) { let result = new NumberValue(0); // Default return value // First pass: process imports (they need to be available before any code runs) for (const statement of program.statements) { if (statement.type === 'ImportStatement') { await this.interpret(statement); } } // Second pass: hoist function declarations for (const statement of program.statements) { if (statement instanceof ast_1.FunctionDeclaration) { await this.interpretFunctionDeclaration(statement); } } // Third pass: execute all statements for (const statement of program.statements) { result = await this.interpret(statement); } return result; } async interpretNumberLiteral(node) { return new NumberValue(node.value); } async interpretStringLiteral(node) { return new StringValue(node.value); } async interpretInterpolatedString(node) { let result = ''; // Interpolated strings have n parts and n-1 expressions // Example: "Hello ${name}, you are ${age} years old" // parts: ["Hello ", ", you are ", " years old"] // expressions: [name, age] for (let i = 0; i < node.parts.length; i++) { result += node.parts[i]; // Add the evaluated expression if there is one if (i < node.expressions.length) { const exprValue = await this.interpret(node.expressions[i]); result += exprValue.toString(); } } return new StringValue(result); } async interpretBooleanLiteral(node) { return new BooleanValue(node.value); } async interpretNullLiteral(_node) { return new NullValue(); } async interpretUndefinedLiteral(_node) { return new UndefinedValue(); } async interpretIdentifier(node) { try { return this.environment.get(node.name); } catch (error) { throw new RuntimeError(`Undefined variable: ${node.name}`, node, node.location); } } async interpretBinaryExpression(node) { // Special handling for property access operators where right side shouldn't be evaluated if (node.operator === '.' || node.operator === '~.') { const left = await this.interpret(node.left); // Don't evaluate right side - it's a property name, not an expression return this.applyBinaryOperator(node.operator, left, node.right, node); } // Special handling for short-circuit operators if (node.operator === '||' || node.operator === '&&') { const left = await this.interpret(node.left); // Short-circuit evaluation for || if (node.operator === '||' && left.isTruthy()) { return left; } // Short-circuit evaluation for && if (node.operator === '&&' && !left.isTruthy()) { return left; } // Only evaluate right side if we didn't short-circuit const right = await this.interpret(node.right); return right; } const left = await this.interpret(node.left); const right = await this.interpret(node.right); return this.applyBinaryOperator(node.operator, left, right, node); } applyBinaryOperator(operator, left, right, node) { // Handle property access operators early (they have special right-side handling) if (operator === '.') { return right; } if (operator === '~.') { return this.applyConfidentPropertyAccess(left, right, node); } // Ensure right is a Value for all other operators if (!(right instanceof NumberValue || right instanceof StringValue || right instanceof BooleanValue || right instanceof ConfidenceValue || right instanceof FunctionValue || right instanceof ArrayValue || right instanceof ObjectValue || right instanceof NullValue || right instanceof UndefinedValue)) { throw new RuntimeError(`Invalid right operand for operator ${operator}`, node); } // Handle confidence propagation (except for instanceof which doesn't propagate confidence) if ((left instanceof ConfidenceValue || right instanceof ConfidenceValue) && operator !== 'instanceof') { return this.applyBinaryOperatorWithConfidence(operator, left, right, node); } switch (operator) { case '+': if (left instanceof NumberValue && right instanceof NumberValue) { return new NumberValue(left.value + right.value); } if (left instanceof StringValue || right instanceof StringValue) { return new StringValue(left.toString() + right.toString()); } throw new RuntimeError(`Cannot apply + to ${left.type} and ${right.type}`, node, node.location); case '-': if (left instanceof NumberValue && right instanceof NumberValue) { return new NumberValue(left.value - right.value); } throw new RuntimeError(`Cannot apply - to ${left.type} and ${right.type}`, node); case '*': if (left instanceof NumberValue && right instanceof NumberValue) { return new NumberValue(left.value * right.value); } throw new RuntimeError(`Cannot apply * to ${left.type} and ${right.type}`, node); case '/': if (left instanceof NumberValue && right instanceof NumberValue) { if (right.value === 0) { throw new RuntimeError('Division by zero', node); } return new NumberValue(left.value / right.value); } throw new RuntimeError(`Cannot apply / to ${left.type} and ${right.type}`, node); case '**': if (left instanceof NumberValue && right instanceof NumberValue) { return new NumberValue(Math.pow(left.value, right.value)); } throw new RuntimeError(`Cannot apply ** to ${left.type} and ${right.type}`, node); case '%': if (left instanceof NumberValue && right instanceof NumberValue) { if (right.value === 0) { throw new RuntimeError('Modulo by zero', node); } return new NumberValue(left.value % right.value); } throw new RuntimeError(`Cannot apply % to ${left.type} and ${right.type}`, node); case '>': if (left instanceof NumberValue && right instanceof NumberValue) { return new BooleanValue(left.value > right.value); } throw new RuntimeError(`Cannot compare ${left.type} and ${right.type}`, node); case '<': if (left instanceof NumberValue && right instanceof NumberValue) { return new BooleanValue(left.value < right.value); } throw new RuntimeError(`Cannot compare ${left.type} and ${right.type}`, node); case '>=': if (left instanceof NumberValue && right instanceof NumberValue) { return new BooleanValue(left.value >= right.value); } throw new RuntimeError(`Cannot compare ${left.type} and ${right.type}`, node); case '<=': if (left instanceof NumberValue && right instanceof NumberValue) { return new BooleanValue(left.value <= right.value); } throw new RuntimeError(`Cannot compare ${left.type} and ${right.type}`, node); case '==': return new BooleanValue(this.looseEquals(left, right)); case '!=': return new BooleanValue(!this.looseEquals(left, right)); case '===': return new BooleanValue(left.equals(right)); case '!==': return new BooleanValue(!left.equals(right)); case '&&': case '||': // These are handled in interpretBinaryExpression for short-circuit evaluation throw new RuntimeError('Logical operators should be handled in interpretBinaryExpression', node); case '??': // Nullish coalescing - return right if left is null or undefined if (left instanceof NullValue || left instanceof UndefinedValue) { return right; } return left; case '~~': // Confidence chaining - should not reach here as it's handled with confidence throw new RuntimeError('Confidence chaining requires confident values', node); case '~??': // Confidence coalesce - should not reach here as it's handled with confidence throw new RuntimeError('Confidence coalesce requires confident val