import { Head , History , Item , Path , Parser , Version , VersionID , FieldID , Token , Statement , CompileError , Try , Call , Do , With , Reference , assertDefined , Container , _Array , isNumber } from "./exports" ; /** A subtext workspace */ export class Workspace extends Item < never , History > { /** Workspace is at the top of the tree */ declare container : never ; _path = Path . empty ; /** whether eval() should do analysis */ private _analyzing : boolean = false ; get analyzing ( ) { return this . _analyzing ; } /** serial numbers assigned to FieldIDs */ private fieldSerial = 0 ; newFieldID ( name ? , token ? ) { let serial = ++ this . fieldSerial ; let id = new FieldID ( serial ) ; id . name = name ; id . token = token ; return id ; } private newVersionID ( name ) { let serial = ++ this . fieldSerial ; let id = new VersionID ( serial ) ; id . name = name ; return id ; } /** add a new version to history */ private newVersion ( ) { let history = this . value ! ; let newVersion = new Version ; newVersion . id = this . newVersionID ( new Date ( ) . toLocaleString ( ) ) ; newVersion . io = 'data' ; newVersion . formulaType = 'none' ; history . add ( newVersion ) ; return newVersion ; } /** current version of workspace */ get currentVersion ( ) { return this . value ! . currentVersion ; } /** queue of items with deferred analysis */ analysisQueue : Item [ ] = [ ] ; /** queue of functions to analyze exports */ exportAnalysisQueue : ( ( ) => void ) [ ] = [ ] ; param source param builtin throws static compile ( source , builtins = true ) { if ( builtins ) { source = "builtins = include builtins\n" + source ; } let ws = new Workspace ; let history = new History ; ws . value = history ; history . containingItem = ws ; let version = new Version ; version . id = ws . newVersionID ( 'initial' ) ; history . add ( version ) ; let head = new Head ; version . value = head ; head . containingItem = version ; let parser = new Parser ( source ) ; parser . requireHead ( head ) ; ws . analyze ( version ) ; version . evaluated = true ; return ws ; } /** analyze a version. Sets Item.editError for errors that can occur during
     * editing. Throws exceptions on errors that only occur in compiling. */ analyze ( version ) { const head = assertDefined ( version . value ) ; this . _analyzing = true ; head . eval ( ) ; while ( this . analysisQueue . length ) { this . analysisQueue . shift ( ) ! . resolve ( ) ; } while ( this . exportAnalysisQueue . length ) { this . exportAnalysisQueue . shift ( ) ! ( ) ; } const analyzeUpdates = ( container ) => { for ( let item of container . items ) { if ( item . io === 'interface' ) { let delta = item . setDelta ( item ) ; delta . uncopy ( ) ; version . feedback ( version , item ) ; } else if ( item . io === 'input' && item . value instanceof Container ) { analyzeUpdates ( item . value ) ; } } } ; analyzeUpdates ( head ) ; head . uneval ( ) ; this . _analyzing = false ; for ( let item of version . visit ( ) ) { if ( item . path . ids . slice ( version . path . length ) . find ( id => isNumber ( id ) && id !== 0 ) ) { continue ; } if ( item instanceof Statement && ! item . used && ( item . dataflow === undefined || item . dataflow === 'let' ) && ! ( item . container instanceof Try ) && ! ( item . container instanceof Call ) ) { throw new CompileError ( item , 'unused value' ) ; } if ( item . value instanceof Do && item . usesPrevious ) { throw new CompileError ( item , 'do-block cannot use previous value' ) ; } if ( item . value instanceof With && ! item . usesPrevious ) { throw new CompileError ( item , 'with-block must use previous value' ) ; } } head . eval ( ) ; } /** dump item at string path in current version */ dumpAt ( path ) { return this . currentVersion . down ( path ) . dump ( ) ; } param path param formula updateAt ( path , formula ) { let target = this . currentVersion . down ( path ) ; if ( ! this . currentVersion . writeSink ( target ) ) { throw 'not updatable' ; } let targetRef = new Reference ; targetRef . path = target . path ; targetRef . tokens = [ new Token ( 'that' , 0 , 0 , '' ) ] ; targetRef . context = 1 ; targetRef . guards = [ ] ; for ( let up = target ; ! ! up . container ; up = up . container . containingItem ) { targetRef . guards . unshift ( up . conditional ? '!' : undefined ) ; } let newVersion = this . newVersion ( ) ; newVersion . formulaType = 'update' ; newVersion . setMeta ( '^target' , targetRef ) ; let payload = newVersion . setMeta ( '^payload' ) ; let parser = new Parser ( formula ) ; parser . space = this ; parser . requireFormula ( payload ) ; newVersion . eval ( ) ; if ( newVersion . editError ) { throw 'edit error: ' + newVersion . originalEditError ; } } /** write a JS value to a path */ writeAt ( path , value ) { let formula ; if ( typeof value === 'string' ) { formula = "'" + value + "'" ; } else { formula = value . toString ( ) ; } this . updateAt ( path , formula ) ; } /** set a path to `on` */ turnOn ( path ) { this . updateAt ( path , 'on' ) ; } /** create an item in an array */ createAt ( path ) { this . updateAt ( path , '&()' ) ; } /** delete an item from an array */ deleteAt ( path , index ) { this . updateAt ( path , 'delete! ' + index ) ; } /** add a selection */ selectAt ( path , index ) { this . updateAt ( path , 'select! ' + index ) ; } /** remove a selection */ deselectAt ( path , index ) { this . updateAt ( path , 'deselect! ' + index ) ; } param path param command editAt ( path , command ) { let target = this . currentVersion . down ( path ) ; let targetRef = new Reference ; targetRef . path = target . path ; targetRef . tokens = [ new Token ( 'that' , 0 , 0 , '' ) ] ; targetRef . context = 1 ; targetRef . guards = [ ] ; for ( let up = target ; ! ! up . container ; up = up . container . containingItem ) { targetRef . guards . unshift ( undefined ) ; } let newVersion = this . newVersion ( ) ; newVersion . formulaType = 'update' ; newVersion . setMeta ( '^target' , targetRef ) ; let parser = new Parser ( command ) ; parser . space = this ; parser . requireEdit ( newVersion ) ; newVersion . eval ( ) ; } /** array of items in current version with edit errors */ get editErrors ( ) : Item [ ] { return this . currentVersion . editErrors ; } /** Array of edit error messages in current version */ get editErrorMessages ( ) : string [ ] { return this . currentVersion . editErrorMessages ; } }