Runtime API
The Runtime API provides the execution engine for Prism programs, handling AST interpretation, value management, confidence tracking, and context management.
Overview
The runtime system includes:
- AST interpretation and execution
- Value types with confidence tracking
- Environment management for variable scoping
- Context management for uncertainty handling
- Built-in functions and LLM integration
- Error handling with detailed error messages
Interpreter Class
Constructor
class Interpreter {
constructor()
}
Creates a new interpreter instance with an empty environment and context manager.
Methods
interpret()
async interpret(node: ASTNode): Promise<Value>
Interprets an AST node and returns the resulting value.
Parameters:
node: The AST node to interpret
Returns: A Promise resolving to the execution result
Example:
import { parse, Interpreter } from '@prism-lang/core';
const interpreter = new Interpreter();
const ast = parse('x = 10; y = x * 2;');
const result = await interpreter.interpret(ast);
console.log(result.toString()); // "20"
registerLLMProvider()
registerLLMProvider(name: string, provider: LLMProvider): void
Registers an LLM provider for use in llm() function calls.
Parameters:
name: Unique identifier for the providerprovider: LLMProvider implementation
setDefaultLLMProvider()
setDefaultLLMProvider(name: string): void
Sets the default LLM provider for llm() calls.
Parameters:
name: Name of a registered provider
Example:
import { MockLLMProvider } from '@prism-lang/llm';
const interpreter = new Interpreter();
const mockProvider = new MockLLMProvider();
interpreter.registerLLMProvider('mock', mockProvider);
interpreter.setDefaultLLMProvider('mock');
Runtime Class
While the Interpreter gives you low-level control, most applications use the higher-level Runtime, which wires an interpreter together with the module system, built-ins, and helpers for cache management.
createRuntime
function createRuntime(options?: RuntimeOptions): Runtime
Creates a fully configured runtime.
options.moduleSystem(optional): Provide a preconfiguredModuleSystem(custom file readers, virtual files, etc.). When omitted, a new one is created automatically.options.confidence(optional): Configure confidence combination rules and provenance tracking.
interface RuntimeOptions {
moduleSystem?: ModuleSystem;
confidence?: {
strategy?: {
arithmetic?: 'min' | 'max' | 'product' | 'average';
comparison?: 'min' | 'max' | 'product' | 'average';
logicalAnd?: 'min' | 'max' | 'product' | 'average';
logicalOr?: 'min' | 'max' | 'product' | 'average';
chain?: 'min' | 'max' | 'product' | 'average';
ternary?: 'min' | 'max' | 'product' | 'average';
functionCall?: 'min' | 'max' | 'product' | 'average';
parallel?: 'min' | 'max';
coalesceThreshold?: number;
thresholdGate?: {
threshold?: number;
reduceFactor?: number;
onFail?: 'reduce' | 'returnNull' | 'returnLeft';
};
};
trackProvenance?: boolean;
};
}
Example: custom confidence strategy
import { createRuntime } from '@prism-lang/core';
const runtime = createRuntime({
confidence: {
strategy: {
arithmetic: 'min',
logicalOr: 'max',
ternary: 'product',
thresholdGate: { threshold: 0.8, reduceFactor: 0.4 }
},
trackProvenance: true
}
});
Runtime Methods
execute()
await runtime.execute(ast: Program): Promise<Value>
Runs a parsed program.
registerLLMProvider() / setDefaultLLMProvider()
Proxy to the interpreter helpers; register or select an LLM provider shared across all modules.
getModuleSystem()
const moduleSystem = runtime.getModuleSystem();
Returns the ModuleSystem instance backing the runtime. Useful if you need direct access for advanced tooling.
invalidateModule()
runtime.invalidateModule('/path/to/module.prism', {
invalidateDependents: true // default
});
Drops a module (and, by default, all modules that depend on it) from the cache. The next import will re-execute it.
reloadModule()
const module = await runtime.reloadModule('/shared/state.prism');
Convenience helper that invalidates and immediately reloads a module, returning the updated Module descriptor (exports, dependency graph, etc.). Dependents are automatically invalidated so subsequent imports see the new exports.
Example: hot reload loop
import { createRuntime } from '@prism-lang/core';
const runtime = createRuntime();
await runtime.getModuleSystem().loadModule('/app/main.prism', runtime);
// when a file changes:
await runtime.reloadModule('/app/theme.prism');
This reuses the same interpreter, so existing globals, registered LLM providers, and environments remain intact between reloads.
The
prism run --watchcommand uses this helper internally to refresh the edited module and all dependents without restarting the CLI.
streamLLM()
const stream = runtime.streamLLM("Draft today's update", {
provider: 'claude',
structuredOutput: false,
temperature: 0.3,
});
for await (const chunk of stream.chunks) {
if (chunk.type === 'text' && chunk.content) {
process.stdout.write(chunk.content);
}
}
const final = await stream.response;
console.log(`\nConfidence: ${(final.confidence * 100).toFixed(1)}%`);
Returns a RuntimeLLMStream with:
chunks:AsyncIterable<LLMStreamChunk>of text/metadata eventsresponse: Promise resolving to the finalLLMResponsecancel(reason?): stop streaming (best-effort for providers without native streaming)
If a provider does not implement streaming, Prism falls back to a single chunk once the request finishes. Providers that rely on structured responses should set structuredOutput: false when streaming plain text.
Value Types
All runtime values extend the abstract Value class:
abstract class Value {
abstract type: string;
abstract value: unknown;
abstract equals(other: Value): boolean;
abstract isTruthy(): boolean;
abstract toString(): string;
}
NumberValue
class NumberValue extends Value {
constructor(value: number)
type: 'number'
value: number
}
Represents numeric values.
Truthy: When not zero
StringValue
class StringValue extends Value {
constructor(value: string)
type: 'string'
value: string
}
Represents string values.
Truthy: When not empty
BooleanValue
class BooleanValue extends Value {
constructor(value: boolean)
type: 'boolean'
value: boolean
}
Represents boolean values.
Truthy: When true
NullValue
class NullValue extends Value {
constructor()
type: 'null'
value: null
}
Represents null values.
Truthy: Always false
ArrayValue
class ArrayValue extends Value {
constructor(elements: Value[])
type: 'array'
value: Value[]
elements: Value[]
}
Represents array values.
Truthy: Always true
Example:
const arr = new ArrayValue([
new NumberValue(1),
new StringValue("hello")
]);
ObjectValue
class ObjectValue extends Value {
constructor(properties: Map<string, Value>)
type: 'object'
value: Map<string, Value>
properties: Map<string, Value>
}
Represents object values with string keys.
Truthy: Always true
Example:
const obj = new ObjectValue(new Map([
['name', new StringValue('Alice')],
['age', new NumberValue(30)]
]));
FunctionValue
class FunctionValue extends Value {
constructor(
name: string,
value: (args: Value[]) => Promise<Value>,
arity?: number
)
type: 'function'
name: string
value: (args: Value[]) => Promise<Value>
arity?: number
}
Represents function values, including built-ins and lambdas.
Truthy: Always true
ConfidenceValue
class ConfidenceValue extends Value {
constructor(
value: Value,
confidence: ConfidenceLib
)
type: 'confident'
value: Value
confidence: ConfidenceLib
}
Wraps any value with a confidence score.
Truthy: Based on wrapped value
Example:
const confident = new ConfidenceValue(
new StringValue("prediction"),
new ConfidenceLib(0.85)
);
console.log(confident.toString()); // "prediction (~0.85)"
Environment Management
Environment Class
class Environment {
constructor(parent?: Environment)
define(name: string, value: Value): void
get(name: string): Value
set(name: string, value: Value): void
getAllVariables(): Map<string, Value>
}
Manages variable scoping with lexical parent chaining.
Methods:
define(): Creates a new variable in current scopeget(): Retrieves variable value (searches parent scopes)set(): Updates existing variable (searches parent scopes)getAllVariables(): Returns all variables in current scope
Example:
const global = new Environment();
global.define('x', new NumberValue(10));
const local = new Environment(global);
local.define('y', new NumberValue(20));
console.log(local.get('x')); // NumberValue(10) - from parent
console.log(local.get('y')); // NumberValue(20) - from local
Error Handling
RuntimeError
class RuntimeError extends Error {
constructor(
message: string,
node?: ASTNode,
location?: { line: number; column: number }
)
line?: number
column?: number
node?: ASTNode
}
Thrown during runtime execution errors.
Example:
try {
await interpreter.interpret(ast);
} catch (error) {
if (error instanceof RuntimeError) {
console.error(`Runtime error at line ${error.line}: ${error.message}`);
}
}
LoopControlError
class LoopControlError extends Error {
constructor(type: 'break' | 'continue')
type: 'break' | 'continue'
}
Internal error used for loop control flow (break/continue).
Built-in Functions
The interpreter provides several built-in functions:
llm()
llm(prompt: string, options?: LLMCallOptions): Promise<ConfidenceValue<string>>
Calls an LLM provider using the current runtime configuration.
Parameters:
prompt: The prompt stringoptions: Optional object to override provider behavior
interface LLMCallOptions {
provider?: string;
model?: string;
temperature?: number;
maxTokens?: number;
topP?: number;
timeout?: number;
structuredOutput?: boolean;
includeReasoning?: boolean;
confidenceExtractor?: (responseText: string) => Promise<{ value: number }>;
extractor?: (response: {
content: string;
confidence: number;
model: string;
tokensUsed: number;
provider: string;
prompt: string;
options: Record<string, unknown>;
metadata?: Record<string, unknown>;
}) => number | ConfidenceValue<any>;
}
confidenceExtractor is forwarded to the provider implementation (e.g. @prism-lang/llm) so it can derive a confidence score when structured output isn't available. extractor receives an object with { content, confidence, model, tokensUsed, provider, prompt, options, metadata } and lets you override the returned confidence by returning a number or confident value.
Returns: Confident string response using either the provider-reported confidence or the extractor override.
Example:
let response = llm("What is 2+2?");
// => "4" (~95%)
let custom = llm("Summarize this doc", {
provider: "claude",
model: "claude-3-sonnet",
temperature: 0.2,
extractor: info => info.confidence * 0.8
});
stream_llm()
let handle = stream_llm("Draft a summary", { structuredOutput: false })
let chunk = await handle.next()
while (chunk) {
console.log(chunk.text)
chunk = await handle.next()
}
let final = await handle.result()
Returns a stream handle containing:
next(): Promise<Chunk | null>– resolves to the nextLLMStreamChunkwith fields such astype,text,reasoning,error.result(): Promise<ConfidenceValue<string>>– resolves to the final confident string (extractor rules apply).cancel(): void– aborts the stream.
If the provider doesn’t support streaming, Prism buffers the response and returns it as a single chunk once complete.
Array Functions
map()
map(array: Array, fn: Function): Array
Transforms array elements.
Example:
let numbers = [1, 2, 3];
let doubled = map(numbers, x => x * 2);
// Returns: [2, 4, 6]
filter()
filter(array: Array, predicate: Function): Array
Filters array elements.
Example:
let numbers = [1, 2, 3, 4, 5];
let evens = filter(numbers, x => x % 2 == 0);
// Returns: [2, 4]
reduce()
reduce(array: Array, reducer: Function, initial?: any): any
Reduces array to single value.
Example:
let numbers = [1, 2, 3, 4];
let sum = reduce(numbers, (acc, x) => acc + x, 0);
// Returns: 10
Math Functions
max()
max(...values: number[]): number
Returns maximum value.
Example:
let largest = max(10, 5, 20, 15);
// Returns: 20
min()
min(...values: number[]): number
Returns minimum value.
Example:
let smallest = min(10, 5, 20, 15);
// Returns: 5
Async Functions
delay()
delay(milliseconds: number): Promise<void>
Pauses execution for the specified number of milliseconds. Returns a promise that resolves after the delay.
Example:
async function slowOperation() {
console.log("Starting...")
await delay(2000) // Wait 2 seconds
console.log("Done!")
}
// Retry with exponential backoff
async function retryWithBackoff(operation, maxRetries) {
for let i = 0; i < maxRetries; i++ {
try {
return await operation()
} catch (e) {
await delay(1000 * (2 ** i)) // 1s, 2s, 4s...
}
}
}
sleep()
sleep(milliseconds: number): Promise<void>
Alias for delay(). Pauses execution for the specified duration.
Example:
await sleep(500) // Same as await delay(500)
debounce()
debounce(waitMs: number): Function
Creates a debounced wrapper that delays invoking a function until after waitMs milliseconds have elapsed since the last call. Useful for rate-limiting user input handlers.
Example:
// Create a debounced search function
let searchDebouncer = debounce(300)
// In event handler
let onSearchInput = (query) => {
searchDebouncer(() => {
let results = performSearch(query)
updateUI(results)
})
}
// Only executes 300ms after the user stops typing
Collection Functions
sortBy()
sortBy(key: string, direction?: "asc" | "desc"): Function
Creates a function that sorts an array of objects by the specified key. Returns a higher-order function for use in pipelines.
Parameters:
key: The property name to sort bydirection: Sort direction -"asc"(default) or"desc"
Example:
let users = [
{name: "Alice", score: 85},
{name: "Bob", score: 92},
{name: "Charlie", score: 78}
]
// Create sorter functions
let byScoreDesc = sortBy("score", "desc")
let byName = sortBy("name")
// Apply sorting
let rankedUsers = byScoreDesc(users)
// [{name: "Bob", score: 92}, {name: "Alice", score: 85}, {name: "Charlie", score: 78}]
let alphabetical = byName(users)
// [{name: "Alice", ...}, {name: "Bob", ...}, {name: "Charlie", ...}]
// Use in pipeline
let result = users |> sortBy("score", "desc")(_) |> map(u => u.name)
// ["Bob", "Alice", "Charlie"]
groupBy()
groupBy(key: string | Function): Function
Creates a function that groups an array of objects by the specified key or function. Returns an object with keys for each group.
Parameters:
key: Property name to group by, or a function that returns the group key
Example:
let items = [
{name: "Apple", category: "fruit", price: 1.5},
{name: "Banana", category: "fruit", price: 0.5},
{name: "Carrot", category: "vegetable", price: 0.8},
{name: "Broccoli", category: "vegetable", price: 1.2}
]
// Group by property
let byCategory = groupBy("category")
let grouped = byCategory(items)
// {
// fruit: [{name: "Apple", ...}, {name: "Banana", ...}],
// vegetable: [{name: "Carrot", ...}, {name: "Broccoli", ...}]
// }
// Group by computed value
let byPriceRange = groupBy(item => item.price > 1 ? "expensive" : "cheap")
let priceGrouped = byPriceRange(items)
// {
// expensive: [{name: "Apple", ...}, {name: "Broccoli", ...}],
// cheap: [{name: "Banana", ...}, {name: "Carrot", ...}]
// }
// Use in pipeline
let result = items |> groupBy("category")(_)
Confidence Functions
confidence()
confidence(threshold: number): Function
Creates a function that wraps another function's return value with the specified confidence level. Useful for creating confidence-aware processing pipelines.
Parameters:
threshold: Confidence value between 0 and 1 to attach to results
Example:
// Wrap a function to add confidence to its output
let highConfidence = confidence(0.95)
let mediumConfidence = confidence(0.7)
let processWithConfidence = highConfidence(data => data * 2)
let result = processWithConfidence(10)
// 20 ~> 0.95
// Create calibrated processors
let sensorProcessor = confidence(0.85)
let userInputProcessor = confidence(0.6)
let sensorReading = sensorProcessor(() => readSensor())
let userValue = userInputProcessor(() => getUserInput())
threshold()
threshold(minConfidence: number): Function
Creates a filter function that only passes values meeting the minimum confidence threshold. Values below the threshold are filtered out.
Parameters:
minConfidence: Minimum confidence value (0-1) required to pass
Example:
// Filter data by confidence level
let highConfidenceOnly = threshold(0.9)
let moderateConfidence = threshold(0.5)
let data = [
10 ~> 0.95,
20 ~> 0.7,
30 ~> 0.92,
40 ~> 0.4
]
let reliable = highConfidenceOnly(data)
// [10 ~> 0.95, 30 ~> 0.92]
let usable = moderateConfidence(data)
// [10 ~> 0.95, 20 ~> 0.7, 30 ~> 0.92]
// Use in pipeline for confident decision making
let result = predictions
|> threshold(0.8)(_)
|> map(p => p.recommendation)
|> first
Execution Features
Confidence Propagation
Confidence values are propagated through operations:
let x = 10 ~> 0.9;
let y = 20 ~> 0.8;
let z = x + y; // Result has confidence based on inputs
Context Management
The runtime maintains execution contexts for uncertainty handling:
in context "analysis" {
// Code executes in analysis context
let result = processData(input);
}
Loop Control
Supports break and continue statements:
for let i = 0; i < 10; i++ {
if (i == 5) {
break; // Exit loop
}
if (i % 2 == 0) {
continue; // Skip even numbers
}
// Process odd numbers
}
Destructuring Assignment
Supports array and object destructuring with confidence:
// Array destructuring
let [a, b, c] = [1, 2, 3];
let [x, ...rest] = array;
// Object destructuring
let {name, age} = person;
let {x: coordX, y: coordY} = point;
// With confidence thresholds
let a = null;
let b = null;
[a, b] ~> 0.8 = uncertainArray;
Performance Considerations
- Async Execution: All operations are async to support LLM calls
- Environment Lookup: Variable lookup is O(depth) where depth is scope nesting
- Confidence Tracking: Adds minimal overhead to operations
- Memory Management: Values are garbage collected when out of scope
Example: Custom Function Registration
import { Interpreter, FunctionValue, NumberValue, Value } from '@prism-lang/core';
const interpreter = new Interpreter();
// Add custom sqrt function
interpreter.environment.define('sqrt', new FunctionValue(
'sqrt',
async (args: Value[]) => {
if (args.length !== 1) {
throw new RuntimeError('sqrt() requires exactly one argument');
}
const arg = args[0];
if (!(arg instanceof NumberValue)) {
throw new RuntimeError('sqrt() requires a number');
}
return new NumberValue(Math.sqrt(arg.value));
},
1 // arity
));
// Use the custom function
const result = await interpreter.interpret(parse('sqrt(16)'));
console.log(result.toString()); // "4"