import { Do , CompileError , arrayLast , Crash , Field , Code , assert , cast , Choice , assertDefined , trap , Item , Reference , Statement , arrayRemove } from "./exports" ; /** a try block is the basic control structure of Subtext. It contains a sequnce
 * of do-blocks called clauses. The clauses are executed in order until one does
 * not reject */ export class Try extends Code { /** evaluate, setting this.result to first successful clause, else
     * this.rejected. this.conditional true if fall-through rejects instead of crashing
     * */ eval ( ) { if ( this . result || this . rejected ) { return ; } if ( this . analyzing ) { /** FIXME - this has caused so much pain! Needed to allow implicitly
                recursive functions. Instead have an explicit `recursive-type exp`
                statement following the arguments. Maybe too late for this
                implementation. Note recursive choices will probably still need
                deferred analysis and copying */ let first = this . fields [ 0 ] ; for ( let clause of this . fields ) { /** function to analyze clause */ const analyzeClause = ( ) => { clause . eval ( ) ; if ( ! clause . conditional && ( this . conditional || clause !== arrayLast ( this . fields ) ) ) { throw new CompileError ( clause , 'clause must be conditional if not last' ) ; } first . eval ( ) ; if ( clause === first ) { this . result = first ; ( cast ( first . get ( '^code' ) . value , Code ) . result ! ) . uncopy ( ) ; } else { if ( ! first . value ! . updatableFrom ( clause . value ! ) ) { let code = clause . get ( '^code' ) ; code . editError = 'type' ; assert ( ! code . isDerived ) ; clause . propagateError ( code ) ; } if ( this . getMaybe ( '^export' ) && ! clause . getMaybe ( '^export' ) ) { throw new CompileError ( clause , 'all clauses must export' ) ; } } } ; if ( clause === first ) { analyzeClause ( ) ; } else if ( ! clause . deferral ) { assert ( clause . evaluated === false ) ; clause . evaluated = undefined ; clause . deferral = analyzeClause ; this . workspace . analysisQueue . push ( clause ) ; } continue ; } let exportField = first . getMaybe ( '^export' ) ; if ( exportField ) { let choice = new Choice ; this . export = this . containingItem . setMeta ( '^export' , choice ) ; this . export . conditional = this . conditional ; this . fields . forEach ( clause => { let option = new Field ; if ( clause . id . name === undefined ) { throw new CompileError ( clause , 'exporting clause must be named' ) ; } option . id = clause . id ; choice . add ( option ) ; option . io = 'input' ; option . conditional = true ; this . export ! . workspace . analysisQueue . push ( option ) ; option . deferral = ( ) => { clause . resolve ( ) ; let clauseExport = cast ( clause . get ( '^code' ) . value , Code ) . export ; if ( ! clauseExport ) { throw new CompileError ( clause , 'all clauses must export' ) ; } this . fieldImport ( option , clauseExport ) ; option . eval ( ) ; } ; } ) ; } return ; } for ( let clause of this . fields ) { clause . eval ( ) ; if ( ! clause . rejected ) { this . result = clause ; this . propagateError ( clause ) ; if ( clause . get ( '^code' ) . editError === 'type' ) { this . result = this . fields [ 0 ] ; } this . export = this . containingItem . getMaybe ( '^export' ) ; if ( this . export ) { let choice = cast ( this . export . value , Choice ) ; let option = choice . setChoice ( this . fields . indexOf ( clause ) ) ; option . detachValueIf ( ) ; option . copyValue ( assertDefined ( clause . getMaybe ( '^export' ) ) ) ; } break ; } } if ( ! this . result ) { if ( this . conditional ) { this . rejected = true ; let exportField = this . getMaybe ( '^export' ) ; if ( exportField ) { exportField . rejected = true ; } } else { throw new Crash ( arrayLast ( this . fields ) . id . token ! , 'try failed' ) ; } } } /** nested write statements. During analysis merges writes of clauses */ writeStatements ( ) { if ( ! this . analyzing ) { for ( let clause of this . statements ) { if ( ! clause . rejected ) { return clause . writeStatements ( ) ; } } return [ ] ; } let mergedWrites = [ ] ; for ( let clause of this . statements ) { let prevMerged = mergedWrites . slice ( ) ; writeLoop : for ( let write of clause . writeStatements ( ) ) { let writeTarget = cast ( write . get ( '^target' ) . value , Reference ) . target ! ; for ( let merged of prevMerged ) { let mergedTarget = cast ( merged . get ( '^target' ) . value , Reference ) . target ! ; if ( writeTarget === mergedTarget ) { continue writeLoop ; } if ( writeTarget . contains ( mergedTarget ) && writeTarget . writeSink ( mergedTarget ) ! . io !== 'interface' ) { arrayRemove ( mergedWrites , merged ) ; } else if ( mergedTarget . contains ( writeTarget ) && mergedTarget . writeSink ( writeTarget ) ! . io !== 'interface' ) { continue writeLoop ; } } mergedWrites . push ( write ) ; continue writeLoop ; } } return mergedWrites ; } }