2026-07-12 21:07:53 +00:00
import { Cause , Effect } from "effect"
2026-07-12 17:47:56 +00:00
import { isBlockedMember , ToolReference , ToolRuntimeError , type SafeObject } from "../tool-runtime.js"
2026-07-06 17:01:14 +00:00
import {
type AstNode ,
asNode ,
type Binding ,
CodeModeFunction ,
CoercionFunction ,
ComputedValue ,
ErrorConstructorReference ,
GlobalMethodReference ,
GlobalNamespace ,
getArray ,
getBoolean ,
getNode ,
getOptionalNode ,
getString ,
IntrinsicReference ,
InterpreterRuntimeError ,
isRecord ,
type MemberReference ,
OptionalShortCircuit ,
2026-07-11 01:46:13 +00:00
PromiseCapabilityFunction ,
2026-07-10 23:22:04 +00:00
PromiseInstanceMethodReference ,
2026-07-06 17:01:14 +00:00
PromiseMethodReference ,
type PromiseMethodName ,
PromiseNamespace ,
ProgramThrow ,
type ProgramNode ,
2026-07-11 18:43:33 +00:00
SearchFunction ,
2026-07-06 17:01:14 +00:00
type StatementResult ,
supportedSyntaxMessage ,
unsupportedSyntax ,
UriFunction ,
} from "./model.js"
2026-07-12 17:47:56 +00:00
import { caughtErrorValue , constructErrorValue } from "./errors.js"
2026-07-14 20:25:39 +00:00
import { type CallbackRunner , invokeArrayFrom , invokeGlobalMethod , invokeIntrinsic } from "./methods.js"
2026-07-06 17:01:14 +00:00
import {
2026-07-12 17:47:56 +00:00
constructPromise ,
invokePromiseInstanceMethod ,
invokePromiseMethod ,
PromiseRuntime ,
selfResolutionError ,
} from "./promises.js"
import { containsOpaqueReference , isRuntimeReference , rejectCircularInsertion , typeofValue } from "./references.js"
import { ScopeStack } from "./scope.js"
import { arrayMethods , mapMethods , setMethods , spreadItems } from "../stdlib/collections.js"
import { consoleMethods , formatConsoleMessage } from "../stdlib/console.js"
import { dateMethods } from "../stdlib/date.js"
import { mathConstants } from "../stdlib/math.js"
import { numberConstants , numberMethods , numberStatics } from "../stdlib/number.js"
import { objectMethodsPreservingIdentity } from "../stdlib/object.js"
2026-07-12 21:07:53 +00:00
import { promiseStatics } from "../stdlib/promise.js"
2026-07-12 17:47:56 +00:00
import { escapeRegexHint , regexpMethods , regexpProperties , regexFailureReason } from "../stdlib/regexp.js"
import { stringMethods , stringStatics } from "../stdlib/string.js"
2026-07-06 17:01:14 +00:00
import {
urlMethods ,
urlProperties ,
urlSearchParamsMethods ,
urlWritableProperties ,
invokeUriFunction ,
uriArgument ,
urlArgument ,
} from "../stdlib/url.js"
import {
boundedData ,
coerceToNumber ,
coerceToString ,
compoundOperators ,
errorBrandName ,
errorConstructors ,
invokeCoercion ,
valueConstructors ,
} from "../stdlib/value.js"
import {
2026-07-13 21:38:11 +00:00
isCodeModeValue ,
CodeModeDate ,
CodeModeMap ,
CodeModePromise ,
CodeModeRegExp ,
CodeModeSet ,
CodeModeURL ,
CodeModeURLSearchParams ,
2026-07-06 17:01:14 +00:00
} from "../values.js"
const instanceofValue = ( lhs : unknown , rhs : unknown , node : AstNode ) : boolean = > {
if ( rhs instanceof ErrorConstructorReference ) {
const brand = errorBrandName ( lhs )
return brand !== undefined && ( rhs . name === "Error" || brand === rhs . name )
}
if ( rhs instanceof GlobalNamespace ) {
switch ( rhs . name ) {
case "Date" :
2026-07-13 21:38:11 +00:00
return lhs instanceof CodeModeDate
2026-07-06 17:01:14 +00:00
case "RegExp" :
2026-07-13 21:38:11 +00:00
return lhs instanceof CodeModeRegExp
2026-07-06 17:01:14 +00:00
case "Map" :
2026-07-13 21:38:11 +00:00
return lhs instanceof CodeModeMap
2026-07-06 17:01:14 +00:00
case "Set" :
2026-07-13 21:38:11 +00:00
return lhs instanceof CodeModeSet
2026-07-06 17:01:14 +00:00
case "URL" :
2026-07-13 21:38:11 +00:00
return lhs instanceof CodeModeURL
2026-07-06 17:01:14 +00:00
case "URLSearchParams" :
2026-07-13 21:38:11 +00:00
return lhs instanceof CodeModeURLSearchParams
2026-07-06 17:01:14 +00:00
case "Array" :
return Array . isArray ( lhs )
case "Object" :
return lhs !== null && ( typeof lhs === "object" || typeofValue ( lhs ) === "function" )
}
}
2026-07-13 21:38:11 +00:00
if ( rhs instanceof PromiseNamespace ) return lhs instanceof CodeModePromise
2026-07-06 17:01:14 +00:00
if ( rhs instanceof CoercionFunction && ( rhs . name === "Number" || rhs . name === "String" || rhs . name === "Boolean" ) ) {
return false
}
throw new InterpreterRuntimeError (
"The right-hand side of 'instanceof' must be a constructor CodeMode knows: Error (or a specific error type like TypeError), Date, RegExp, Map, Set, URL, URLSearchParams, Array, Object, or Promise." ,
node ,
)
}
const collectPatternNames = ( pattern : AstNode , out : Array < string > = [ ] ) : Array < string > = > {
switch ( pattern . type ) {
case "Identifier" :
out . push ( getString ( pattern , "name" ) )
break
case "AssignmentPattern" :
collectPatternNames ( getNode ( pattern , "left" ) , out )
break
case "RestElement" :
collectPatternNames ( getNode ( pattern , "argument" ) , out )
break
case "ArrayPattern" :
for ( const element of getArray ( pattern , "elements" ) ) {
if ( element !== null ) collectPatternNames ( asNode ( element , "elements" ) , out )
}
break
case "ObjectPattern" :
for ( const property of getArray ( pattern , "properties" ) ) {
const prop = asNode ( property , "properties" )
collectPatternNames ( prop . type === "RestElement" ? getNode ( prop , "argument" ) : getNode ( prop , "value" ) , out )
}
break
}
return out
}
2026-07-17 03:26:45 +00:00
const loopDeclaration = ( left : AstNode , statement : "for...of" | "for...in" ) = > {
if ( left . type !== "VariableDeclaration" ) return undefined
const declarations = getArray ( left , "declarations" )
if ( declarations . length !== 1 ) {
throw new InterpreterRuntimeError ( ` ${ statement } supports one declared binding. ` , left )
}
const kind = getString ( left , "kind" )
return {
pattern : getNode ( asNode ( declarations [ 0 ] , "declarations[0]" ) , "id" ) ,
mutable : kind !== "const" ,
lexical : kind !== "var" ,
}
}
2026-07-12 17:47:56 +00:00
export class Interpreter < R > {
private scopes : ScopeStack
2026-07-06 17:01:14 +00:00
private readonly invokeTool : ( path : ReadonlyArray < string > , args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R >
2026-07-11 18:43:33 +00:00
private readonly invokeSearch : ( args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R >
2026-07-06 17:01:14 +00:00
private readonly toolKeys : ( path : ReadonlyArray < string > ) = > ReadonlyArray < string >
private readonly logs : Array < string >
2026-07-10 05:33:21 +00:00
private readonly promises : PromiseRuntime < R >
2026-07-12 17:47:56 +00:00
private readonly runner : CallbackRunner < R > = {
invokeFunction : ( fn , args ) = > this . invokeFunction ( fn , args ) ,
2026-07-14 20:25:39 +00:00
invokeCallable : ( callable , args , node ) = > this . invokeCallable ( callable , args , node ) ,
2026-07-12 17:47:56 +00:00
settlePromise : ( promise ) = > this . settlePromise ( promise ) ,
}
2026-07-06 17:01:14 +00:00
constructor (
invokeTool : ( path : ReadonlyArray < string > , args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R > ,
2026-07-11 18:43:33 +00:00
invokeSearch : ( args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R > ,
2026-07-06 17:01:14 +00:00
toolKeys : ( path : ReadonlyArray < string > ) = > ReadonlyArray < string > ,
2026-07-10 05:33:21 +00:00
promises : PromiseRuntime < R > ,
2026-07-06 17:01:14 +00:00
logs : Array < string > = [ ] ,
) {
const globalScope = new Map < string , Binding > ( )
2026-07-12 17:47:56 +00:00
this . scopes = new ScopeStack ( [ globalScope ] )
2026-07-06 17:01:14 +00:00
this . invokeTool = invokeTool
2026-07-11 18:43:33 +00:00
this . invokeSearch = invokeSearch
2026-07-06 17:01:14 +00:00
this . toolKeys = toolKeys
this . logs = logs
2026-07-10 05:33:21 +00:00
this . promises = promises
2026-07-06 17:01:14 +00:00
globalScope . set ( "tools" , { mutable : false , value : new ToolReference ( [ ] ) } )
2026-07-11 18:43:33 +00:00
globalScope . set ( "search" , { mutable : false , value : new SearchFunction ( ) } )
2026-07-06 17:01:14 +00:00
globalScope . set ( "Promise" , { mutable : false , value : new PromiseNamespace ( ) } )
globalScope . set ( "undefined" , { mutable : false , value : undefined } )
globalScope . set ( "Object" , { mutable : false , value : new GlobalNamespace ( "Object" ) } )
globalScope . set ( "Math" , { mutable : false , value : new GlobalNamespace ( "Math" ) } )
globalScope . set ( "JSON" , { mutable : false , value : new GlobalNamespace ( "JSON" ) } )
globalScope . set ( "Number" , { mutable : false , value : new CoercionFunction ( "Number" ) } )
globalScope . set ( "String" , { mutable : false , value : new CoercionFunction ( "String" ) } )
globalScope . set ( "Boolean" , { mutable : false , value : new CoercionFunction ( "Boolean" ) } )
globalScope . set ( "Array" , { mutable : false , value : new GlobalNamespace ( "Array" ) } )
globalScope . set ( "console" , { mutable : false , value : new GlobalNamespace ( "console" ) } )
globalScope . set ( "parseInt" , { mutable : false , value : new CoercionFunction ( "parseInt" ) } )
globalScope . set ( "parseFloat" , { mutable : false , value : new CoercionFunction ( "parseFloat" ) } )
globalScope . set ( "Date" , { mutable : false , value : new GlobalNamespace ( "Date" ) } )
globalScope . set ( "RegExp" , { mutable : false , value : new GlobalNamespace ( "RegExp" ) } )
globalScope . set ( "Map" , { mutable : false , value : new GlobalNamespace ( "Map" ) } )
globalScope . set ( "Set" , { mutable : false , value : new GlobalNamespace ( "Set" ) } )
globalScope . set ( "URL" , { mutable : false , value : new GlobalNamespace ( "URL" ) } )
globalScope . set ( "URLSearchParams" , { mutable : false , value : new GlobalNamespace ( "URLSearchParams" ) } )
globalScope . set ( "encodeURI" , { mutable : false , value : new UriFunction ( "encodeURI" ) } )
globalScope . set ( "encodeURIComponent" , { mutable : false , value : new UriFunction ( "encodeURIComponent" ) } )
globalScope . set ( "decodeURI" , { mutable : false , value : new UriFunction ( "decodeURI" ) } )
globalScope . set ( "decodeURIComponent" , { mutable : false , value : new UriFunction ( "decodeURIComponent" ) } )
for ( const name of errorConstructors ) {
globalScope . set ( name , { mutable : false , value : new ErrorConstructorReference ( name ) } )
}
globalScope . set ( "NaN" , { mutable : false , value : NaN } )
globalScope . set ( "Infinity" , { mutable : false , value : Infinity } )
}
run ( program : ProgramNode ) : Effect . Effect < unknown , unknown , R > {
const self = this
2026-07-12 17:47:56 +00:00
// Keep top-level declarations separate so they can shadow builtins.
this . scopes . push ( )
2026-07-06 17:01:14 +00:00
return Effect . gen ( function * ( ) {
2026-07-17 03:26:45 +00:00
self . predeclareLexical ( program . body )
2026-07-06 17:01:14 +00:00
self . hoistFunctions ( program . body )
let value : unknown = undefined
2026-07-07 16:44:50 +00:00
for ( const [ index , statement ] of program . body . entries ( ) ) {
if ( index === program . body . length - 1 && statement . type === "ExpressionStatement" ) {
value = yield * self . evaluateExpression ( getNode ( statement , "expression" ) )
break
}
2026-07-06 17:01:14 +00:00
const result = yield * self . evaluateStatement ( statement )
if ( result . kind === "return" ) {
value = result . value
break
}
if ( result . kind === "break" || result . kind === "continue" ) {
throw new InterpreterRuntimeError ( ` Unexpected ' ${ result . kind } ' outside of a loop. ` , statement )
}
}
2026-07-12 17:47:56 +00:00
// The implicit async body adopts returned promises before copy-out.
2026-07-13 21:38:11 +00:00
if ( value instanceof CodeModePromise ) value = yield * self . settlePromise ( value )
2026-07-06 17:01:14 +00:00
return value
2026-07-12 17:47:56 +00:00
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . scopes . pop ( ) ) ) )
2026-07-06 17:01:14 +00:00
}
2026-07-12 17:47:56 +00:00
// Fork at the call site so admission and hooks occur when the call is made.
2026-07-06 17:01:14 +00:00
private createToolCallPromise (
path : ReadonlyArray < string > ,
args : Array < unknown > ,
2026-07-13 21:38:11 +00:00
) : Effect . Effect < CodeModePromise , never , R > {
2026-07-12 21:07:53 +00:00
return this . createPromise ( Effect . suspend ( ( ) = > this . invokeTool ( path , args ) ) )
2026-07-06 21:29:11 +00:00
}
2026-07-13 21:38:11 +00:00
private createPromise ( effect : Effect.Effect < unknown , unknown , R > ) : Effect . Effect < CodeModePromise , never , R > {
2026-07-10 05:33:21 +00:00
return this . promises . create ( effect )
2026-07-06 17:01:14 +00:00
}
2026-07-12 17:47:56 +00:00
// Fiber exits make settlement idempotent; yielding prevents inline continuation.
2026-07-13 21:38:11 +00:00
private settlePromise ( promise : CodeModePromise ) : Effect . Effect < unknown , unknown , never > {
2026-07-10 05:33:21 +00:00
const promises = this . promises
return Effect . suspend ( ( ) = > {
promises . markObserved ( promise )
2026-07-10 23:22:04 +00:00
return Effect . flatMap ( promises . await ( promise ) , ( exit ) = > Effect . andThen ( Effect . yieldNow , exit ) )
2026-07-10 05:33:21 +00:00
} )
2026-07-06 17:01:14 +00:00
}
private evaluateStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
switch ( node . type ) {
case "ExpressionStatement" :
2026-07-07 16:44:50 +00:00
return Effect . as ( this . evaluateExpression ( getNode ( node , "expression" ) ) , { kind : "none" } )
2026-07-06 17:01:14 +00:00
case "VariableDeclaration" :
return Effect . map ( this . evaluateVariableDeclaration ( node ) , ( ) = > ( { kind : "none" } ) )
case "ReturnStatement" : {
const argumentNode = getOptionalNode ( node , "argument" )
return argumentNode
? Effect . map ( this . evaluateExpression ( argumentNode ) , ( value ) = > ( { kind : "return" , value } ) )
: Effect . succeed ( { kind : "return" , value : undefined } )
}
case "BlockStatement" :
return this . evaluateBlock ( node )
case "IfStatement" :
return this . evaluateIfStatement ( node )
case "SwitchStatement" :
return this . evaluateSwitchStatement ( node )
case "WhileStatement" :
return this . evaluateWhileStatement ( node )
case "DoWhileStatement" :
return this . evaluateDoWhileStatement ( node )
case "ForStatement" :
return this . evaluateForStatement ( node )
case "ForOfStatement" :
return this . evaluateForOfStatement ( node )
case "ForInStatement" :
return this . evaluateForInStatement ( node )
case "BreakStatement" :
return Effect . succeed ( this . evaluateBreakStatement ( node ) )
case "ContinueStatement" :
return Effect . succeed ( this . evaluateContinueStatement ( node ) )
case "ThrowStatement" :
return this . evaluateThrowStatement ( node )
case "TryStatement" :
return this . evaluateTryStatement ( node )
case "EmptyStatement" :
return Effect . succeed ( { kind : "none" } )
case "FunctionDeclaration" :
2026-07-12 17:47:56 +00:00
return Effect . succeed ( { kind : "none" } )
2026-07-06 17:01:14 +00:00
default :
throw unsupportedSyntax ( node . type , node )
}
}
private evaluateBlock ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
2026-07-12 17:47:56 +00:00
this . scopes . push ( )
2026-07-06 17:01:14 +00:00
const self = this
return Effect . gen ( function * ( ) {
const body = getArray ( node , "body" )
2026-07-17 03:26:45 +00:00
self . predeclareLexical ( body )
2026-07-06 17:01:14 +00:00
self . hoistFunctions ( body )
for ( const statementValue of body ) {
const statement = asNode ( statementValue , "body" )
const result = yield * self . evaluateStatement ( statement )
if ( result . kind !== "none" ) {
return result
}
}
return { kind : "none" } satisfies StatementResult
2026-07-12 17:47:56 +00:00
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . scopes . pop ( ) ) ) )
2026-07-06 17:01:14 +00:00
}
private createFunction ( node : AstNode ) : CodeModeFunction {
if ( node . generator === true ) {
throw new InterpreterRuntimeError (
"Generator functions are not supported in CodeMode." ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
}
return new CodeModeFunction (
getArray ( node , "params" ) . map ( ( parameter , index ) = > asNode ( parameter , ` params[ ${ index } ] ` ) ) ,
getNode ( node , "body" ) ,
2026-07-12 17:47:56 +00:00
this . scopes . capture ( ) ,
2026-07-06 21:29:11 +00:00
node . async === true ,
2026-07-06 17:01:14 +00:00
)
}
private hoistFunctions ( statements : Array < unknown > ) : void {
for ( const statementValue of statements ) {
if ( ! isRecord ( statementValue ) || statementValue . type !== "FunctionDeclaration" ) continue
const node = statementValue as AstNode
2026-07-12 17:47:56 +00:00
this . scopes . declare ( getString ( getNode ( node , "id" ) , "name" ) , this . createFunction ( node ) , true , node )
2026-07-06 17:01:14 +00:00
}
}
2026-07-17 03:26:45 +00:00
private predeclareLexical ( statements : Array < unknown > ) : void {
for ( const statementValue of statements ) {
if ( ! isRecord ( statementValue ) || statementValue . type !== "VariableDeclaration" ) continue
const statement = statementValue as AstNode
const kind = getString ( statement , "kind" )
if ( kind === "var" ) continue
for ( const declarationValue of getArray ( statement , "declarations" ) ) {
const declaration = asNode ( declarationValue , "declarations" )
for ( const name of collectPatternNames ( getNode ( declaration , "id" ) ) ) {
this . scopes . reserve ( name , kind !== "const" , declaration )
}
}
}
}
private predeclarePattern ( pattern : AstNode , mutable : boolean , node : AstNode ) : void {
for ( const name of collectPatternNames ( pattern ) ) this . scopes . reserve ( name , mutable , node )
}
2026-07-06 17:01:14 +00:00
private evaluateIfStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const testNode = getNode ( node , "test" )
const consequentNode = getNode ( node , "consequent" )
const alternateNode = getOptionalNode ( node , "alternate" )
return Effect . flatMap ( this . evaluateExpression ( testNode ) , ( test ) = >
test
? this . evaluateStatement ( consequentNode )
: alternateNode
? this . evaluateStatement ( alternateNode )
: Effect . succeed ( { kind : "none" } ) ,
)
}
private evaluateSwitchStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const self = this
return Effect . gen ( function * ( ) {
const discriminant = yield * self . evaluateExpression ( getNode ( node , "discriminant" ) )
if ( containsOpaqueReference ( discriminant ) ) {
throw new InterpreterRuntimeError (
"Switch discriminants must be data values in CodeMode." ,
node ,
"InvalidDataValue" ,
)
}
2026-07-17 03:26:45 +00:00
self . scopes . push ( )
return yield * Effect . gen ( function * ( ) {
const cases = getArray ( node , "cases" ) . map ( ( value , index ) = > asNode ( value , ` cases[ ${ index } ] ` ) )
self . predeclareLexical ( cases . flatMap ( ( branch ) = > getArray ( branch , "consequent" ) ) )
let defaultIndex : number | undefined
let selected : number | undefined
for ( const [ index , branch ] of cases . entries ( ) ) {
const test = getOptionalNode ( branch , "test" )
if ( ! test ) {
defaultIndex = index
continue
}
const candidate = yield * self . evaluateExpression ( test )
if ( containsOpaqueReference ( candidate ) ) {
throw new InterpreterRuntimeError (
"Switch case values must be data values in CodeMode." ,
test ,
"InvalidDataValue" ,
)
}
if ( candidate === discriminant ) {
selected = index
break
}
2026-07-06 17:01:14 +00:00
}
2026-07-17 03:26:45 +00:00
const start = selected ? ? defaultIndex
if ( start === undefined ) return { kind : "none" } satisfies StatementResult
for ( let index = start ; index < cases . length ; index += 1 ) {
for ( const statementValue of getArray ( cases [ index ] ! , "consequent" ) ) {
const result = yield * self . evaluateStatement ( asNode ( statementValue , "consequent" ) )
if ( result . kind === "break" ) return { kind : "none" } satisfies StatementResult
if ( result . kind === "return" || result . kind === "continue" ) return result
}
2026-07-06 17:01:14 +00:00
}
2026-07-17 03:26:45 +00:00
return { kind : "none" } satisfies StatementResult
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . scopes . pop ( ) ) ) )
} )
2026-07-06 17:01:14 +00:00
}
private evaluateWhileStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const testNode = getNode ( node , "test" )
const bodyNode = getNode ( node , "body" )
const self = this
return Effect . gen ( function * ( ) {
while ( yield * self . evaluateExpression ( testNode ) ) {
const result = yield * self . evaluateStatement ( bodyNode )
if ( result . kind === "continue" ) {
continue
}
if ( result . kind === "break" ) {
return { kind : "none" } satisfies StatementResult
}
if ( result . kind === "return" ) {
return result
}
}
return { kind : "none" } satisfies StatementResult
} )
}
private evaluateDoWhileStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const bodyNode = getNode ( node , "body" )
const testNode = getNode ( node , "test" )
const self = this
return Effect . gen ( function * ( ) {
do {
const result = yield * self . evaluateStatement ( bodyNode )
if ( result . kind === "continue" ) {
continue
}
if ( result . kind === "break" ) {
return { kind : "none" } satisfies StatementResult
}
if ( result . kind === "return" ) {
return result
}
} while ( yield * self . evaluateExpression ( testNode ) )
return { kind : "none" } satisfies StatementResult
} )
}
private evaluateForStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
2026-07-12 17:47:56 +00:00
this . scopes . push ( )
2026-07-06 17:01:14 +00:00
const self = this
return Effect . gen ( function * ( ) {
const initNode = getOptionalNode ( node , "init" )
const testNode = getOptionalNode ( node , "test" )
const updateNode = getOptionalNode ( node , "update" )
const bodyNode = getNode ( node , "body" )
2026-07-17 03:26:45 +00:00
if ( initNode ? . type === "VariableDeclaration" && getString ( initNode , "kind" ) !== "var" ) {
self . predeclareLexical ( [ initNode ] )
}
2026-07-06 17:01:14 +00:00
if ( initNode ) {
if ( initNode . type === "VariableDeclaration" ) {
yield * self . evaluateVariableDeclaration ( initNode )
} else {
yield * self . evaluateExpression ( initNode )
}
}
const perIterationBindings =
initNode ? . type === "VariableDeclaration" && getString ( initNode , "kind" ) !== "var"
2026-07-12 17:47:56 +00:00
? Array . from ( self . scopes . current ( ) . keys ( ) )
2026-07-06 17:01:14 +00:00
: [ ]
2026-07-17 03:26:45 +00:00
const nextIteration = ( ) = > {
if ( perIterationBindings . length === 0 ) return
const current = self . scopes . current ( )
self . scopes . pop ( )
self . scopes . push (
new Map ( perIterationBindings . map ( ( name ) : [ string , Binding ] = > [ name , { . . . current . get ( name ) ! } ] ) ) ,
2026-07-06 17:01:14 +00:00
)
2026-07-17 03:26:45 +00:00
}
nextIteration ( )
while ( testNode ? yield * self . evaluateExpression ( testNode ) : true ) {
const result = yield * self . evaluateStatement ( bodyNode )
2026-07-06 17:01:14 +00:00
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "break" ) {
return { kind : "none" } satisfies StatementResult
}
2026-07-17 03:26:45 +00:00
nextIteration ( )
2026-07-06 17:01:14 +00:00
if ( updateNode ) {
yield * self . evaluateExpression ( updateNode )
}
if ( result . kind === "continue" ) {
continue
}
}
return { kind : "none" } satisfies StatementResult
2026-07-12 17:47:56 +00:00
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . scopes . pop ( ) ) ) )
2026-07-06 17:01:14 +00:00
}
private evaluateForOfStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
if ( getBoolean ( node , "await" ) ) {
throw new InterpreterRuntimeError ( "for await...of is not supported." , node )
}
2026-07-17 03:26:45 +00:00
const left = getNode ( node , "left" )
const declared = loopDeclaration ( left , "for...of" )
if ( declared ? . lexical ) this . scopes . push ( )
2026-07-06 17:01:14 +00:00
const self = this
return Effect . gen ( function * ( ) {
2026-07-17 03:26:45 +00:00
if ( declared ? . lexical ) self . predeclarePattern ( declared . pattern , declared . mutable , left )
2026-07-06 17:01:14 +00:00
const right = yield * self . evaluateExpression ( getNode ( node , "right" ) )
const body = getNode ( node , "body" )
2026-07-10 23:22:04 +00:00
const iterable = spreadItems ( right )
2026-07-06 17:01:14 +00:00
if ( iterable === undefined ) {
throw new InterpreterRuntimeError ( "for...of requires an array, string, Map, or Set value in CodeMode." , node )
}
2026-07-06 21:59:13 +00:00
let assignment : AstNode | undefined
2026-07-06 17:01:14 +00:00
2026-07-17 03:26:45 +00:00
if (
left . type !== "VariableDeclaration" &&
( left . type === "Identifier" ||
left . type === "MemberExpression" ||
left . type === "ArrayPattern" ||
left . type === "ObjectPattern" )
2026-07-06 21:59:13 +00:00
) {
assignment = left
2026-07-17 03:26:45 +00:00
} else if ( left . type !== "VariableDeclaration" ) {
2026-07-06 17:01:14 +00:00
throw new InterpreterRuntimeError ( "Unsupported for...of binding." , left )
}
for ( const value of iterable ) {
2026-07-17 03:26:45 +00:00
const result = yield * Effect . gen ( function * ( ) {
if ( declared ) {
self . scopes . push ( )
if ( declared . lexical ) self . predeclarePattern ( declared . pattern , declared . mutable , left )
yield * self . declarePattern ( declared . pattern , value , declared . mutable , left , declared . lexical )
} else if ( assignment ) {
yield * self . assignPattern ( assignment , value , left )
}
return yield * self . evaluateStatement ( body )
} ) . pipe (
2026-07-06 17:01:14 +00:00
Effect . ensuring (
Effect . sync ( ( ) = > {
2026-07-17 03:26:45 +00:00
if ( declared ) self . scopes . pop ( )
2026-07-06 17:01:14 +00:00
} ) ,
) ,
)
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "break" ) {
2026-07-17 03:26:45 +00:00
return { kind : "none" } satisfies StatementResult
2026-07-06 17:01:14 +00:00
}
if ( result . kind === "continue" ) {
continue
}
}
2026-07-17 03:26:45 +00:00
return { kind : "none" } satisfies StatementResult
} ) . pipe (
Effect . ensuring (
Effect . sync ( ( ) = > {
if ( declared ? . lexical ) self . scopes . pop ( )
} ) ,
) ,
)
2026-07-06 17:01:14 +00:00
}
private enumerableKeys ( value : unknown ) : Array < string > | undefined {
if ( value instanceof ToolReference ) {
return [ . . . this . toolKeys ( value . path ) ]
}
if ( Array . isArray ( value ) ) {
return Object . keys ( value )
}
if ( value !== null && typeof value === "object" && ! isRuntimeReference ( value ) ) {
return Object . keys ( value )
}
return undefined
}
private evaluateForInStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
2026-07-17 03:26:45 +00:00
const left = getNode ( node , "left" )
const declared = loopDeclaration ( left , "for...in" )
if ( declared ? . lexical ) this . scopes . push ( )
2026-07-06 17:01:14 +00:00
const self = this
return Effect . gen ( function * ( ) {
2026-07-17 03:26:45 +00:00
if ( declared ? . lexical ) self . predeclarePattern ( declared . pattern , declared . mutable , left )
2026-07-06 17:01:14 +00:00
const right = yield * self . evaluateExpression ( getNode ( node , "right" ) )
const body = getNode ( node , "body" )
const keys = self . enumerableKeys ( right )
if ( keys === undefined ) {
throw new InterpreterRuntimeError (
"for...in requires a plain object, array, or tools reference in CodeMode. Use for...of for arrays/strings/Maps/Sets, or Object.keys(value) for a key list." ,
node ,
)
}
let assignmentName : string | undefined
2026-07-17 03:26:45 +00:00
if ( left . type === "Identifier" ) {
2026-07-06 17:01:14 +00:00
assignmentName = getString ( left , "name" )
2026-07-17 03:26:45 +00:00
} else if ( left . type !== "VariableDeclaration" ) {
2026-07-06 17:01:14 +00:00
throw new InterpreterRuntimeError ( "Unsupported for...in binding." , left )
}
for ( const key of keys ) {
2026-07-17 03:26:45 +00:00
const result = yield * Effect . gen ( function * ( ) {
if ( declared ) {
self . scopes . push ( )
if ( declared . lexical ) self . predeclarePattern ( declared . pattern , declared . mutable , left )
yield * self . declarePattern ( declared . pattern , key , declared . mutable , left , declared . lexical )
} else if ( assignmentName ) {
self . scopes . set ( assignmentName , key , left )
}
return yield * self . evaluateStatement ( body )
} ) . pipe (
2026-07-06 17:01:14 +00:00
Effect . ensuring (
Effect . sync ( ( ) = > {
2026-07-17 03:26:45 +00:00
if ( declared ) self . scopes . pop ( )
2026-07-06 17:01:14 +00:00
} ) ,
) ,
)
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "break" ) {
2026-07-17 03:26:45 +00:00
return { kind : "none" } satisfies StatementResult
2026-07-06 17:01:14 +00:00
}
if ( result . kind === "continue" ) {
continue
}
}
2026-07-17 03:26:45 +00:00
return { kind : "none" } satisfies StatementResult
} ) . pipe (
Effect . ensuring (
Effect . sync ( ( ) = > {
if ( declared ? . lexical ) self . scopes . pop ( )
} ) ,
) ,
)
2026-07-06 17:01:14 +00:00
}
private evaluateBreakStatement ( node : AstNode ) : StatementResult {
const labelNode = getOptionalNode ( node , "label" )
if ( labelNode ) {
throw new InterpreterRuntimeError ( "Labeled break is not supported in v1." , node )
}
return { kind : "break" }
}
private evaluateContinueStatement ( node : AstNode ) : StatementResult {
const labelNode = getOptionalNode ( node , "label" )
if ( labelNode ) {
throw new InterpreterRuntimeError ( "Labeled continue is not supported in v1." , node )
}
return { kind : "continue" }
}
private evaluateThrowStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const argument = getNode ( node , "argument" )
return Effect . flatMap ( this . evaluateExpression ( argument ) , ( value ) = > Effect . fail ( new ProgramThrow ( value ) ) )
}
private evaluateTryStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const body = getNode ( node , "block" )
const handler = getOptionalNode ( node , "handler" )
const finalizer = getOptionalNode ( node , "finalizer" )
const self = this
const attempted = Effect . matchCauseEffect ( this . evaluateStatement ( body ) , {
onFailure : ( cause ) = > {
if ( cause . reasons . some ( Cause . isInterruptReason ) || ! handler ) {
return Effect . failCause ( cause )
}
const caught = caughtErrorValue ( Cause . squash ( cause ) )
const parameter = getOptionalNode ( handler , "param" )
2026-07-12 17:47:56 +00:00
self . scopes . push ( )
2026-07-06 17:01:14 +00:00
return Effect . gen ( function * ( ) {
if ( parameter ) yield * self . declarePattern ( parameter , caught , true , handler )
return yield * self . evaluateStatement ( getNode ( handler , "body" ) )
2026-07-12 17:47:56 +00:00
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . scopes . pop ( ) ) ) )
2026-07-06 17:01:14 +00:00
} ,
onSuccess : Effect.succeed ,
} )
if ( ! finalizer ) return attempted
const isAbrupt = ( result : StatementResult ) : boolean = >
result . kind === "return" || result . kind === "break" || result . kind === "continue"
return Effect . matchCauseEffect ( attempted , {
onFailure : ( cause ) = >
cause . reasons . some ( Cause . isInterruptReason )
? Effect . failCause ( cause )
: Effect . flatMap ( this . evaluateStatement ( finalizer ) , ( final ) = >
isAbrupt ( final ) ? Effect . succeed ( final ) : Effect . failCause ( cause ) ,
) ,
onSuccess : ( result ) = >
Effect . flatMap ( this . evaluateStatement ( finalizer ) , ( final ) = >
isAbrupt ( final ) ? Effect . succeed ( final ) : Effect . succeed ( result ) ,
) ,
} )
}
private evaluateVariableDeclaration ( node : AstNode ) : Effect . Effect < void , unknown , R > {
const kind = getString ( node , "kind" )
const declarations = getArray ( node , "declarations" )
const self = this
return Effect . gen ( function * ( ) {
for ( const declarationValue of declarations ) {
const declaration = asNode ( declarationValue , "declarations" )
if ( declaration . type !== "VariableDeclarator" ) {
throw new InterpreterRuntimeError ( "Unsupported variable declaration shape." , declaration )
}
const init = getOptionalNode ( declaration , "init" )
const value = init ? yield * self . evaluateExpression ( init ) : undefined
2026-07-17 03:26:45 +00:00
yield * self . declarePattern ( getNode ( declaration , "id" ) , value , kind !== "const" , declaration , kind !== "var" )
2026-07-06 17:01:14 +00:00
}
} )
}
private declarePattern (
pattern : AstNode ,
value : unknown ,
mutable : boolean ,
node : AstNode ,
2026-07-17 03:26:45 +00:00
initialize = false ,
2026-07-06 17:01:14 +00:00
) : Effect . Effect < void , unknown , R > {
const self = this
return Effect . gen ( function * ( ) {
if ( pattern . type === "Identifier" ) {
2026-07-17 03:26:45 +00:00
const name = getString ( pattern , "name" )
if ( initialize ) self . scopes . initialize ( name , value , node )
else self . scopes . declare ( name , value , mutable , node )
2026-07-06 17:01:14 +00:00
return
}
if ( pattern . type === "AssignmentPattern" ) {
const resolved = value === undefined ? yield * self . evaluateExpression ( getNode ( pattern , "right" ) ) : value
2026-07-17 03:26:45 +00:00
yield * self . declarePattern ( getNode ( pattern , "left" ) , resolved , mutable , node , initialize )
2026-07-06 17:01:14 +00:00
return
}
if ( pattern . type === "ObjectPattern" ) {
2026-07-16 17:57:37 +00:00
if ( value === null || typeof value !== "object" || isRuntimeReference ( value ) ) {
2026-07-06 17:01:14 +00:00
throw new InterpreterRuntimeError (
2026-07-16 17:57:37 +00:00
"Object destructuring requires a data object or array value." ,
2026-07-06 17:01:14 +00:00
pattern ,
"InvalidDataValue" ,
)
}
const consumed = new Set < string > ( )
for ( const propertyValue of getArray ( pattern , "properties" ) ) {
const property = asNode ( propertyValue , "properties" )
if ( property . type === "RestElement" ) {
const rest : SafeObject = Object . create ( null ) as SafeObject
for ( const [ key , item ] of Object . entries ( value as SafeObject ) ) {
if ( ! consumed . has ( key ) && ! isBlockedMember ( key ) ) rest [ key ] = item
}
2026-07-17 03:26:45 +00:00
yield * self . declarePattern ( getNode ( property , "argument" ) , rest , mutable , property , initialize )
2026-07-06 17:01:14 +00:00
continue
}
2026-07-16 17:57:37 +00:00
const key = yield * self . destructuringPropertyKey ( property )
if ( isBlockedMember ( String ( key ) ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ String ( key ) } ' is not available in CodeMode. ` , property )
2026-07-06 17:01:14 +00:00
}
2026-07-16 17:57:37 +00:00
consumed . add ( String ( key ) )
yield * self . declarePattern (
getNode ( property , "value" ) ,
self . destructuringPropertyValue ( value as SafeObject | Array < unknown > , key ) ,
mutable ,
property ,
2026-07-17 03:26:45 +00:00
initialize ,
2026-07-16 17:57:37 +00:00
)
2026-07-06 17:01:14 +00:00
}
return
}
if ( pattern . type === "ArrayPattern" ) {
2026-07-16 17:57:37 +00:00
const items = spreadItems ( value )
if ( items === undefined ) {
throw new InterpreterRuntimeError ( "Array destructuring requires a supported iterable value." , pattern )
2026-07-06 17:01:14 +00:00
}
for ( const [ index , item ] of getArray ( pattern , "elements" ) . entries ( ) ) {
if ( item === null ) continue
const element = asNode ( item , ` elements[ ${ index } ] ` )
if ( element . type === "RestElement" ) {
2026-07-17 03:26:45 +00:00
yield * self . declarePattern ( getNode ( element , "argument" ) , items . slice ( index ) , mutable , element , initialize )
2026-07-06 17:01:14 +00:00
break
}
2026-07-17 03:26:45 +00:00
yield * self . declarePattern ( element , items [ index ] , mutable , pattern , initialize )
2026-07-06 17:01:14 +00:00
}
return
}
throw new InterpreterRuntimeError ( ` Unsupported binding pattern ' ${ pattern . type } '. ` , pattern )
} )
}
2026-07-06 19:56:52 +00:00
private assignPattern ( pattern : AstNode , value : unknown , node : AstNode ) : Effect . Effect < void , unknown , R > {
const self = this
return Effect . gen ( function * ( ) {
if ( pattern . type === "Identifier" ) {
2026-07-12 17:47:56 +00:00
self . scopes . set ( getString ( pattern , "name" ) , value , pattern )
2026-07-06 19:56:52 +00:00
return
}
if ( pattern . type === "MemberExpression" ) {
yield * self . writeMember ( pattern , value )
return
}
if ( pattern . type === "AssignmentPattern" ) {
const resolved = value === undefined ? yield * self . evaluateExpression ( getNode ( pattern , "right" ) ) : value
yield * self . assignPattern ( getNode ( pattern , "left" ) , resolved , node )
return
}
if ( pattern . type === "ObjectPattern" ) {
2026-07-16 17:57:37 +00:00
if ( value === null || typeof value !== "object" || isRuntimeReference ( value ) ) {
2026-07-06 19:56:52 +00:00
throw new InterpreterRuntimeError (
2026-07-16 17:57:37 +00:00
"Object destructuring requires a data object or array value." ,
2026-07-06 19:56:52 +00:00
pattern ,
"InvalidDataValue" ,
)
}
2026-07-16 17:57:37 +00:00
const source = value as SafeObject | Array < unknown >
2026-07-06 19:56:52 +00:00
const consumed = new Set < string > ( )
for ( const propertyValue of getArray ( pattern , "properties" ) ) {
const property = asNode ( propertyValue , "properties" )
if ( property . type === "RestElement" ) {
const rest : SafeObject = Object . create ( null ) as SafeObject
for ( const [ key , item ] of Object . entries ( source ) ) {
if ( ! consumed . has ( key ) && ! isBlockedMember ( key ) ) rest [ key ] = item
}
yield * self . assignPattern ( getNode ( property , "argument" ) , rest , property )
continue
}
2026-07-16 17:57:37 +00:00
const key = yield * self . destructuringPropertyKey ( property )
if ( isBlockedMember ( String ( key ) ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ String ( key ) } ' is not available in CodeMode. ` , property )
2026-07-06 19:56:52 +00:00
}
2026-07-16 17:57:37 +00:00
consumed . add ( String ( key ) )
yield * self . assignPattern ( getNode ( property , "value" ) , self . destructuringPropertyValue ( source , key ) , property )
2026-07-06 19:56:52 +00:00
}
return
}
if ( pattern . type === "ArrayPattern" ) {
2026-07-16 17:57:37 +00:00
const items = spreadItems ( value )
if ( items === undefined ) {
throw new InterpreterRuntimeError ( "Array destructuring requires a supported iterable value." , pattern )
2026-07-06 19:56:52 +00:00
}
for ( const [ index , item ] of getArray ( pattern , "elements" ) . entries ( ) ) {
if ( item === null ) continue
const element = asNode ( item , ` elements[ ${ index } ] ` )
if ( element . type === "RestElement" ) {
2026-07-16 17:57:37 +00:00
yield * self . assignPattern ( getNode ( element , "argument" ) , items . slice ( index ) , element )
2026-07-06 19:56:52 +00:00
break
}
2026-07-16 17:57:37 +00:00
yield * self . assignPattern ( element , items [ index ] , pattern )
2026-07-06 19:56:52 +00:00
}
return
}
throw new InterpreterRuntimeError ( ` Unsupported assignment pattern ' ${ pattern . type } '. ` , node )
} )
}
2026-07-16 17:57:37 +00:00
private destructuringPropertyKey ( property : AstNode ) : Effect . Effect < string | number , unknown , R > {
if ( property . type !== "Property" || getString ( property , "kind" ) !== "init" ) {
throw new InterpreterRuntimeError ( "Unsupported object destructuring property." , property )
}
const keyNode = getNode ( property , "key" )
if ( getBoolean ( property , "computed" ) ) {
return Effect . map ( this . evaluateExpression ( keyNode ) , ( value ) = > this . toPropertyKey ( value , keyNode ) )
}
return Effect . succeed ( keyNode . type === "Identifier" ? getString ( keyNode , "name" ) : String ( keyNode . value ) )
}
private destructuringPropertyValue ( source : SafeObject | Array < unknown > , key : string | number ) : unknown {
if ( ! Array . isArray ( source ) ) return source [ String ( key ) ]
if ( key === "length" ) return source . length
if ( typeof key === "number" ) return source [ key ]
if ( Object . hasOwn ( source , key ) ) return ( source as Record < string , unknown > & Array < unknown > ) [ key ]
if ( arrayMethods . has ( key ) ) return new IntrinsicReference ( source , key )
return undefined
}
2026-07-06 17:01:14 +00:00
private evaluateExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
switch ( node . type ) {
case "Literal" : {
const regex = node . regex
if ( isRecord ( regex ) && typeof regex . pattern === "string" ) {
return Effect . sync ( ( ) = >
this . constructRegExp ( [ regex . pattern , typeof regex . flags === "string" ? regex . flags : "" ] , node ) ,
)
}
return Effect . sync ( ( ) = > boundedData ( node . value , "Literal" ) )
}
case "Identifier" :
2026-07-12 17:47:56 +00:00
return Effect . sync ( ( ) = > this . scopes . get ( getString ( node , "name" ) , node ) )
2026-07-06 17:01:14 +00:00
case "BinaryExpression" :
return this . evaluateBinaryExpression ( node )
case "LogicalExpression" :
return this . evaluateLogicalExpression ( node )
case "UnaryExpression" :
return this . evaluateUnaryExpression ( node )
case "AssignmentExpression" :
return this . evaluateAssignmentExpression ( node )
2026-07-06 21:59:13 +00:00
case "SequenceExpression" : {
const self = this
return Effect . gen ( function * ( ) {
let result : unknown
for ( const expression of getArray ( node , "expressions" ) ) {
result = yield * self . evaluateExpression ( asNode ( expression , "expressions" ) )
}
return result
} )
}
2026-07-06 17:01:14 +00:00
case "CallExpression" :
return this . evaluateCallExpression ( node )
case "ArrowFunctionExpression" :
case "FunctionExpression" :
return Effect . sync ( ( ) = > this . createFunction ( node ) )
case "MemberExpression" :
return this . readMember ( node )
case "ChainExpression" :
return Effect . map ( this . evaluateExpression ( getNode ( node , "expression" ) ) , ( value ) = >
value === OptionalShortCircuit ? undefined : value ,
)
case "ObjectExpression" :
return this . evaluateObjectExpression ( node )
case "ArrayExpression" :
return this . evaluateArrayExpression ( node )
case "TemplateLiteral" :
return this . evaluateTemplateLiteral ( node )
case "ConditionalExpression" :
return this . evaluateConditionalExpression ( node )
case "UpdateExpression" :
return this . evaluateUpdateExpression ( node )
case "AwaitExpression" : {
2026-07-12 17:47:56 +00:00
// Await always suspends, including for plain values.
2026-07-06 17:01:14 +00:00
const self = this
return Effect . flatMap ( this . evaluateExpression ( getNode ( node , "argument" ) ) , ( value ) = >
2026-07-13 21:38:11 +00:00
value instanceof CodeModePromise ? self . settlePromise ( value ) : Effect . as ( Effect . yieldNow , value ) ,
2026-07-06 17:01:14 +00:00
)
}
case "NewExpression" :
return this . evaluateNewExpression ( node )
default :
throw unsupportedSyntax ( node . type , node )
}
}
private evaluateNewExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const callee = getNode ( node , "callee" )
if ( callee . type !== "Identifier" ) {
throw unsupportedSyntax ( "NewExpression" , node )
}
const name = getString ( callee , "name" )
const argNodes = getArray ( node , "arguments" )
const self = this
if ( name === "Promise" ) {
2026-07-12 17:47:56 +00:00
return Effect . flatMap ( this . evaluateCallArguments ( argNodes ) , ( args ) = >
constructPromise ( self . runner , self . promises , args [ 0 ] , node ) ,
)
2026-07-06 17:01:14 +00:00
}
if ( errorConstructors . has ( name ) ) {
2026-07-11 01:46:13 +00:00
return Effect . map ( this . evaluateCallArguments ( argNodes ) , ( args ) = > constructErrorValue ( name , args , node ) )
2026-07-06 17:01:14 +00:00
}
2026-07-14 20:25:39 +00:00
// Array and Object construct identically with or without new, like JS.
if ( name === "Array" ) {
return Effect . map ( this . evaluateCallArguments ( argNodes ) , ( args ) = > self . constructArray ( args , node ) )
}
if ( name === "Object" ) {
return Effect . map ( this . evaluateCallArguments ( argNodes ) , ( args ) = > self . constructObject ( args , node ) )
}
2026-07-06 17:01:14 +00:00
if ( valueConstructors . has ( name ) ) {
return Effect . gen ( function * ( ) {
const args = yield * self . evaluateCallArguments ( argNodes )
switch ( name ) {
case "Date" :
return self . constructDate ( args )
case "RegExp" :
return self . constructRegExp ( args , node )
case "Map" :
return self . constructMap ( args [ 0 ] , node )
case "Set" :
return self . constructSet ( args [ 0 ] , node )
case "URL" :
return self . constructURL ( args , node )
default :
return self . constructURLSearchParams ( args [ 0 ] , node )
}
} )
}
throw unsupportedSyntax ( "NewExpression" , node )
}
2026-07-14 20:25:39 +00:00
private constructArray ( args : Array < unknown > , node : AstNode ) : Array < unknown > {
if ( args . length !== 1 ) return [ . . . args ]
const first = args [ 0 ]
if ( typeof first !== "number" ) return [ first ]
if ( ! Number . isInteger ( first ) || first < 0 || first > 4294967295 ) {
throw new InterpreterRuntimeError ( "Invalid array length." , node ) . as ( "RangeError" )
}
// Sparse like JS: Array(3) has holes, and combinator loops already skip them.
return new Array ( first )
}
private constructObject ( args : Array < unknown > , node : AstNode ) : unknown {
const first = args [ 0 ]
if ( first === null || first === undefined ) return { }
if ( typeof first === "object" ) return first
throw new InterpreterRuntimeError (
` Object( ${ typeof first } ) wrapper objects are not supported in CodeMode; use the primitive value directly. ` ,
node ,
)
}
2026-07-13 21:38:11 +00:00
private constructDate ( args : Array < unknown > ) : CodeModeDate {
if ( args . length === 0 ) return new CodeModeDate ( Date . now ( ) )
2026-07-06 17:01:14 +00:00
if ( args . length === 1 ) {
const arg = args [ 0 ]
2026-07-13 21:38:11 +00:00
if ( arg instanceof CodeModeDate ) return new CodeModeDate ( arg . time )
if ( typeof arg === "number" ) return new CodeModeDate ( new Date ( arg ) . getTime ( ) )
if ( typeof arg === "string" ) return new CodeModeDate ( Date . parse ( arg ) )
return new CodeModeDate ( Number . NaN )
2026-07-06 17:01:14 +00:00
}
const parts = args . map ( ( arg ) = > coerceToNumber ( arg ) )
2026-07-13 21:38:11 +00:00
return new CodeModeDate ( new Date ( . . . ( parts as [ number , number ] ) ) . getTime ( ) )
2026-07-06 17:01:14 +00:00
}
2026-07-13 21:38:11 +00:00
private constructRegExp ( args : Array < unknown > , node : AstNode ) : CodeModeRegExp {
2026-07-06 17:01:14 +00:00
const first = args [ 0 ]
const pattern =
2026-07-13 21:38:11 +00:00
first instanceof CodeModeRegExp ? first.regex.source : first === undefined ? "" : coerceToString ( first )
2026-07-06 17:01:14 +00:00
const flagsArg = args [ 1 ]
if ( flagsArg !== undefined && typeof flagsArg !== "string" ) {
throw new InterpreterRuntimeError (
` RegExp flags must be a string of flag characters (e.g. "g", "gi"), not ${ flagsArg === null ? "null" : typeof flagsArg } . ` ,
node ,
2026-07-14 20:25:39 +00:00
) . as ( "SyntaxError" )
2026-07-06 17:01:14 +00:00
}
2026-07-13 21:38:11 +00:00
const flags = flagsArg ? ? ( first instanceof CodeModeRegExp ? first . regex . flags : "" )
2026-07-06 17:01:14 +00:00
try {
2026-07-13 21:38:11 +00:00
return new CodeModeRegExp ( pattern , flags )
2026-07-06 17:01:14 +00:00
} catch ( error ) {
const reason = regexFailureReason ( error )
throw new InterpreterRuntimeError (
/flag/i . test ( reason )
? ` new RegExp(...) received invalid flags ${ JSON . stringify ( flags ) } ( ${ reason } ). Valid flags are d, g, i, m, s, u, v, and y. `
: ` new RegExp(...) received ${ JSON . stringify ( pattern ) } , which is not a valid regular expression pattern ( ${ reason } ). ${ escapeRegexHint } ` ,
node ,
) . as ( "SyntaxError" )
}
}
2026-07-13 21:38:11 +00:00
private constructMap ( init : unknown , node : AstNode ) : CodeModeMap {
const target = new CodeModeMap ( )
2026-07-06 17:01:14 +00:00
if ( init === undefined || init === null ) return target
const entries = Array . isArray ( init )
? init
2026-07-13 21:38:11 +00:00
: init instanceof CodeModeMap
2026-07-06 17:01:14 +00:00
? Array . from ( init . map . entries ( ) , ( [ key , item ] ) : Array < unknown > = > [ key , item ] )
: undefined
if ( entries === undefined ) {
throw new InterpreterRuntimeError (
"new Map(...) expects an array of [key, value] pairs, a Map, or no argument." ,
node ,
)
}
for ( const pair of entries ) {
if ( ! Array . isArray ( pair ) ) {
throw new InterpreterRuntimeError ( "new Map(...) expects [key, value] pairs." , node )
}
target . map . set ( pair [ 0 ] , pair [ 1 ] )
}
return target
}
2026-07-13 21:38:11 +00:00
private constructSet ( init : unknown , node : AstNode ) : CodeModeSet {
const target = new CodeModeSet ( )
2026-07-06 17:01:14 +00:00
if ( init === undefined || init === null ) return target
const items = Array . isArray ( init )
? init
2026-07-13 21:38:11 +00:00
: init instanceof CodeModeSet
2026-07-06 17:01:14 +00:00
? Array . from ( init . set . values ( ) )
: typeof init === "string"
? Array . from ( init )
: undefined
if ( items === undefined ) {
throw new InterpreterRuntimeError ( "new Set(...) expects an array, Set, string, or no argument." , node )
}
for ( const item of items ) target . set . add ( item )
return target
}
2026-07-13 21:38:11 +00:00
private constructURL ( args : Array < unknown > , node : AstNode ) : CodeModeURL {
2026-07-06 17:01:14 +00:00
if ( args . length === 0 ) {
throw new InterpreterRuntimeError ( "new URL(...) requires a URL string and an optional base URL." , node ) . as (
"TypeError" ,
)
}
const input = urlArgument ( args [ 0 ] , "new URL input" )
const base = args [ 1 ] === undefined ? undefined : urlArgument ( args [ 1 ] , "new URL base" )
try {
2026-07-13 21:38:11 +00:00
return new CodeModeURL ( new URL ( input , base ) )
2026-07-06 17:01:14 +00:00
} catch {
throw new InterpreterRuntimeError (
` new URL(...) received an invalid URL ${ base === undefined ? "" : " or base URL" } . ` ,
node ,
) . as ( "TypeError" )
}
}
2026-07-13 21:38:11 +00:00
private constructURLSearchParams ( init : unknown , node : AstNode ) : CodeModeURLSearchParams {
if ( init === undefined ) return new CodeModeURLSearchParams ( new URLSearchParams ( ) )
if ( init instanceof CodeModeURLSearchParams ) {
return new CodeModeURLSearchParams ( new URLSearchParams ( init . params ) )
2026-07-06 17:01:14 +00:00
}
2026-07-13 21:38:11 +00:00
if ( typeof init === "string" ) return new CodeModeURLSearchParams ( new URLSearchParams ( init ) )
2026-07-06 17:01:14 +00:00
if ( init === null || typeof init === "number" || typeof init === "boolean" ) {
2026-07-13 21:38:11 +00:00
return new CodeModeURLSearchParams ( new URLSearchParams ( coerceToString ( init ) ) )
2026-07-06 17:01:14 +00:00
}
2026-07-13 21:38:11 +00:00
if ( init instanceof CodeModeMap ) {
2026-07-06 17:01:14 +00:00
return this . constructURLSearchParams (
Array . from ( init . map . entries ( ) , ( [ key , value ] ) = > [ key , value ] ) ,
node ,
)
}
if ( Array . isArray ( init ) ) {
const entries = init . map ( ( pair ) = > {
if ( ! Array . isArray ( pair ) || pair . length !== 2 ) {
throw new InterpreterRuntimeError (
"new URLSearchParams(...) expects an array of [name, value] pairs." ,
node ,
) . as ( "TypeError" )
}
return [ uriArgument ( pair [ 0 ] , "URLSearchParams name" ) , uriArgument ( pair [ 1 ] , "URLSearchParams value" ) ] as [
string ,
string ,
]
} )
2026-07-13 21:38:11 +00:00
return new CodeModeURLSearchParams ( new URLSearchParams ( entries ) )
2026-07-06 17:01:14 +00:00
}
2026-07-13 21:38:11 +00:00
if ( isCodeModeValue ( init ) ) return new CodeModeURLSearchParams ( new URLSearchParams ( ) )
2026-07-06 17:01:14 +00:00
const data = boundedData ( init , "new URLSearchParams input" )
if ( data === null || typeof data !== "object" ) {
throw new InterpreterRuntimeError (
"new URLSearchParams(...) expects a query string, data object, array of pairs, or URLSearchParams." ,
node ,
) . as ( "TypeError" )
}
2026-07-13 21:38:11 +00:00
return new CodeModeURLSearchParams (
2026-07-06 17:01:14 +00:00
new URLSearchParams ( Object . fromEntries ( Object . entries ( data ) . map ( ( [ key , value ] ) = > [ key , coerceToString ( value ) ] ) ) ) ,
)
}
private evaluateBinaryExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
const self = this
return Effect . gen ( function * ( ) {
const lhs = yield * self . evaluateExpression ( getNode ( node , "left" ) )
const rhs = yield * self . evaluateExpression ( getNode ( node , "right" ) )
if ( operator === "instanceof" ) return instanceofValue ( lhs , rhs , node )
return boundedData ( self . applyBinaryOperator ( operator , lhs , rhs , node ) , "Binary expression result" )
} )
}
private applyBinaryOperator ( operator : string , lhs : unknown , rhs : unknown , node : AstNode ) : unknown {
if ( containsOpaqueReference ( lhs ) || containsOpaqueReference ( rhs ) ) {
throw new InterpreterRuntimeError ( "Binary operators require data values in CodeMode." , node , "InvalidDataValue" )
}
2026-07-12 17:47:56 +00:00
// Null-prototype data needs explicit primitive coercion; identity and `in` retain raw objects.
// Dates use string coercion for `+` and epoch time elsewhere.
2026-07-06 17:01:14 +00:00
const coerceOperand = ( operand : unknown ) : unknown = > {
2026-07-13 21:38:11 +00:00
if ( operand instanceof CodeModeDate ) return operator === "+" ? coerceToString ( operand ) : operand . time
2026-07-06 17:01:14 +00:00
return operand !== null && typeof operand === "object" ? coerceToString ( operand ) : operand
}
const bothObjects = lhs !== null && typeof lhs === "object" && rhs !== null && typeof rhs === "object"
const l = coerceOperand ( lhs )
const r = coerceOperand ( rhs )
switch ( operator ) {
case "+" :
return ( l as string ) + ( r as string )
case "-" :
return ( l as number ) - ( r as number )
case "*" :
return ( l as number ) * ( r as number )
case "/" :
return ( l as number ) / ( r as number )
case "%" :
return ( l as number ) % ( r as number )
case "**" :
return ( l as number ) * * ( r as number )
case "==" :
return bothObjects ? lhs === rhs : l == r
case "===" :
return lhs === rhs
case "!=" :
return bothObjects ? lhs !== rhs : l != r
case "!==" :
return lhs !== rhs
case "<" :
return ( l as string ) < ( r as string )
case "<=" :
return ( l as string ) <= ( r as string )
case ">" :
return ( l as string ) > ( r as string )
case ">=" :
return ( l as string ) >= ( r as string )
case "&" :
return ( l as number ) & ( r as number )
case "|" :
return ( l as number ) | ( r as number )
case "^" :
return ( l as number ) ^ ( r as number )
case "<<" :
return ( l as number ) << ( r as number )
case ">>" :
return ( l as number ) >> ( r as number )
case ">>>" :
return ( l as number ) >>> ( r as number )
case "in" :
if ( rhs === null || typeof rhs !== "object" ) {
throw new InterpreterRuntimeError ( "The 'in' operator requires a data object on the right-hand side." , node )
}
2026-07-12 17:47:56 +00:00
// Never expose properties inherited from host prototypes.
2026-07-06 17:01:14 +00:00
return Object . hasOwn ( rhs as object , coerceOperand ( lhs ) as PropertyKey )
default :
throw new InterpreterRuntimeError ( ` Unsupported binary operator ' ${ operator } '. ` , node )
}
}
private evaluateLogicalExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
return Effect . flatMap ( this . evaluateExpression ( getNode ( node , "left" ) ) , ( left ) = > {
if ( operator === "&&" ) return left ? this . evaluateExpression ( getNode ( node , "right" ) ) : Effect . succeed ( left )
if ( operator === "||" ) return left ? Effect . succeed ( left ) : this . evaluateExpression ( getNode ( node , "right" ) )
if ( operator === "??" )
return left !== null && left !== undefined
? Effect . succeed ( left )
: this . evaluateExpression ( getNode ( node , "right" ) )
throw new InterpreterRuntimeError ( ` Unsupported logical operator ' ${ operator } '. ` , node )
} )
}
private evaluateUnaryExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
const argument = getNode ( node , "argument" )
2026-07-16 17:21:28 +00:00
if ( operator === "delete" ) return this . evaluateDeleteExpression ( argument )
2026-07-12 17:47:56 +00:00
// Undeclared names short-circuit, but declared TDZ bindings must still throw.
if ( operator === "typeof" && argument . type === "Identifier" && ! this . scopes . resolve ( getString ( argument , "name" ) ) ) {
2026-07-06 17:01:14 +00:00
return Effect . succeed ( "undefined" )
}
return Effect . map ( this . evaluateExpression ( argument ) , ( value ) = > {
if ( operator === "typeof" ) return typeofValue ( value )
if ( operator === "!" ) return ! value
2026-07-16 16:15:29 +00:00
if ( operator === "void" ) return undefined
2026-07-06 17:01:14 +00:00
if ( containsOpaqueReference ( value ) ) {
throw new InterpreterRuntimeError ( "Unary operators require data values in CodeMode." , node , "InvalidDataValue" )
}
const operand =
2026-07-13 21:38:11 +00:00
value instanceof CodeModeDate
2026-07-06 17:01:14 +00:00
? value . time
: value !== null && typeof value === "object"
? coerceToString ( value )
: value
let result : unknown
switch ( operator ) {
case "+" :
result = + ( operand as number )
break
case "-" :
result = - ( operand as number )
break
case "~" :
result = ~ ( operand as number )
break
default :
throw new InterpreterRuntimeError ( ` Unsupported unary operator ' ${ operator } '. ` , node )
}
return boundedData ( result , "Unary expression result" )
} )
}
private evaluateAssignmentExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const left = getNode ( node , "left" )
const operator = getString ( node , "operator" )
const self = this
return Effect . gen ( function * ( ) {
if ( operator === "??=" || operator === "||=" || operator === "&&=" ) {
return yield * self . evaluateLogicalAssignment ( node , left , operator )
}
2026-07-06 19:56:52 +00:00
if ( operator === "=" && ( left . type === "ObjectPattern" || left . type === "ArrayPattern" ) ) {
const rightValue = yield * self . evaluateExpression ( getNode ( node , "right" ) )
yield * self . assignPattern ( left , rightValue , node )
return rightValue
}
2026-07-06 17:01:14 +00:00
if ( left . type === "Identifier" ) {
const name = getString ( left , "name" )
2026-07-06 19:55:32 +00:00
if ( operator !== "=" ) {
2026-07-12 17:47:56 +00:00
const current = self . scopes . get ( name , left )
2026-07-06 19:55:32 +00:00
const rightValue = yield * self . evaluateExpression ( getNode ( node , "right" ) )
2026-07-06 17:01:14 +00:00
const next = boundedData (
self . applyCompoundAssignment ( operator , current , rightValue , node ) ,
"Assignment result" ,
)
2026-07-12 17:47:56 +00:00
return self . scopes . set ( name , next , left )
2026-07-06 19:55:32 +00:00
}
const rightValue = yield * self . evaluateExpression ( getNode ( node , "right" ) )
2026-07-12 17:47:56 +00:00
return self . scopes . set ( name , rightValue , left )
2026-07-06 19:55:32 +00:00
}
if ( left . type === "MemberExpression" ) {
return yield * self . modifyMember ( left , ( current ) = >
Effect . map ( self . evaluateExpression ( getNode ( node , "right" ) ) , ( rightValue ) = > {
if ( operator === "=" ) return { write : true , next : rightValue , result : rightValue }
const next = boundedData (
self . applyCompoundAssignment ( operator , current , rightValue , node ) ,
"Assignment result" ,
)
return { write : true , next , result : next }
} ) ,
)
2026-07-06 17:01:14 +00:00
}
throw new InterpreterRuntimeError ( "Assignment target must be an Identifier or MemberExpression." , left )
} )
}
private evaluateLogicalAssignment (
node : AstNode ,
left : AstNode ,
operator : string ,
) : Effect . Effect < unknown , unknown , R > {
const self = this
const shouldAssign = ( current : unknown ) : boolean = >
operator === "??=" ? current === null || current === undefined : operator === "||=" ? ! current : Boolean ( current )
if ( left . type === "Identifier" ) {
const name = getString ( left , "name" )
return Effect . gen ( function * ( ) {
2026-07-12 17:47:56 +00:00
const current = self . scopes . get ( name , left )
2026-07-06 17:01:14 +00:00
if ( ! shouldAssign ( current ) ) return current
const rightValue = yield * self . evaluateExpression ( getNode ( node , "right" ) )
2026-07-12 17:47:56 +00:00
return self . scopes . set ( name , rightValue , left )
2026-07-06 17:01:14 +00:00
} )
}
if ( left . type === "MemberExpression" ) {
return self . modifyMember ( left , ( current ) = >
shouldAssign ( current )
? Effect . map ( self . evaluateExpression ( getNode ( node , "right" ) ) , ( rightValue ) = > ( {
write : true ,
next : rightValue ,
result : rightValue ,
} ) )
: Effect . succeed ( { write : false , next : current , result : current } ) ,
)
}
throw new InterpreterRuntimeError ( "Assignment target must be an Identifier or MemberExpression." , left )
}
private evaluateUpdateExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
const argument = getNode ( node , "argument" )
const prefix = getBoolean ( node , "prefix" )
const increment = operator === "++" ? 1 : operator === "--" ? - 1 : undefined
if ( increment === undefined ) {
throw new InterpreterRuntimeError ( ` Unsupported update operator ' ${ operator } '. ` , node )
}
if ( argument . type === "Identifier" ) {
return Effect . sync ( ( ) = > {
const name = getString ( argument , "name" )
2026-07-12 17:47:56 +00:00
const current = Number ( this . scopes . get ( name , argument ) )
2026-07-06 17:01:14 +00:00
const next = current + increment
2026-07-12 17:47:56 +00:00
this . scopes . set ( name , next , argument )
2026-07-06 17:01:14 +00:00
return prefix ? next : current
} )
}
if ( argument . type === "MemberExpression" ) {
return this . modifyMember ( argument , ( current ) = > {
const value = Number ( current )
const next = value + increment
return Effect . succeed ( { write : true , next , result : prefix ? next : value } )
} )
}
throw new InterpreterRuntimeError ( "Update target must be an Identifier or MemberExpression." , argument )
}
private evaluateCallExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const callee = getNode ( node , "callee" )
const argNodes = getArray ( node , "arguments" )
const self = this
return Effect . gen ( function * ( ) {
const callable = yield * self . evaluateExpression ( callee )
if ( callable === OptionalShortCircuit ) return OptionalShortCircuit
if ( ( callable === null || callable === undefined ) && node . optional === true ) return OptionalShortCircuit
const args = yield * self . evaluateCallArguments ( argNodes )
2026-07-14 20:25:39 +00:00
return yield * self . invokeCallable ( callable , args , node , callee )
} )
}
2026-07-06 17:01:14 +00:00
2026-07-14 20:25:39 +00:00
// The single dispatch for every invocation: call expressions and callbacks share it.
private invokeCallable (
callable : unknown ,
args : Array < unknown > ,
node : AstNode ,
callee : AstNode = node ,
) : Effect . Effect < unknown , unknown , R > {
const self = this
return Effect . gen ( function * ( ) {
2026-07-06 17:01:14 +00:00
if ( callable instanceof ToolReference ) {
if ( callable . path . length === 0 ) throw new InterpreterRuntimeError ( "The tools root is not callable." , callee )
return yield * self . createToolCallPromise ( callable . path , args )
}
if ( callable instanceof PromiseMethodReference ) {
2026-07-12 17:47:56 +00:00
return yield * invokePromiseMethod ( self . runner , self . promises , callable , args , node )
2026-07-06 17:01:14 +00:00
}
2026-07-10 23:22:04 +00:00
if ( callable instanceof PromiseInstanceMethodReference ) {
2026-07-12 17:47:56 +00:00
return yield * invokePromiseInstanceMethod ( self . runner , self . promises , callable , args , node )
2026-07-10 23:22:04 +00:00
}
2026-07-06 17:01:14 +00:00
if ( callable instanceof CodeModeFunction ) {
return yield * self . invokeFunction ( callable , args )
}
if ( callable instanceof IntrinsicReference ) {
2026-07-12 17:47:56 +00:00
return yield * invokeIntrinsic ( self . runner , callable , args , node )
2026-07-06 17:01:14 +00:00
}
if ( callable instanceof GlobalMethodReference ) {
if ( callable . namespace === "console" ) return self . invokeConsole ( callable . name , args , node )
if ( callable . namespace === "Object" && args [ 0 ] instanceof ToolReference ) {
2026-07-06 21:47:17 +00:00
return self . invokeObjectMethodOnTools ( callable . name , args [ 0 ] , node )
2026-07-06 17:01:14 +00:00
}
2026-07-06 21:47:17 +00:00
if ( callable . namespace === "Object" && objectMethodsPreservingIdentity . has ( callable . name ) ) {
2026-07-06 19:55:32 +00:00
return invokeGlobalMethod ( callable , args , node )
}
2026-07-14 20:25:39 +00:00
if ( callable . namespace === "Array" && callable . name === "from" ) {
return yield * invokeArrayFrom ( self . runner , args , node )
}
if ( callable . namespace === "Array" && callable . name === "of" ) {
2026-07-07 18:40:55 +00:00
return invokeGlobalMethod ( callable , args , node )
}
2026-07-06 17:01:14 +00:00
return boundedData ( invokeGlobalMethod ( callable , args , node ) , ` ${ callable . namespace } . ${ callable . name } result ` )
}
if ( callable instanceof CoercionFunction ) {
return boundedData ( invokeCoercion ( callable , args , node ) , ` ${ callable . name } result ` )
}
if ( callable instanceof UriFunction ) {
return invokeUriFunction ( callable , args , node )
}
2026-07-11 18:43:33 +00:00
if ( callable instanceof SearchFunction ) {
return yield * self . invokeSearch ( args )
}
2026-07-06 17:01:14 +00:00
if ( callable instanceof ErrorConstructorReference ) {
2026-07-11 01:46:13 +00:00
return constructErrorValue ( callable . name , args , node )
}
2026-07-14 20:25:39 +00:00
if ( callable instanceof GlobalNamespace ) {
// Real JS permits calling Array, Object, Date, and RegExp without new.
if ( callable . name === "Array" ) return self . constructArray ( args , node )
if ( callable . name === "Object" ) return self . constructObject ( args , node )
// ISO instead of the host's locale string: CodeMode date strings are
// deterministic and must not leak the host timezone.
if ( callable . name === "Date" ) return new Date ( ) . toISOString ( )
if ( callable . name === "RegExp" ) return self . constructRegExp ( args , node )
if ( typeofValue ( callable ) === "function" ) {
throw new InterpreterRuntimeError ( ` Constructor ${ callable . name } requires 'new'. ` , node ) . as ( "TypeError" )
}
throw new InterpreterRuntimeError ( ` ${ callable . name } is not a function. ` , node ) . as ( "TypeError" )
}
if ( callable instanceof PromiseNamespace ) {
throw new InterpreterRuntimeError ( "Constructor Promise requires 'new'." , node ) . as ( "TypeError" )
}
2026-07-11 01:46:13 +00:00
if ( callable instanceof PromiseCapabilityFunction ) {
callable . settle ( args [ 0 ] )
return undefined
2026-07-06 17:01:14 +00:00
}
throw new InterpreterRuntimeError ( "Only tools are callable in CodeMode." , callee )
} )
}
private invokeObjectMethodOnTools ( name : string , ref : ToolReference , node : AstNode ) : unknown {
if ( name === "keys" ) {
return boundedData ( this . enumerableKeys ( ref ) ! , "Object.keys result" )
}
throw new InterpreterRuntimeError (
2026-07-11 18:43:33 +00:00
` Object. ${ name } (...) cannot read tool references: they are not plain data. Use Object.keys(tools) for names, or search({ query }) for signatures. ` ,
2026-07-06 17:01:14 +00:00
node ,
"InvalidDataValue" ,
)
}
private invokeConsole ( name : string , args : Array < unknown > , node : AstNode ) : undefined {
if ( ! consoleMethods . has ( name ) )
throw new InterpreterRuntimeError ( ` console. ${ name } is not available in CodeMode. ` , node )
2026-07-12 17:47:56 +00:00
this . logs . push ( formatConsoleMessage ( name , args ) )
2026-07-06 17:01:14 +00:00
return undefined
}
private evaluateCallArguments ( argNodes : Array < unknown > ) : Effect . Effect < Array < unknown > , unknown , R > {
const self = this
return Effect . gen ( function * ( ) {
const args : Array < unknown > = [ ]
for ( const [ index , arg ] of argNodes . entries ( ) ) {
const argNode = asNode ( arg , ` arguments[ ${ index } ] ` )
if ( argNode . type === "SpreadElement" ) {
const spread = yield * self . evaluateExpression ( getNode ( argNode , "argument" ) )
const items = spreadItems ( spread )
if ( items === undefined )
throw new InterpreterRuntimeError (
"Spread arguments require an array, string, Map, or Set in CodeMode." ,
argNode ,
)
args . push ( . . . items )
} else {
args . push ( yield * self . evaluateExpression ( argNode ) )
}
}
return args
} )
}
private invokeFunction ( fn : CodeModeFunction , args : Array < unknown > ) : Effect . Effect < unknown , unknown , R > {
2026-07-12 21:07:53 +00:00
const invocation = new Interpreter ( this . invokeTool , this . invokeSearch , this . toolKeys , this . promises , this . logs )
2026-07-12 17:47:56 +00:00
invocation . scopes = new ScopeStack ( [ . . . fn . capturedScopes , new Map ( ) ] )
2026-07-06 21:29:11 +00:00
const run = Effect . gen ( function * ( ) {
2026-07-12 17:47:56 +00:00
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
const paramScope = invocation . scopes . current ( )
2026-07-06 21:29:11 +00:00
for ( const parameter of fn . parameters ) {
for ( const name of collectPatternNames ( parameter ) ) {
paramScope . set ( name , { mutable : true , value : undefined , initialized : false } )
2026-07-06 17:01:14 +00:00
}
2026-07-06 21:29:11 +00:00
}
for ( const [ index , parameter ] of fn . parameters . entries ( ) ) {
if ( parameter . type === "RestElement" ) {
2026-07-17 03:26:45 +00:00
yield * invocation . declarePattern ( getNode ( parameter , "argument" ) , args . slice ( index ) , true , parameter , true )
2026-07-06 21:29:11 +00:00
break
2026-07-06 17:01:14 +00:00
}
2026-07-17 03:26:45 +00:00
yield * invocation . declarePattern ( parameter , args [ index ] , true , parameter , true )
2026-07-06 21:29:11 +00:00
}
2026-07-06 17:01:14 +00:00
2026-07-06 21:29:11 +00:00
if ( fn . body . type === "BlockStatement" ) {
const result = yield * invocation . evaluateStatement ( fn . body )
2026-07-07 16:44:50 +00:00
return result . kind === "return" ? result.value : undefined
2026-07-06 21:29:11 +00:00
}
2026-07-06 17:01:14 +00:00
2026-07-06 21:29:11 +00:00
return yield * invocation . evaluateExpression ( fn . body )
2026-07-06 17:01:14 +00:00
} )
2026-07-06 21:29:11 +00:00
if ( ! fn . async ) return run
2026-07-12 17:47:56 +00:00
// The initial yield assigns `box.own` before the body can self-resolve.
2026-07-13 21:38:11 +00:00
const box : { own? : CodeModePromise } = { }
2026-07-10 23:22:04 +00:00
return Effect . map (
this . createPromise (
Effect . flatMap ( run , ( value ) = > {
2026-07-13 21:38:11 +00:00
if ( ! ( value instanceof CodeModePromise ) ) return Effect . succeed ( value )
2026-07-10 23:22:04 +00:00
if ( value === box . own ) return Effect . fail ( selfResolutionError ( ) )
return invocation . settlePromise ( value )
} ) ,
2026-07-06 21:29:11 +00:00
) ,
2026-07-10 23:22:04 +00:00
( promise ) = > {
box . own = promise
return promise
} ,
2026-07-06 21:29:11 +00:00
)
2026-07-06 17:01:14 +00:00
}
private evaluateObjectExpression ( node : AstNode ) : Effect . Effect < Record < string , unknown > , unknown , R > {
const objectValue : Record < string , unknown > = Object . create ( null ) as Record < string , unknown >
const properties = getArray ( node , "properties" )
const self = this
return Effect . gen ( function * ( ) {
for ( const propertyValue of properties ) {
const property = asNode ( propertyValue , "properties" )
if ( property . type === "SpreadElement" ) {
const spread = yield * self . evaluateExpression ( getNode ( property , "argument" ) )
2026-07-13 21:38:11 +00:00
if ( spread === null || spread === undefined || isCodeModeValue ( spread ) ) continue
2026-07-06 17:01:14 +00:00
if ( typeof spread !== "object" || Array . isArray ( spread ) || isRuntimeReference ( spread ) ) {
throw new InterpreterRuntimeError (
"Object spread requires a data object in CodeMode." ,
property ,
"InvalidDataValue" ,
)
}
for ( const [ key , value ] of Object . entries ( spread ) ) {
if ( isBlockedMember ( key ) )
throw new InterpreterRuntimeError ( ` Property ' ${ key } ' is not available in CodeMode. ` , property )
objectValue [ key ] = value
}
continue
}
if ( property . type !== "Property" ) {
throw new InterpreterRuntimeError ( "Only standard object properties are supported." , property )
}
if ( getString ( property , "kind" ) !== "init" ) {
throw new InterpreterRuntimeError ( "Only init object properties are supported." , property )
}
const keyNode = getNode ( property , "key" )
const valueNode = getNode ( property , "value" )
const computed = getBoolean ( property , "computed" )
let key : PropertyKey
if ( computed ) {
key = self . toPropertyKey ( yield * self . evaluateExpression ( keyNode ) , keyNode )
} else if ( keyNode . type === "Identifier" ) {
key = getString ( keyNode , "name" )
} else if ( keyNode . type === "Literal" ) {
key = self . toPropertyKey ( keyNode . value , keyNode )
} else {
throw new InterpreterRuntimeError ( "Unsupported object property key shape." , keyNode )
}
if ( isBlockedMember ( String ( key ) ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ String ( key ) } ' is not available in CodeMode. ` , keyNode )
}
objectValue [ String ( key ) ] = yield * self . evaluateExpression ( valueNode )
}
return objectValue
} )
}
private evaluateArrayExpression ( node : AstNode ) : Effect . Effect < Array < unknown > , unknown , R > {
const elements = getArray ( node , "elements" )
const values : Array < unknown > = [ ]
const self = this
return Effect . gen ( function * ( ) {
for ( const elementValue of elements ) {
if ( elementValue === null ) {
2026-07-14 20:25:39 +00:00
// A literal elision is a real hole, like JS: extend length without an own index.
values . length += 1
2026-07-06 17:01:14 +00:00
continue
}
const element = asNode ( elementValue , "elements" )
if ( element . type === "SpreadElement" ) {
const spread = yield * self . evaluateExpression ( getNode ( element , "argument" ) )
const items = spreadItems ( spread )
if ( items === undefined )
throw new InterpreterRuntimeError (
"Array spread requires an array, string, Map, or Set in CodeMode." ,
element ,
)
values . push ( . . . items )
} else {
values . push ( yield * self . evaluateExpression ( element ) )
}
}
return values
} )
}
private evaluateTemplateLiteral ( node : AstNode ) : Effect . Effect < string , unknown , R > {
const quasis = getArray ( node , "quasis" )
const expressions = getArray ( node , "expressions" )
let output = ""
const self = this
return Effect . gen ( function * ( ) {
for ( let index = 0 ; index < quasis . length ; index += 1 ) {
const quasi = asNode ( quasis [ index ] , "quasis" )
const rawValue = quasi . value
if ( ! isRecord ( rawValue ) || typeof rawValue . cooked !== "string" ) {
throw new InterpreterRuntimeError ( "Invalid template literal quasi." , quasi )
}
output += rawValue . cooked
if ( index < expressions . length ) {
const raw = yield * self . evaluateExpression ( asNode ( expressions [ index ] , "expressions" ) )
output += coerceToString ( boundedData ( raw , "Template interpolation" ) )
}
}
return output
} )
}
private evaluateConditionalExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
return Effect . flatMap ( this . evaluateExpression ( getNode ( node , "test" ) ) , ( test ) = >
this . evaluateExpression ( getNode ( node , test ? "consequent" : "alternate" ) ) ,
)
}
private applyCompoundAssignment ( operator : string , current : unknown , incoming : unknown , node : AstNode ) : unknown {
if ( ! compoundOperators . has ( operator ) ) {
throw new InterpreterRuntimeError ( ` Unsupported assignment operator ' ${ operator } '. ` , node )
}
return this . applyBinaryOperator ( operator . slice ( 0 , - 1 ) , current , incoming , node )
}
private getMemberReference (
node : AstNode ,
2026-07-16 17:21:28 +00:00
operation : "read" | "delete" = "read" ,
2026-07-06 17:01:14 +00:00
) : Effect . Effect <
| MemberReference
| ToolReference
| PromiseMethodReference
2026-07-10 23:22:04 +00:00
| PromiseInstanceMethodReference
2026-07-06 17:01:14 +00:00
| IntrinsicReference
| GlobalMethodReference
| ComputedValue
| typeof OptionalShortCircuit
| undefined ,
unknown ,
R
> {
const objectNode = getNode ( node , "object" )
const propertyNode = getNode ( node , "property" )
const computed = getBoolean ( node , "computed" )
const optional = node . optional === true
const self = this
return Effect . gen ( function * ( ) {
const objectValue = yield * self . evaluateExpression ( objectNode )
if ( objectValue === OptionalShortCircuit ) return OptionalShortCircuit
if ( ( objectValue === null || objectValue === undefined ) && optional ) return OptionalShortCircuit
const key = computed
? self . toPropertyKey ( yield * self . evaluateExpression ( propertyNode ) , propertyNode )
: propertyNode . type === "Identifier"
? getString ( propertyNode , "name" )
: self . toPropertyKey ( yield * self . evaluateExpression ( propertyNode ) , propertyNode )
if ( objectValue instanceof ToolReference ) {
2026-07-15 16:09:09 +00:00
if ( typeof key !== "string" ) {
throw new InterpreterRuntimeError ( "Tool paths must use string property names." , propertyNode )
2026-07-06 17:01:14 +00:00
}
return new ToolReference ( [ . . . objectValue . path , key ] )
}
if ( objectValue instanceof PromiseNamespace ) {
if ( typeof key === "string" && promiseStatics . has ( key as PromiseMethodName ) ) {
return new PromiseMethodReference ( key as PromiseMethodName )
}
throw new InterpreterRuntimeError (
2026-07-11 01:46:13 +00:00
` Promise. ${ String ( key ) } is not available in CodeMode. Available: Promise.all, Promise.allSettled, Promise.race, Promise.any, Promise.resolve, and Promise.reject; consume promises with await. ` ,
2026-07-06 17:01:14 +00:00
propertyNode ,
)
}
if ( objectValue instanceof GlobalNamespace ) {
if ( typeof key !== "string" || isBlockedMember ( key ) ) {
throw new InterpreterRuntimeError (
` ${ objectValue . name } . ${ String ( key ) } is not available in CodeMode. ` ,
propertyNode ,
)
}
if ( objectValue . name === "Math" && mathConstants . has ( key ) ) {
return new ComputedValue ( ( Math as unknown as Record < string , number > ) [ key ] )
}
return new GlobalMethodReference ( objectValue . name , key )
}
if ( typeof objectValue === "string" ) {
if ( key === "length" ) return new ComputedValue ( objectValue . length )
if ( typeof key === "number" ) return new ComputedValue ( objectValue [ key ] )
if ( typeof key === "string" && /^\d+$/ . test ( key ) ) return new ComputedValue ( objectValue [ Number ( key ) ] )
if ( typeof key === "string" && stringMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
if ( typeof objectValue === "number" ) {
if ( typeof key === "string" && numberMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
if ( objectValue instanceof CoercionFunction && typeof key === "string" && ! isBlockedMember ( key ) ) {
if ( objectValue . name === "Number" && numberConstants . has ( key ) ) {
return new ComputedValue ( ( Number as unknown as Record < string , number > ) [ key ] )
}
if ( objectValue . name === "Number" && numberStatics . has ( key ) ) return new GlobalMethodReference ( "Number" , key )
if ( objectValue . name === "String" && stringStatics . has ( key ) ) return new GlobalMethodReference ( "String" , key )
}
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModeDate ) {
2026-07-06 17:01:14 +00:00
if ( typeof key === "string" && dateMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModeRegExp ) {
2026-07-06 17:01:14 +00:00
if ( typeof key === "string" && regexpProperties . has ( key ) ) {
return new ComputedValue ( ( objectValue . regex as unknown as Record < string , unknown > ) [ key ] )
}
if ( typeof key === "string" && regexpMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModeMap ) {
2026-07-06 17:01:14 +00:00
if ( key === "size" ) return new ComputedValue ( objectValue . map . size )
if ( typeof key === "string" && mapMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModeSet ) {
2026-07-06 17:01:14 +00:00
if ( key === "size" ) return new ComputedValue ( objectValue . set . size )
if ( typeof key === "string" && setMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModeURL ) {
2026-07-06 17:01:14 +00:00
if ( key === "searchParams" ) {
return new ComputedValue ( objectValue . searchParams )
}
if ( typeof key === "string" && urlMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
if ( typeof key === "string" && urlProperties . has ( key ) ) return { target : objectValue , key }
return new ComputedValue ( undefined )
}
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModeURLSearchParams ) {
2026-07-06 17:01:14 +00:00
if ( key === "size" ) return new ComputedValue ( objectValue . params . size )
if ( typeof key === "string" && urlSearchParamsMethods . has ( key ) ) {
return new IntrinsicReference ( objectValue , key )
}
return new ComputedValue ( undefined )
}
2026-07-12 17:47:56 +00:00
// Reject unknown promise properties so a missing await cannot hide.
2026-07-13 21:38:11 +00:00
if ( objectValue instanceof CodeModePromise ) {
2026-07-06 17:01:14 +00:00
if ( key === "then" || key === "catch" || key === "finally" ) {
2026-07-10 23:22:04 +00:00
return new PromiseInstanceMethodReference ( objectValue , key )
2026-07-06 17:01:14 +00:00
}
throw new InterpreterRuntimeError (
2026-07-10 23:22:04 +00:00
"This value is an un-awaited Promise; await it first - e.g. `const result = await tools.ns.tool(...)`." ,
2026-07-06 17:01:14 +00:00
objectNode ,
"InvalidDataValue" ,
)
}
if ( isRuntimeReference ( objectValue ) ) {
throw new InterpreterRuntimeError (
"CodeMode runtime references are opaque and do not expose properties." ,
objectNode ,
"InvalidDataValue" ,
)
}
if ( typeof objectValue !== "object" || objectValue === null ) {
throw new InterpreterRuntimeError ( "Cannot access a property on a non-object value." , objectNode )
}
if ( typeof key === "string" && isBlockedMember ( key ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ key } ' is not available in CodeMode. ` , propertyNode )
}
if ( Array . isArray ( objectValue ) ) {
2026-07-16 17:21:28 +00:00
if ( operation === "delete" ) return { target : objectValue , key }
2026-07-06 17:01:14 +00:00
if (
key !== "length" &&
! ( typeof key === "string" && arrayMethods . has ( key ) ) &&
typeof key !== "number" &&
! /^\d+$/ . test ( key )
) {
if ( typeof key === "string" && Object . hasOwn ( objectValue , key ) ) {
return new ComputedValue ( ( objectValue as Record < string , unknown > & Array < unknown > ) [ key ] )
}
return new ComputedValue ( undefined )
}
return { target : objectValue , key }
}
return { target : objectValue as SafeObject , key }
} )
}
private readMember ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
return Effect . map ( this . getMemberReference ( node ) , ( reference ) = > {
if ( reference === OptionalShortCircuit ) return OptionalShortCircuit
if ( reference instanceof ComputedValue ) return reference . value
if (
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
2026-07-10 23:22:04 +00:00
reference instanceof PromiseInstanceMethodReference ||
2026-07-06 17:01:14 +00:00
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
)
return reference
if ( Array . isArray ( reference . target ) ) {
if ( typeof reference . key === "string" && arrayMethods . has ( reference . key ) ) {
return new IntrinsicReference ( reference . target , reference . key )
}
return reference . key === "length" ? reference.target.length : reference.target [ Number ( reference . key ) ]
}
2026-07-13 21:38:11 +00:00
if ( reference . target instanceof CodeModeURL ) {
2026-07-06 17:01:14 +00:00
return ( reference . target . url as unknown as Record < string , unknown > ) [ String ( reference . key ) ]
}
return reference . target [ String ( reference . key ) ]
} )
}
private writeMember ( node : AstNode , value : unknown ) : Effect . Effect < unknown , unknown , R > {
return this . modifyMember ( node , ( ) = > Effect . succeed ( { write : true , next : value , result : value } ) )
}
2026-07-16 17:21:28 +00:00
private evaluateDeleteExpression ( argument : AstNode ) : Effect . Effect < boolean , unknown , R > {
const target = argument . type === "ChainExpression" ? getNode ( argument , "expression" ) : argument
if ( target . type !== "MemberExpression" ) {
throw new InterpreterRuntimeError ( "Only data fields may be deleted in CodeMode." , argument )
}
return Effect . map ( this . getMemberReference ( target , "delete" ) , ( reference ) = > {
if ( reference === OptionalShortCircuit ) return true
if (
reference instanceof ComputedValue ||
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
reference instanceof PromiseInstanceMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference ||
reference . target instanceof CodeModeURL
) {
throw new InterpreterRuntimeError ( "Only data fields may be deleted in CodeMode." , target , "InvalidDataValue" )
}
return Reflect . deleteProperty ( reference . target , reference . key )
} )
}
2026-07-12 17:47:56 +00:00
// Resolve side-effecting object and key expressions exactly once.
2026-07-06 17:01:14 +00:00
private modifyMember (
node : AstNode ,
compute : ( current : unknown ) = > Effect . Effect < { write : boolean ; next : unknown ; result : unknown } , unknown , R > ,
) : Effect . Effect < unknown , unknown , R > {
const self = this
return Effect . gen ( function * ( ) {
const reference = yield * self . getMemberReference ( node )
if (
reference === OptionalShortCircuit ||
reference instanceof ComputedValue ||
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
2026-07-10 23:22:04 +00:00
reference instanceof PromiseInstanceMethodReference ||
2026-07-06 17:01:14 +00:00
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
) {
throw new InterpreterRuntimeError ( "Only data fields may be assigned in CodeMode." , node )
}
if ( Array . isArray ( reference . target ) ) {
if ( reference . key === "length" )
throw new InterpreterRuntimeError ( "Array length cannot be assigned in CodeMode." , node )
if ( typeof reference . key === "string" && arrayMethods . has ( reference . key ) ) {
throw new InterpreterRuntimeError ( "Array methods cannot be assigned in CodeMode." , node )
}
}
const key = Array . isArray ( reference . target ) ? Number ( reference . key ) : String ( reference . key )
const current =
2026-07-13 21:38:11 +00:00
reference . target instanceof CodeModeURL
2026-07-06 17:01:14 +00:00
? ( reference . target . url as unknown as Record < string , unknown > ) [ key ]
: ( reference . target as Record < PropertyKey , unknown > ) [ key ]
const { write , next , result } = yield * compute ( current )
if ( write ) self . assignToReference ( reference , key , next , node )
return result
} )
}
private assignToReference ( reference : MemberReference , key : number | string , next : unknown , node : AstNode ) : void {
if ( Array . isArray ( reference . target ) ) {
const target = reference . target
const index = key as number
if ( ! Number . isInteger ( index ) || index < 0 ) {
throw new InterpreterRuntimeError (
"Array assignment index must be a non-negative integer." ,
node ,
"InvalidDataValue" ,
)
}
2026-07-12 17:47:56 +00:00
rejectCircularInsertion ( target , next , "Array assignment result" , node )
2026-07-06 17:01:14 +00:00
target [ index ] = next
return
}
2026-07-13 21:38:11 +00:00
if ( reference . target instanceof CodeModeURL ) {
2026-07-06 17:01:14 +00:00
const property = key as string
if ( ! urlWritableProperties . has ( property ) ) {
throw new InterpreterRuntimeError ( ` URL. ${ property } is read-only. ` , node ) . as ( "TypeError" )
}
try {
const url = reference . target . url as unknown as Record < string , string >
url [ property ] = uriArgument ( next , ` URL. ${ property } value ` )
return
} catch ( error ) {
if ( error instanceof InterpreterRuntimeError || error instanceof ToolRuntimeError ) throw error
throw new InterpreterRuntimeError ( ` URL. ${ property } received an invalid value. ` , node ) . as ( "TypeError" )
}
}
const target = reference . target as SafeObject
const objectKey = key as string
2026-07-12 17:47:56 +00:00
rejectCircularInsertion ( target , next , "Object assignment result" , node )
2026-07-06 17:01:14 +00:00
target [ objectKey ] = next
}
private toPropertyKey ( value : unknown , node : AstNode ) : string | number {
if ( typeof value === "string" || typeof value === "number" ) {
return value
}
throw new InterpreterRuntimeError ( "Property key must be a string or number." , node )
}
}