@prism-lang/core
Version:
A programming language for uncertainty
1,478 lines (1,214 loc) • 127 kB
text/typescript
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);