@prism-lang/core
Version:
A programming language for uncertainty
1,146 lines • 142 kB
JavaScript
"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