UNPKG

@prism-lang/core

Version:

A programming language for uncertainty

1,478 lines (1,214 loc) 127 kB
import { ASTNode, Program, Statement, IdentifierExpression, NumberLiteral, StringLiteral, InterpolatedString, BooleanLiteral, NullLiteral, UndefinedLiteral, BinaryExpression, UnaryExpression, CallExpression, TernaryExpression, ConfidentTernaryExpression, ArrayLiteral, ObjectLiteral, PropertyAccess, OptionalChainAccess, IndexAccess, ConfidenceExpression, AssignmentStatement, AssignmentExpression, AwaitExpression, IfStatement, UncertainIfStatement, ContextStatement, AgentDeclaration, BlockStatement, ExpressionStatement, BinaryOperator, LambdaExpression, SpreadElement, ForLoop, ForInLoop, WhileLoop, DoWhileLoop, UncertainForLoop, UncertainWhileLoop, ArrayPattern, ObjectPattern, RestElement, DestructuringAssignment, FunctionDeclaration, ReturnStatement, VariableDeclaration, ImportStatement, ExportStatement, } from './ast'; import { ConfidenceValue as ConfidenceLib, ConfidenceLevel } from './confidence'; import { Context, ContextManager } from './context'; import { LLMProvider, LLMRequest, MockLLMProvider } from './llm-types'; export class RuntimeError extends Error { public line?: number; public column?: number; constructor(message: string, public node?: ASTNode, location?: { line: number; column: number }) { // If location is provided, enhance the error message const enhancedMessage = location ? `Error at line ${location.line}, column ${location.column}: ${message}` : message; super(enhancedMessage); this.name = 'RuntimeError'; if (location) { this.line = location.line; this.column = location.column; } } } export class LoopControlError extends Error { constructor(public type: 'break' | 'continue') { super(type); this.name = 'LoopControlError'; } } export class ReturnException extends Error { constructor(public value?: Value) { super('return'); this.name = 'ReturnException'; } } export abstract class Value { abstract type: string; abstract value: unknown; abstract equals(other: Value): boolean; abstract isTruthy(): boolean; abstract toString(): string; } export class NumberValue extends Value { type = 'number'; constructor(public value: number) { super(); } equals(other: Value): boolean { return other instanceof NumberValue && other.value === this.value; } isTruthy(): boolean { return this.value !== 0; } toString(): string { return this.value.toString(); } } export class StringValue extends Value { type = 'string'; constructor(public value: string) { super(); } equals(other: Value): boolean { return other instanceof StringValue && other.value === this.value; } isTruthy(): boolean { return this.value.length > 0; } toString(): string { return this.value; } } export class BooleanValue extends Value { type = 'boolean'; constructor(public value: boolean) { super(); } equals(other: Value): boolean { return other instanceof BooleanValue && other.value === this.value; } isTruthy(): boolean { return this.value; } toString(): string { return this.value.toString(); } } export class NullValue extends Value { type = 'null'; value = null; constructor() { super(); } equals(other: Value): boolean { return other instanceof NullValue; } isTruthy(): boolean { return false; } toString(): string { return 'null'; } } export class UndefinedValue extends Value { type = 'undefined'; value = undefined; constructor() { super(); } equals(other: Value): boolean { return other instanceof UndefinedValue; } isTruthy(): boolean { return false; } toString(): string { return 'undefined'; } } export class ConfidenceValue extends Value { type = 'confident'; constructor( public value: Value, public confidence: ConfidenceLib ) { super(); } equals(other: Value): boolean { return other instanceof ConfidenceValue && other.value.equals(this.value) && other.confidence.equals(this.confidence); } isTruthy(): boolean { return this.value.isTruthy(); } toString(): string { return `${this.value.toString()} (~${this.confidence.toString()})`; } } export class ArrayValue extends Value { type = 'array'; value: Value[]; constructor(public elements: Value[]) { super(); this.value = elements; } equals(other: Value): boolean { 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(): boolean { return true; } toString(): string { return `[${this.elements.map(e => e.toString()).join(', ')}]`; } } export class ObjectValue extends Value { type = 'object'; value: Map<string, Value>; constructor(public properties: Map<string, Value>) { super(); this.value = properties; } equals(other: Value): boolean { 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(): boolean { return true; } toString(): string { const props = Array.from(this.properties.entries()) .filter(([_, v]) => !(v instanceof UndefinedValue)) .map(([k, v]) => `${k}: ${v.toString()}`) .join(', '); return props.length > 0 ? `{ ${props} }` : '{}'; } } export class FunctionValue extends Value { type = 'function'; constructor( public name: string, public value: (args: Value[]) => Promise<Value>, public arity?: number ) { super(); } equals(other: Value): boolean { return other instanceof FunctionValue && other.name === this.name; } isTruthy(): boolean { return true; } toString(): string { return `[Function: ${this.name}]`; } } export class PromiseValue extends Value { type = 'promise'; constructor(public value: Promise<Value>) { super(); } equals(_other: Value): boolean { return false; // Promises are never equal } isTruthy(): boolean { return true; // Promises are always truthy } toString(): string { return '[Promise]'; } } interface VariableInfo { value: Value; mutable: boolean; // true for let, false for const declared: boolean; // true for const/let, false for legacy assignments } export class Environment { private variables = new Map<string, VariableInfo>(); constructor(private parent?: Environment) {} // Define a variable with mutability info (for const/let) define(name: string, value: Value, mutable: boolean = true, declared: boolean = false): void { 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: string): Value { 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: string, value: Value): void { 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(): Map<string, Value> { const result = new Map<string, Value>(); for (const [name, info] of this.variables) { result.set(name, info.value); } return result; } } export class Interpreter { public environment: Environment; private contextManager: ContextManager; private llmProviders = new Map<string, LLMProvider>(); private defaultLLMProvider?: string; constructor() { this.environment = new Environment(); this.contextManager = new ContextManager(); this.setupBuiltins(); } private setupBuiltins(): void { // 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: string; // 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 as StringValue).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 LLMRequest(promptString); const response = await provider.complete(request); return new ConfidenceValue( new StringValue(response.content), new ConfidenceLib(response.confidence) ); } catch (error) { throw new RuntimeError(`LLM call failed: ${(error as 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 ConfidenceLib(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: Value[] = []; 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 ConfidenceLib(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: Value[] = []; 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 ConfidenceLib(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: Value; let startIndex: number; 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: Value | null = null; let maxNum: number = -Infinity; let maxConfidence: ConfidenceLib | null = null; for (const arg of args) { let value = arg; let confidence: ConfidenceLib | null = 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: Value | null = null; let minNum: number = Infinity; let minConfidence: ConfidenceLib | null = null; for (const arg of args) { let value = arg; let confidence: ConfidenceLib | null = 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<string, Value>(); 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 ConfidenceLib(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 ConfidenceLib(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<string, Value[]>(); for (const element of array.elements) { let groupKey: string; 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<string, Value>(); 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: NodeJS.Timeout | null = null; let lastResult: Value = new UndefinedValue(); // Return debounced function return new FunctionValue('debouncedFunction', async (innerArgs) => { return new Promise<Value>((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<string, Value>()); 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: Value[] = []; 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: string, provider: LLMProvider): void { this.llmProviders.set(name, provider); } setDefaultLLMProvider(name: string): void { if (!this.llmProviders.has(name)) { throw new RuntimeError(`LLM provider '${name}' not found`); } this.defaultLLMProvider = name; } getDefaultLLMProviderName(): string | undefined { return this.defaultLLMProvider; } private getDefaultLLMProvider(): LLMProvider | undefined { if (!this.defaultLLMProvider) { return undefined; } return this.llmProviders.get(this.defaultLLMProvider); } async interpret(node: ASTNode): Promise<Value> { switch (node.type) { case 'Program': return this.interpretProgram(node as Program); case 'NumberLiteral': return this.interpretNumberLiteral(node as NumberLiteral); case 'StringLiteral': return this.interpretStringLiteral(node as StringLiteral); case 'InterpolatedString': return this.interpretInterpolatedString(node as InterpolatedString); case 'BooleanLiteral': return this.interpretBooleanLiteral(node as BooleanLiteral); case 'NullLiteral': return this.interpretNullLiteral(node as NullLiteral); case 'UndefinedLiteral': return this.interpretUndefinedLiteral(node as UndefinedLiteral); case 'IdentifierExpression': return this.interpretIdentifier(node as IdentifierExpression); case 'BinaryExpression': return this.interpretBinaryExpression(node as BinaryExpression); case 'UnaryExpression': return this.interpretUnaryExpression(node as UnaryExpression); case 'CallExpression': return this.interpretCallExpression(node as CallExpression); case 'TernaryExpression': return this.interpretTernaryExpression(node as TernaryExpression); case 'ConfidentTernaryExpression': return this.interpretConfidentTernaryExpression(node as ConfidentTernaryExpression); case 'ArrayLiteral': return this.interpretArrayLiteral(node as ArrayLiteral); case 'ObjectLiteral': return this.interpretObjectLiteral(node as ObjectLiteral); case 'PropertyAccess': return this.interpretPropertyAccess(node as PropertyAccess); case 'OptionalChainAccess': return this.interpretOptionalChainAccess(node as OptionalChainAccess); case 'IndexAccess': return this.interpretIndexAccess(node as IndexAccess); case 'LambdaExpression': return this.interpretLambdaExpression(node as LambdaExpression); case 'PlaceholderExpression': throw new RuntimeError('Placeholder (_) can only be used within pipeline expressions', node); case 'ConfidenceExpression': return this.interpretConfidenceExpression(node as ConfidenceExpression); case 'AssignmentStatement': return this.interpretAssignmentStatement(node as AssignmentStatement); case 'DestructuringAssignment': return this.interpretDestructuringAssignment(node as DestructuringAssignment); case 'AssignmentExpression': return this.interpretAssignmentExpression(node as AssignmentExpression); case 'AwaitExpression': return this.interpretAwaitExpression(node as AwaitExpression); case 'IfStatement': return this.interpretIfStatement(node as IfStatement); case 'UncertainIfStatement': return this.interpretUncertainIfStatement(node as UncertainIfStatement); case 'ContextStatement': return this.interpretContextStatement(node as ContextStatement); case 'AgentDeclaration': return this.interpretAgentDeclaration(node as AgentDeclaration); case 'BlockStatement': return this.interpretBlockStatement(node as BlockStatement); case 'ExpressionStatement': return this.interpretExpressionStatement(node as ExpressionStatement); case 'ForLoop': return this.interpretForLoop(node as ForLoop); case 'ForInLoop': return this.interpretForInLoop(node as ForInLoop); case 'WhileLoop': return this.interpretWhileLoop(node as WhileLoop); case 'DoWhileLoop': return this.interpretDoWhileLoop(node as DoWhileLoop); case 'UncertainForLoop': return this.interpretUncertainForLoop(node as UncertainForLoop); case 'UncertainWhileLoop': return this.interpretUncertainWhileLoop(node as UncertainWhileLoop); case 'BreakStatement': throw new LoopControlError('break'); case 'ContinueStatement': throw new LoopControlError('continue'); case 'FunctionDeclaration': return this.interpretFunctionDeclaration(node as FunctionDeclaration); case 'ReturnStatement': throw await this.interpretReturnStatement(node as ReturnStatement); case 'VariableDeclaration': return this.interpretVariableDeclaration(node as VariableDeclaration); case 'ImportStatement': return this.interpretImportStatement(node as ImportStatement); case 'ExportStatement': return this.interpretExportStatement(node as ExportStatement); default: throw new RuntimeError(`Unknown node type: ${(node as any).type}`, node); } } private async interpretProgram(program: Program): Promise<Value> { let result: Value = 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 FunctionDeclaration) { await this.interpretFunctionDeclaration(statement); } } // Third pass: execute all statements for (const statement of program.statements) { result = await this.interpret(statement); } return result; } private async interpretNumberLiteral(node: NumberLiteral): Promise<Value> { return new NumberValue(node.value); } private async interpretStringLiteral(node: StringLiteral): Promise<Value> { return new StringValue(node.value); } private async interpretInterpolatedString(node: InterpolatedString): Promise<Value> { 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); } private async interpretBooleanLiteral(node: BooleanLiteral): Promise<Value> { return new BooleanValue(node.value); } private async interpretNullLiteral(_node: NullLiteral): Promise<Value> { return new NullValue(); } private async interpretUndefinedLiteral(_node: UndefinedLiteral): Promise<Value> { return new UndefinedValue(); } private async interpretIdentifier(node: IdentifierExpression): Promise<Value> { try { return this.environment.get(node.name); } catch (error) { throw new RuntimeError(`Undefined variable: ${node.name}`, node, node.location); } } private async interpretBinaryExpression(node: BinaryExpression): Promise<Value> { // 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 as IdentifierExpression, 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); } private applyBinaryOperator( operator: BinaryOperator, left: Value, right: Value | IdentifierExpression, node: BinaryExpression ): Value { // Handle property access operators early (they have special right-side handling) if (operator === '.') { return right as Value; } if (operator === '~.') { return this.applyConfidentPropertyAccess(left, right as IdentifierExpression, 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 as Value, 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 values', node); case '~&&': // Confidence AND - should not reach here as it's handled with confidence throw new RuntimeError('Confidence AND requires confident values', node); case '~||': // Confidence OR - should not reach here as it's handled with confidence throw new RuntimeError('Confidence OR requires confident values', node); case '~+': // Confident addition - should not reach here as it's handled with confidence throw new RuntimeError('Confident addition requires confident values', node); case '~-': // Confident subtraction - should not reach here as it's handled with confidence throw new RuntimeError('Confident subtraction requires confident values', node); case '~*': // Confident multiplication - should not reach here as it's handled with confidence throw new RuntimeError('Confident multiplication requires confident values', node); case '~/': // Confident division - should not reach here as it's handled with confidence throw new RuntimeError('Confident division requires confident values', node); case '~==': // Confident equality - should not reach here as it's handled with confidence throw new RuntimeError('Confident equality requires confident values', node); case '~!=': // Confident not equal - should not reach here as it's handled with confidence throw new RuntimeError('Confident not equal requires confident values', node); case '~<': // Confident less than - should not reach here as it's handled with confidence throw new RuntimeError('Confident less than requires confident values', node); case '~>=': // Confident greater equal - should not reach here as it's handled with confidence throw new RuntimeError('Confident greater equal requires confident values', node); case '~<=': // Confident less equal - should not reach here as it's handled with confidence throw new RuntimeError('Confident less equal requires confident values', node); case '~||>': // Parallel confidence - should not reach here as it's handled with confidence throw new RuntimeError('Parallel confidence requires confident values', node); case '~@>': // Threshold gate - should not reach here as it's handled with confidence throw new RuntimeError('Threshold gate requires confident values', node); case '~|>': // Confidence pipeline - should not reach here as it's handled with confidence throw new RuntimeError('Confidence pipeline requires confident values', node); case '~?>': // Confidence threshold gate - handle non-confident values return this.applyConfidenceThresholdGate(left, right, node); case 'instanceof': // Check if left is an instance of the constructor/type specified by right // For now, we'll check against built-in types if (right instanceof StringValue) { const typeName = right.value.toLowerCase(); // Handle confidence values by checking the wrapped value const valueToCheck = left instanceof ConfidenceValue ? left.value : left; switch (typeName) { case 'number': return new BooleanValue(valueToCheck instanceof NumberValue); case 'string': return new BooleanValue(valueToCheck instanceof StringValue); case 'boolean': return new BooleanValue(valueToCheck instanceof BooleanValue); case 'array': return new BooleanValue(valueToCheck instanceof ArrayValue);