import { arrayEquals , Token , Path , Item , assert , MetaID , trap , Block , CompileError , ID , arrayLast , another , Value , cast , Call , Do , Code , Crash , Statement , Choice , _Array , FieldID , Selection , Field } from "./exports" ; /** Guard on an ID in a reference */ export type Guard = '?' | '!' | undefined ; /** Reference to an item at a path in the workspace
 *
 * References are either structural or dependent. A structural reference is to
 * another item within a container of the base item. The syntax of a structural
 * reference starts with a field name, which is lexically bound. A dependent
 * reference is a path within the previous value of the base item. The
 * structural path to the previous item is prefixed to a dependent reference.
 * The syntax of a dependent reference starts with a '.', or '~'.
 */ export class Reference extends Value { /** Tokens of path in source. May have leading 'that' token. Name tokens have
     * leading ^/~ and trailing ?/!. Special tokens used for binding calls: call,
     * arg1, arg2, input. Number tokens are used for testing */ tokens ! : Token [ ] ; /** reference is dependent if starts with 'that' */ get dependent ( ) { return this . tokens [ 0 ] . type === 'that' ; } /** Path to follow */ path ! : Path ; /** index in path to end of contextual part of path. In a structural
     * reference, the context is the LUB container of the base and target items.
     * In a dependent reference, the context is the path of the previous value.
     * The contextual part of the path is unguarded, and we avoid evaluating it to
     * prevent evaluation cycles.
     * */ context ! : number ; /** guards for each ID in path. Context is unguarded */ guards ! : Guard [ ] ; /** pointer to target item of reference (not a copy). Derived during eval, not
     * copied. undefined on reference editError */ target ? : Item ; /** whether reference is conditional within path */ conditional = false ; /** whether reference was rejected. Derived, not copied */ rejected = false ; isBlank ( ) { return false ; } /** Evaluate reference */ eval ( ) { if ( this . target || this . rejected || this . editError === 'reference' ) { return ; } assert ( this . id instanceof MetaID || this instanceof Selection ) ; let from = this . containingItem . container . containingItem ; if ( ! this . path ) { this . bind ( from ) ; } const basePath = this . containingPath ; let target = from . workspace ; for ( let i = 0 ; i < this . path . ids . length ; i ++ ) { let id = this . path . ids [ i ] ; if ( this . rejected && ! this . analyzing ) continue ; let down = target . getMaybe ( id ) ; let moved = down ?. getMaybe ( '^moved' ) ; if ( moved ) { assert ( this . analyzing ) ; let ref = cast ( moved . value , Reference ) ; let movedPath = ref . target ! . path ; if ( this . dependent ) { if ( i >= this . context ) { let context = new Path ( this . path . ids . slice ( 0 , this . context ) ) ; if ( ! context . containsOrEquals ( movedPath ) ) { this . containingItem . editError = 'reference' ; return ; } } else { this . context = this . context - i - 1 + movedPath . length ; } } else { this . context = movedPath . lub ( basePath ) . length ; } this . path = new Path ( [ ... movedPath . ids , ... this . path . ids . slice ( i + 1 ) ] ) ; this . guards = [ ... movedPath . ids . map ( ( ) => undefined ) , ... this . guards . slice ( i + 1 ) ] ; this . eval ( ) ; return ; } if ( down instanceof Field && down . deleted ) { down = undefined ; } if ( ! down ) { this . containingItem . editError = 'reference' ; assert ( ! this . containingItem . isDerived ) ; this . target = undefined ; return ; } target = down ; let next = this . path . ids [ i + 1 ] ; if ( next instanceof MetaID && next !== MetaID . ids [ '^export' ] ) { continue ; } if ( target instanceof Statement ) { target . used = true ; } this . evalIfNeeded ( target ) ; if ( i >= this . context ) { let guard = this . guards [ i ] ; assert ( ! ! guard === target . conditional ) ; if ( target . rejected || ( target . container instanceof Choice && target !== target . container . choice ) ) { this . rejected = true ; if ( ! this . analyzing && guard === '!' ) { throw new Crash ( this . tokens [ i - this . context ] , 'assertion failed' ) ; } } } } if ( ! ( this instanceof Selection ) ) { target . eval ( ) ; } this . target = target ; } /** evaluate item if needed to dereference, to avoid eager deep eval */ evalIfNeeded ( item ) { if ( item . editError === 'reference' ) return ; if ( ! item . value && ! item . rejected ) { item . eval ( ) ; } item . resolve ( ) ; } bind ( from ) { const inHistoryFormula = this . containingItem . inHistoryFormula ; assert ( this . analyzing || inHistoryFormula ) ; assert ( this . tokens && this . tokens . length ) ; let tokenNames = [ ] ; let tokenGuards = [ ] ; this . tokens . forEach ( token => { let suffix = token . text . slice ( - 1 ) ; switch ( suffix ) { case '?' : case '!' : tokenGuards . push ( suffix ) ; tokenNames . push ( token . text . slice ( 0 , - 1 ) ) ; return ; default : tokenGuards . push ( undefined ) ; tokenNames . push ( token . text ) ; return ; } } ) ; let target ; if ( this . tokens [ 0 ] . type === 'input' ) { /** bind to input of a call.
             * This occurs on ^payload^reference of Call.
             * Possibly could generalize to outer-that.
             * */ assert ( this . tokens . length === 1 ) ; assert ( from . id . toString ( ) === '^payload' ) ; let update = from . container . containingItem ; assert ( update . formulaType === 'updateInput' ) ; assert ( update . container instanceof Call ) ; let call = update . container . containingItem ; assert ( call . id . toString ( ) === '^call' ) ; target = this . previous ( call , this . tokens [ 0 ] ) ; if ( target . evaluated === undefined ) { throw new CompileError ( this . tokens [ 0 ] , 'Circular reference' ) ; } this . path = target . path ; this . context = this . path . length ; this . guards = this . path . ids . map ( ( ) => undefined ) ; return ; } if ( this . dependent ) { target = this . previous ( from , this . tokens [ 0 ] ) ; } else { /** bind first name of structural reference lexically by searching upward
             * to match first name. Note upwards scan skips from metadata to base
             * item's container. Also looks one level into includes */ let first = tokenNames [ 0 ] ; lexicalBinding : for ( let up of from . upwards ( ) ) { if ( up . value instanceof Block ) { for ( let field of ( up . value as Block ) . fields ) { if ( field . name === first ) { target = up ; break lexicalBinding ; } if ( field . formulaType === 'include' ) { assert ( field . value instanceof Block ) ; for ( let included of ( field . value as Block ) . fields ) { if ( included . name === first ) { target = field ; break lexicalBinding ; } } } } } } if ( ! target && inHistoryFormula ) { target = this . workspace . down ( 'initial.builtins' ) ; } if ( ! target ) { throw new CompileError ( this . tokens [ 0 ] , 'Undefined name' ) ; } } let ids = target . path . ids . slice ( ) ; let guards = ids . map ( ( ) => undefined ) ; this . context = ids . length ; for ( let i = 0 ; i < this . tokens . length ; i ++ ) { let token = this . tokens [ i ] ; let type = token . type ; let name = tokenNames [ i ] ; let guard = tokenGuards [ i ] ; if ( type === 'that' ) { assert ( i === 0 && this . dependent ) ; continue ; } else if ( name [ 0 ] === '^' ) { if ( tokenGuards [ i - 1 ] !== undefined ) { throw new CompileError ( this . tokens [ i - 1 ] , 'No guard allowed before ^' ) ; } } else if ( type === 'call' ) { if ( target . evaluated === undefined ) { trap ( ) ; } let call = target . getMaybe ( '^code' ) ; if ( ! call || ! ( call . value instanceof Do ) ) { throw new CompileError ( token , 'Can only call a do-block' ) ; } guards . pop ( ) ; guards . push ( undefined ) ; name = '^code' ; } else { this . evalIfNeeded ( target ) ; if ( ! target . value ) { trap ( ) ; } if ( name === '~' ) { name = '^export' ; } } if ( type === 'arg1' || type === 'arg2' ) { target = ( target . value instanceof Do ? target . value . fields [ type === 'arg1' ? 0 : 1 ] : undefined ) ; if ( target ?. io !== 'input' ) { throw new CompileError ( token , 'function input not defined' ) ; } } else { if ( name === '[]' ) { if ( target . value instanceof _Array ) { target = target . value . template ; } else if ( target . value instanceof Selection ) { target = target . value . backing . template ; } else { throw new CompileError ( token , '[] only defined on array and selection' ) ; } } else { target = target . getMaybe ( name ) ; } if ( ! target ) { throw new CompileError ( token , 'Undefined name' ) ; } this . evalIfNeeded ( target ) ; if ( ! target . value && this . analyzing ) { let lastToken = arrayLast ( this . tokens ) ; if ( lastToken . type === 'call' ) { throw new CompileError ( lastToken , 'Recursive call outside secondary try clause' ) ; } throw new CompileError ( lastToken , 'Circular reference' ) ; } let conditional = ! ! target . conditional ; if ( target . container instanceof Code && ! target . inputLike ) { assert ( target . container . containingItem . contains ( from ) ) ; conditional = false ; } if ( conditional && token . text === '~' ) { assert ( this . conditional ) ; guard = '!' ; } if ( ! ! guard !== conditional ) { throw new CompileError ( token , guard ? `invalid reference suffix ${ guard }` : 'conditional reference lacks suffix ? or !' ) ; } if ( conditional && guard === '?' ) { this . conditional = true ; } } ids . push ( target . id ) ; guards . push ( guard ) ; } target . resolve ( ) ; if ( target . evaluated === undefined && ! ( this instanceof Selection ) ) { throw new CompileError ( arrayLast ( this . tokens ) , 'Circular reference' ) ; } this . path = new Path ( ids ) ; this . guards = guards ; } /** reference previous value, disallow if a data block conditional. This is OK
     * in code block because will already have rejected
     *
     * TODO: maybe allow 'that?' and 'that!' to explicitly guard previous
     * reference in data block.
     */ private previous ( from , token ) { let item = from . previous ( ) ; if ( ! item ) { throw new CompileError ( token , 'No previous value' ) ; } if ( item . conditional && ! ( item . container instanceof Code ) && ! ( item . container instanceof Choice ) ) { throw new CompileError ( token , 'Previous value is conditional: use explicit guarded reference' ) ; } return item ; } uneval ( ) { this . rejected = false ; this . target = undefined ; } /** make copy, bottom up, translating paths contextually */ copy ( srcPath , dstPath ) { assert ( this . path ) ; let to = another ( this ) ; to . tokens = this . tokens ; to . path = this . path . translate ( srcPath , dstPath ) ; if ( srcPath . contains ( this . path ) ) { assert ( this . guards . slice ( 0 , srcPath . length ) . every ( guard => guard === undefined ) ) ; to . guards = [ ... dstPath . ids . map ( ( ) => undefined ) , ... this . guards . slice ( srcPath . length ) ] ; to . context = this . context + dstPath . ids . length - srcPath . length ; } else { to . guards = this . guards ; to . context = this . context ; } return to ; } /** References are the same type if they reference the same location
     * contextually */ updatableFrom ( from , fromPath ? , thisPath ? ) { if ( ! fromPath ) fromPath = from . containingPath ; if ( ! thisPath ) thisPath = this . containingPath ; return ( from instanceof Reference && arrayEquals ( this . guards , from . guards ) && this . path . equals ( from . path . translate ( fromPath , thisPath ) ) ) ; } equals ( other ) { return true ; } dump ( ) { let ids = this . path . ids ; if ( ids [ 0 ] === this . containingPath . ids [ 0 ] ) { ids = ids . slice ( 1 ) ; } return ids . join ( '.' ) ; } } /** Dependent reference to a choice option on target of # */ export class OptionReference extends Reference { /** name token for option */ optionToken ! : Token ; /** FieldID of option */ optionID ! : FieldID ; eval ( ) { super . eval ( ) ; if ( ! this . optionID ) { let choice = this . target ! ; if ( ! ( choice . value instanceof Choice ) ) { throw new CompileError ( this . optionToken , 'expecting choice' ) ; } let option = choice . getMaybe ( this . optionToken . text ) ; if ( ! option ) { throw new CompileError ( this . optionToken , 'no such option' ) ; } this . optionID = cast ( option . id , FieldID ) ; } } copy ( srcPath , dstPath ) { let to = super . copy ( srcPath , dstPath ) ; to . optionToken = this . optionToken ; to . optionID = this . optionID ; return to ; } updatableFrom ( from , fromPath ? , thisPath ? ) { return ( from instanceof OptionReference && this . optionID === from . optionID && super . updatableFrom ( from , fromPath , thisPath ) ) ; } }