module author Paolo Masci
 *  date copyright import { ProofNode , ProofNodeType , ProofDescriptor , ProofStatus , serverEvent , ProofTree , PvsFormula , ProofNodeX , PvsProofCommand , ProofEditCopyNode , ProofEditAppendNode , ProofEditDidCopyNode , ProofEditPasteNode , ProofExecFastForward , ProofEditCopyTree , ProofEditDidCopyTree , ProofEditPasteTree , ProofEditDeleteNode , ProofEditDidDeleteNode , ProofEditAppendBranch , ProofEditCutNode , ProofEditDidCutNode , ProofEditCutTree , ProofEditDidCutTree , ProofEditDeleteTree , ProofEditTrimNode , ProofEditDidTrimNode , ProofEditRenameNode , ProofEditDidRenameNode , ProofEditDidAppendNode , PvsContextDescriptor , ProofEditDidActivateCursor , ProofEditDidDeactivateCursor , ProofEditDidUpdateProofStatus , ProofExecDidLoadProof , ProofExecDidLoadSequent , ProofExecDidStartProof , ProofExecDidUpdateSequent , ProofEditTrimUnused , ProofEditDidStartNewProof , ProofExecDidOpenProof , PvsFile , ProofExecDidImportProof , ProofExecRewind , ProofExecDidStopRunning , ProofCommandResponse , ProofOrigin , SequentDescriptor , ProveFormulaRequest , ProofExecDidQuitProof , FileDescriptor , ProofEditDidUpdateDirtyFlag , serverRequest , SaveProofResponse , ProofExecRunSubtree , ProofEditJumpHere } from '../common/serverInterface' ; import * as languageUtils from '../common/languageUtils' ; import * as fsUtils from '../common/fsUtils' ; import { PvsResponse } from '../common/pvs-gui' ; import { isEmptyCommand , isFailCommand , isInvalidCommand , isPostponeCommand , isProofliteGlassbox , isQEDCommand , isQuitCommand , isQuitDontSaveCommand , isSameCommand , isSaveThenQuitCommand , isShowHiddenFormulas , isUndoCommand , isUndoUndoCommand , isUndoUndoPlusCommand , splitCommands } from '../common/languageUtils' ; import { Connection } from 'vscode-languageserver' ; import { PvsProxy } from '../pvsProxy' ; import { PvsLanguageServer } from '../pvsLanguageServer' ; import { saveProofDescriptor } from '../common/fsUtils' ; abstract class TreeItem { id : string ; connection : Connection ; constructor ( label , connection ) { this . id = fsUtils . get_fresh_id ( ) ; this . connection = connection ; } } /**
 * Proof Explorer
 */ export class PvsProofExplorer { protected shasum : string ; protected origin : ProofOrigin ; protected interruptFlag : boolean = false ; protected logFileName : string ; protected tmpLogFileName : string ; protected dirtyFlag : boolean = false ; protected qedNotificationSent : boolean = false ; protected connection : Connection ; protected pvsProxy : PvsProxy ; protected pvsLanguageServer : PvsLanguageServer ; readonly DEBUG_LOG : boolean = false ; protected log ( ... args ) { if ( this . DEBUG_LOG ) { console . log ( args ) ; } } /**
     * Clipboards for cut/paste operations
     */ protected clipboard : ProofItem = null ; protected clipboardTree : ProofItem [ ] = null ; /**
     * Information on the formula loaded in proof explorer
     **/ protected formula : PvsFormula ; /**
     * Attributes for run-time management of the proof tree rendered in the view
     */ protected root : RootNode = null ; protected ghostNode : GhostNode = null ; protected activeNode : ProofCommand | ProofBranch | GhostNode = null ; protected autorunFlag : boolean = false ; protected autorunCallback : ( status : ProofStatus ) => void ; protected runningFlag : boolean = false ; protected stopAt : ProofItem = null ; protected rewindingFlag : boolean = false ; protected rewindTarget : ProofItem = null ; protected undoundoTarget : ProofItem = null ; /**
     * JSON representation of the proof script for the current proof.
     * The representation is updated at the beginning of the proof session.
     */ protected proofDescriptor : ProofDescriptor ; /**
     * Initial proof state
     */ protected initialProofState : SequentDescriptor ; /**
     * Current proof state
     */ protected proofState : SequentDescriptor ; constructor ( connection , pvsProxy , pvsLanguageServer ) { this . connection = connection ; this . pvsProxy = pvsProxy ; this . pvsLanguageServer = pvsLanguageServer ; } getProofX ( ) { if ( this . root ) { const structure = this . root . getNodeXStructure ( ) ; return structure ; } return null ; } getActiveNode ( ) { if ( this . activeNode ) { return this . activeNode . getNodeXStructure ( ) ; } return null ; } getGhostNode ( ) { if ( this . ghostNode ) { return this . ghostNode . getNodeXStructure ( ) ; } return null ; } ghostNodeIsActive ( ) { return this . ghostNode && this . ghostNode . isActive ( ) ; } isVisited ( desc ) { if ( desc ) { const item = this . findNode ( desc . id ) ; if ( item ) { return item . isVisited ( ) ; } } console . warn ( `[proof-explorer] Warning: failed to check visited flag` ) ; return false ; } isPending ( desc ) { if ( desc ) { const item = this . findNode ( desc . id ) ; if ( item ) { return item . isPending ( ) ; } } console . warn ( `[proof-explorer] Warning: failed to check pending flag` ) ; return false ; } isActive ( desc ) { if ( desc ) { const item = this . findNode ( desc . id ) ; if ( item ) { return item . isActive ( ) ; } } console . warn ( `[proof-explorer] Warning: failed to check active flag` ) ; return false ; } newProof ( ) { const evt = { action : "did-start-new-proof" } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } /**
     * Internal function, moves the active node one position back in the proof tree.
     */ protected moveIndicatorBack ( opt ? ) { if ( this . activeNode ) { opt = opt || { } ; const prev = this . activeNode . moveIndicatorBack ( opt ) ; if ( prev . contextValue !== "root" ) { this . markAsActive ( { selected : prev } , { force : true } ) ; } } else { console . warn ( "[proof-explorer] Warning: active node is null" ) ; } } /**
     * Internal function, moves the active node one position forward in the proof tree.
     */ protected moveIndicatorForward ( opt ? ) { if ( this . activeNode ) { opt = opt || { } ; if ( this . activeNode . contextValue !== "ghost" ) { const next = this . activeNode . moveIndicatorForward ( opt ) || this . ghostNode ; if ( next === this . ghostNode ) { this . ghostNode . parent = this . activeNode . parent ; this . ghostNode . realNode = this . activeNode ; this . ghostNode . updateSequent ( opt . proofState , { internalAction : this . autorunFlag } ) ; this . ghostNode . active ( ) ; this . revealNode ( { selected : this . ghostNode } ) ; if ( this . ghostNode . parent . contextValue === "proof-command" || opt . branchComplete ) { this . ghostNode . parent . visited ( ) ; } else { this . ghostNode . parent . pending ( ) ; } } else { this . markAsActive ( { selected : next } ) ; this . revealNode ( { selected : next } ) ; if ( opt . proofState ) { this . proofState = opt . proofState ; this . activeNode . updateSequent ( this . proofState , { internalAction : this . autorunFlag } ) ; } } return next ; } } else { console . warn ( "[proof-explorer] Warning: active node is null" ) ; } return null ; } /**
     * Executes all proof commands in the proof tree, starting from the active node.
     * The execution stops either at the end of the proof tree, or when an anomaly
     * is detected in the proof tree (e.g,. the prover generates more goals than those
     * indicated in the proof tree)
     */ async run ( opt ? ) { opt = opt || { } ; if ( ! this . runningFlag ) { this . runningFlag = true ; await this . step ( opt ) ; } } param desc async fastForwardTo ( desc ) { if ( desc ?. selected ) { const target = ( desc . selected && desc . selected . contextValue === "proof-branch" && desc . selected . children && desc . selected . children . length ) ? desc . selected . children [ 0 ] : desc . selected ; if ( ! target ?. isVisited ( ) && this . activeNode && ! this . ghostNode ?. isActive ( ) ) { this . stopAt = target ; this . runningFlag = true ; await this . step ( { feedbackToTerminal : true } ) ; } else { } } else { console . warn ( `[proof-explorer] Warning: failed to fast forward (selected node is null)` ) ; } } async fastForwardToNodeX ( desc ) { if ( desc ?. selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { await this . fastForwardTo ( { selected } ) ; } } else { console . warn ( `[proof-explorer] Warning: failed to perform proof exec/fast forward (selected node is null)` ) ; } } param desc async runSubtree ( desc ) { if ( desc ?. selected && desc . selected . contextValue !== "ghost" ) { const lastNode = desc . selected . contextValue === "proof-branch" ? desc . selected . getLastChildInSubtree ( ) : desc . selected . parent ?. getLastChildInSubtree ( ) ; if ( lastNode ) { await this . fastForwardTo ( { selected : lastNode } ) ; if ( this . activeNode ?. id === lastNode . id ) { await this . step ( { feedbackToTerminal : true } ) ; } } } else { console . warn ( `[proof-explorer] Warning: failed to run-subtree (selected node is null)` ) ; } } async runSubtreeX ( desc ) { if ( desc ?. selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { await this . runSubtree ( { selected } ) ; } } else { console . warn ( `[proof-explorer] Warning: failed to perform proof exec/sub-subtree (selected node is null)` ) ; } } param desc async rewindTo ( desc ) { if ( desc && desc . selected ) { const target = ( desc . selected && desc . selected . contextValue === "proof-branch" && this . activeNode . children && this . activeNode . children . length ) ? desc . selected . children [ 0 ] : desc . selected ; if ( target ?. isVisited ( ) && this . activeNode ) { this . rewindTarget = target ; this . rewindingFlag = true ; await this . step ( { cmd : "(undo)" , feedbackToTerminal : true } ) ; } } else { console . warn ( `[proof-explorer] Warning: failed to rewind (selected node is null)` ) ; } } async rewindToNodeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { await this . rewindTo ( { selected } ) ; } } else { console . warn ( `[proof-explorer] Warning: failed to perform proof exec/fast forward (selected node is null)` ) ; } } /**
     * repeats the last command
     */ async redo ( ) { return await this . forward ( ) ; } /**
     * executes the next step
     */ async forward ( ) { return await this . step ( { feedbackToTerminal : true } ) ; } /**
     *  goes one step back (undo)
     */ async back ( ) { return await this . undo ( ) ; } /**
     * goes one step back
     */ async undo ( ) { return await this . step ( { cmd : "undo" , feedbackToTerminal : true } ) ; } param cmd async step ( opt ? ) { opt = opt || { } ; if ( this . stopAt && this . activeNode && this . stopAt . id === this . activeNode . id ) { this . stopAt = null ; this . runningFlag = false ; if ( this . connection ) { const evt = { action : "did-stop-running" , sequent : this . proofState } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } return null ; } const cmd = ( opt . cmd ) ? opt . cmd : ( this . activeNode ?. contextValue === "proof-command" && ( this . ghostNode && ! this . ghostNode . isActive ( ) ) ) ? this . activeNode ?. name : null ; if ( cmd ) { if ( this . runningFlag && ! this . autorunFlag && isPostponeCommand ( cmd ) ) { if ( this . stopAt ) { if ( this . stopAt === this . activeNode ) { this . runningFlag = false ; return null ; } } else { this . stopAt = this . activeNode ; } } if ( opt . feedbackToTerminal && ! this . autorunFlag ) { } const command = { fileName : this . formula . fileName , fileExtension : this . formula . fileExtension , theoryName : this . formula . theoryName , formulaName : this . formula . formulaName , contextFolder : this . formula . contextFolder , cmd : cmd . startsWith ( "(" ) ? cmd : `(${ cmd })` } ; const response = await this . proofCommand ( command ) ; if ( this . interruptFlag ) { this . interruptFlag = false ; if ( this . autorunFlag ) { this . runningFlag = false ; this . stopAt = null ; await this . quitProof ( ) ; return null ; } } if ( response ?. result ?. length ) { for ( let i = 0 ; i < response . result . length ; i ++ ) { const proofState = response . result [ i ] ; await this . onStepExecutedNew ( { proofState , args : command , lastSequent : i === response . result . length - 1 } , opt ) ; } if ( this . runningFlag && ! this . ghostNode . isActive ( ) ) { await this . step ( opt ) ; } } return response ; } if ( this . autorunFlag ) { this . runningFlag = false ; this . stopAt = null ; if ( this . root . getProofStatus ( ) !== "untried" ) { this . root . setProofStatus ( "unfinished" ) ; } await this . quitProofAndSave ( { jprfOnly : true } ) ; return null ; } return null ; } protected restoreTreeAttributes ( ) { if ( this . root ) { this . root . restoreTreeAttributes ( ) ; } } protected saveTreeAttributes ( ) { if ( this . root ) { this . root . saveTreeAttributes ( ) ; } } param desc protected proofStructureHasChanged ( desc ) { if ( desc ) { return ! desc . targetBranch || ! desc . activeNode . children || desc . activeNode . children . filter ( ( item ) => { return item . name === desc . targetBranch . name ; } ) . length === 0 ; } return false ; } /**
     * Utility function, stops rewinding a proof
     */ /**
     * Utility function, stops running / rewinding a proof
     */ stopRun ( ) { this . runningFlag = false ; this . stopAt = null ; this . rewindTarget = null ; this . rewindingFlag = false ; if ( this . connection ) { const evt = { action : "did-stop-running" , sequent : this . proofState } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } param desc async onStepExecutedNew ( desc , opt ? ) { if ( desc && desc . proofState ) { let userCmd = desc . args ? desc . args . cmd : null ; const cmd = ( typeof desc . proofState [ "prev-cmd" ] === "string" ) ? desc . proofState [ "prev-cmd" ] : userCmd ; this . proofState = desc . proofState ; let activeNode = this . ghostNode . isActive ( ) ? this . ghostNode . realNode : this . activeNode ; if ( languageUtils . QED ( this . proofState ) ) { this . runningFlag = false ; this . stopAt = null ; if ( this . proofState . sequent && ( isSameCommand ( activeNode . name , cmd ) === false || isSameCommand ( activeNode . name , userCmd ) === false || this . ghostNode . isActive ( ) ) ) { const elem = new ProofCommand ( cmd , activeNode . branchId , activeNode . parent , this . connection ) ; if ( activeNode . isActive ( ) ) { activeNode . notVisited ( ) ; this . appendNode ( { selected : activeNode , elem , sequent : activeNode . sequentDescriptor } , { beforeSelected : true , internalAction : this . autorunFlag } ) ; } else { const tooltip = ( this . ghostNode . isActive ( ) ) ? this . ghostNode . sequentDescriptor : activeNode . sequentDescriptor ; this . appendNode ( { selected : activeNode , elem , sequent : tooltip } , { internalAction : this . autorunFlag } ) ; } this . markAsActive ( { selected : elem } ) ; activeNode = this . activeNode ; } if ( activeNode ) { if ( isProofliteGlassbox ( activeNode . name ) ) { this . deleteNode ( { selected : activeNode } ) ; } else { this . trimNode ( { selected : activeNode } ) ; } } this . ghostNode . notActive ( ) ; await this . proved ( ) ; this . pvsProxy . lispMode ( ) ; return ; } if ( isQuitCommand ( cmd ) ) { this . runningFlag = false ; this . stopAt = null ; return ; } const previousBranchName = activeNode . branchId ; const currentBranchName = languageUtils . getBranchId ( this . proofState . label ) ; const previousBranch = this . findProofBranch ( previousBranchName ) ; const currentBranch = this . findProofBranch ( currentBranchName ) ; const branchCompleted = ( previousBranch && ! previousBranch . isComplete ( ) && languageUtils . branchComplete ( this . proofState , this . formula . formulaName , previousBranchName ) ) || ( currentBranch && ! currentBranch . isComplete ( ) && languageUtils . branchComplete ( this . proofState , this . formula . formulaName , currentBranchName ) ) ; const pathHasChanged = languageUtils . pathHasChanged ( { newBranch : currentBranchName , previousBranch : previousBranchName } ) && ! branchCompleted ; if ( opt . feedbackToTerminal && ! this . autorunFlag ) { const ans = { res : this . proofState , req : desc ?. args } ; this . pvsLanguageServer . getConnection ( ) ?. sendRequest ( serverEvent . proofCommandResponse , ans ) ; } if ( isUndoCommand ( userCmd ) ) { this . runningFlag = false ; this . stopAt = null ; this . undoundoTarget = this . activeNode ; this . saveTreeAttributes ( ) ; if ( languageUtils . branchHasChanged ( { newBranch : currentBranchName , previousBranch : previousBranchName } ) ) { const targetBranch = this . findProofBranch ( currentBranchName ) ; if ( targetBranch ) { this . activeNode . notVisited ( ) ; const visitedChildren = targetBranch . children . filter ( ( elem ) => { return elem . contextValue === "proof-command" && elem . isVisited ( ) ; } ) ; const targetNode = visitedChildren . length ? visitedChildren [ visitedChildren . length - 1 ] : targetBranch ; targetNode . treeNotVisited ( ) ; this . revealNode ( { selected : targetNode } ) ; this . markAsActive ( { selected : targetNode } ) ; } else { console . error ( `[proof-explorer] Error: could not find branch ${ currentBranchName } in the proof tree` ) ; } } else { this . moveIndicatorBack ( { keepSameBranch : true } ) ; } if ( this . rewindingFlag ) { if ( this . rewindTarget ?. isActive ( ) || ! this . rewindTarget ?. isVisitedOrPending ( ) ) { this . stopRun ( ) ; } else { this . step ( { cmd : "(undo)" , feedbackToTerminal : true } ) ; } } return ; } if ( isPostponeCommand ( userCmd , this . proofState ) || pathHasChanged ) { if ( this . runningFlag && isPostponeCommand ( userCmd , this . proofState ) ) { if ( this . autorunFlag ) { this . runningFlag = false ; this . stopAt = null ; if ( this . root . getProofStatus ( ) !== "untried" ) { this . root . setProofStatus ( "unfinished" ) ; } await this . quitProofAndSave ( { jprfOnly : true } ) ; return ; } } if ( languageUtils . branchHasChanged ( { newBranch : currentBranchName , previousBranch : previousBranchName } ) ) { if ( this . ghostNode . isActive ( ) ) { this . ghostNode . notActive ( ) ; } else { this . activeNode . notVisited ( ) ; } const targetBranch = this . findProofBranch ( currentBranchName ) || this . createBranchRecursive ( { id : currentBranchName } , { internalAction : this . autorunFlag } ) ; if ( targetBranch ) { if ( this . activeNode . contextValue !== "proof-command" ) { this . activeNode . notVisited ( ) ; } const visitedChildren = targetBranch . children . filter ( ( elem ) => { return elem . contextValue === "proof-command" && elem . isVisited ( ) ; } ) ; const targetNode = ( visitedChildren . length ) ? visitedChildren [ visitedChildren . length - 1 ] : targetBranch ; targetNode . updateSequent ( this . proofState , { internalAction : this . autorunFlag } ) ; this . markAsActive ( { selected : targetNode } ) ; this . moveIndicatorForward ( { keepSameBranch : true , proofState : this . proofState } ) ; } else { console . error ( `[proof-explorer] Error: could not find branch ${ currentBranchName } in the proof tree. Proof Explorer is out of sync with PVS. Please restart the proof.` ) ; this . runningFlag = false ; this . stopAt = null ; return ; } } else { this . runningFlag = false ; this . stopAt = null ; return ; } } else { if ( isUndoUndoCommand ( userCmd ) ) { if ( this . undoundoTarget ) { let target = this . undoundoTarget ; if ( this . undoundoTarget . isActive ( ) || this . undoundoTarget . contextValue !== "proof-command" ) { this . ghostNode . parent = this . undoundoTarget . parent ; this . ghostNode . realNode = this . undoundoTarget ; target = this . ghostNode ; } this . restoreTreeAttributes ( ) ; this . markAsActive ( { selected : target } ) ; this . undoundoTarget = null ; } else { console . error ( `[proof-explorer] Warning: unable to execute ${ userCmd }` ) ; } return ; } if ( isShowHiddenFormulas ( userCmd ) ) { return ; } if ( languageUtils . isHelpCommand ( userCmd ) || languageUtils . isHelpBangCommand ( userCmd ) ) { } else if ( isUndoUndoPlusCommand ( userCmd ) ) { if ( this . autorunFlag ) { if ( this . root . getProofStatus ( ) !== "untried" ) { this . root . setProofStatus ( "unfinished" ) ; } await this . quitProofAndSave ( { jprfOnly : true } ) ; } } else if ( isInvalidCommand ( this . proofState ) ) { if ( isSameCommand ( activeNode . name , cmd ) || isSameCommand ( activeNode . name , userCmd ) ) { if ( ! this . autorunFlag && activeNode . children ?. length && ! this . ghostNode . isActive ( ) ) { activeNode . treeNotVisited ( ) ; this . cutTreeX ( { action : "cut-tree" , selected : activeNode , keepRoot : true } ) ; } this . moveIndicatorForward ( { keepSameBranch : true , proofState : this . proofState } ) ; activeNode . treeNotVisited ( ) ; } } else if ( languageUtils . interruptedByClient ( this . proofState ) ) { this . stopRun ( ) ; } else if ( languageUtils . noChange ( this . proofState ) || isEmptyCommand ( cmd ) ) { const command = languageUtils . getNoChangeCommand ( this . proofState ) ; if ( ( isSameCommand ( activeNode . name , command ) || isSameCommand ( activeNode . name , userCmd ) || isSameCommand ( activeNode . name , cmd ) ) && ! this . ghostNode . isActive ( ) ) { this . moveIndicatorForward ( { keepSameBranch : true , proofState : this . proofState } ) ; activeNode . treeNotVisited ( ) ; if ( ! this . autorunFlag && activeNode . children ?. length ) { this . cutTreeX ( { action : "cut-tree" , selected : activeNode , keepRoot : true } ) ; } } } else { if ( ! ( isSameCommand ( activeNode . name , cmd ) || isSameCommand ( activeNode . name , userCmd ) ) || this . ghostNode . isActive ( ) ) { const elem = new ProofCommand ( cmd , activeNode . branchId , activeNode . parent , this . connection ) ; if ( activeNode . isActive ( ) ) { const sequent = activeNode . sequentDescriptor ; activeNode . notVisited ( ) ; this . appendNode ( { selected : activeNode , elem , sequent } , { beforeSelected : true } ) ; } else { const sequent = ( this . ghostNode . isActive ( ) ) ? this . ghostNode . sequentDescriptor : activeNode . sequentDescriptor ; this . appendNode ( { selected : activeNode , elem , sequent } ) ; } this . markAsActive ( { selected : elem } ) ; activeNode = this . activeNode ; if ( languageUtils . branchHasChanged ( { newBranch : currentBranchName , previousBranch : previousBranchName } ) ) { this . trimNode ( { selected : activeNode } ) ; } } if ( isSameCommand ( activeNode . name , cmd ) || isSameCommand ( activeNode . name , userCmd ) ) { this . activeNode . rename ( cmd ) ; } if ( languageUtils . branchComplete ( this . proofState , this . formula . formulaName , previousBranchName ) ) { const selected = this . findProofBranch ( previousBranchName ) ; this . removeNotVisited ( { selected } ) ; selected . treeVisited ( ) ; selected . treeComplete ( ) ; } if ( languageUtils . branchComplete ( this . proofState , this . formula . formulaName , currentBranchName ) ) { const selected = this . findProofBranch ( currentBranchName ) ; this . removeNotVisited ( { selected } ) ; selected . treeVisited ( ) ; selected . treeComplete ( ) ; selected . bubbleVisitedAndComplete ( ) ; } if ( languageUtils . branchHasChanged ( { newBranch : currentBranchName , previousBranch : previousBranchName } ) ) { if ( activeNode . children ?. length === 0 && this . proofState [ "num-subgoals" ] > 1 && ! this . autorunFlag ) { activeNode . getNextSibling ( ) ? this . cutTreeX ( { action : "cut-tree" , selected : activeNode , keepRoot : true } ) : this . trimNode ( { selected : activeNode } ) ; } const targetBranch = this . findProofBranch ( currentBranchName ) || this . createBranchRecursive ( { id : currentBranchName } , { internalAction : this . autorunFlag } ) ; if ( targetBranch ) { targetBranch . updateSequent ( this . proofState , { internalAction : this . autorunFlag } ) ; activeNode . visited ( ) ; const visitedChildren = targetBranch . children . filter ( ( elem ) => { return elem . contextValue === "proof-command" && elem . isVisited ( ) ; } ) ; const targetNode = visitedChildren . length ? visitedChildren [ visitedChildren . length - 1 ] : targetBranch ; targetNode . pending ( ) ; this . markAsActive ( { selected : targetNode } ) ; } else { console . error ( `[proof-explorer] Error: could not find branch ${ targetBranch } in the proof tree` ) ; } } else { if ( ! this . autorunFlag && activeNode ?. children ?. length ) { this . cutTreeX ( { action : "cut-tree" , selected : activeNode , keepRoot : true } ) ; } } this . moveIndicatorForward ( { keepSameBranch : true , proofState : this . proofState , branchComplete : branchCompleted } ) ; } } if ( this . ghostNode . isActive ( ) && ! branchCompleted && desc . lastSequent ) { this . runningFlag = false ; if ( this . autorunFlag ) { if ( this . root . getProofStatus ( ) !== "untried" ) { this . root . setProofStatus ( "unfinished" ) ; } await this . quitProofAndSave ( { jprfOnly : true } ) ; } else { if ( this . connection ) { const evt = { action : "did-stop-running" , sequent : this . proofState } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } } else { this . runningFlag = false ; console . error ( "[proof-explorer] Error: could not read proof state information returned by pvs-server." ) ; if ( this . connection ) { const evt = { action : "did-stop-running" , sequent : this . proofState } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } param id protected findProofBranch ( id ) { if ( id === "" ) { return this . root ; } const findNodeAux = ( id , node ) : ProofBranch | null => { if ( node ) { if ( node . contextValue === "proof-branch" && node . branchNameEquals ( id ) ) { return < ProofBranch > node ; } for ( let i = 0 ; i < node . children . length ; i ++ ) { const res = findNodeAux ( id , node . children [ i ] ) ; if ( res ) { return res ; } } } return null ; } ; return findNodeAux ( id , this . root ) ; } protected findNode ( id ) { const findNodeAux = ( id , node ) : ProofBranch | null => { if ( node && node . id === id ) { return node ; } for ( let i = 0 ; i < node . children . length ; i ++ ) { const res = findNodeAux ( id , node . children [ i ] ) ; if ( res ) { return res ; } } return null ; } ; return findNodeAux ( id , this . root ) ; } param id protected findFirstNotVisited ( node ) { if ( node && ! node . isVisited ( ) && ! node . isPending ( ) ) { return node ; } for ( let i = 0 ; i < node . children . length ; i ++ ) { const res = this . findFirstNotVisited ( node . children [ i ] ) ; if ( res ) { return res ; } } return null ; } protected findLastVisitedOrActive ( branch ) { if ( branch ) { if ( branch . children && branch . children . length ) { const candidates = branch . children . filter ( elem => { return elem . isVisited ( ) || elem . isActive ( ) ; } ) ; if ( candidates && candidates . length ) { return candidates [ candidates . length - 1 ] ; } } } return null ; } param id protected createBranchRecursive ( desc , opt ? ) { opt = opt || { } ; let branch = null ; if ( desc && desc . id ) { const depth = desc . id . split ( "." ) . length ; let lastValidParent = this . findLastVisitedOrActive ( this . root ) || this . root ; for ( let i = 0 ; i < depth ; i ++ ) { const branchId = desc . id . split ( "." ) . slice ( 0 , i + 1 ) . join ( "." ) ; branch = this . findProofBranch ( branchId ) ; if ( branch ) { lastValidParent = this . findLastVisitedOrActive ( branch ) || branch ; } else { branch = new ProofBranch ( `(${ branchId })` , branchId , lastValidParent , this . connection ) ; this . appendBranch ( { selected : lastValidParent , elem : branch } , { proofState : this . proofState , internalAction : opt . internalAction } ) ; lastValidParent = branch ; } } } else { console . warn ( `[proof-explorer] Warning: trying to create a new branch but branch descriptor was not provided` ) ; } return branch ; } param desc markAsPending ( desc ) { if ( desc && desc . selected && desc . selected . id !== this . activeNode . id ) { if ( desc . selected . contextValue === "proof-branch" ) { desc . selected . treeNotVisited ( ) ; desc . selected . pending ( ) ; } else { desc . selected . pending ( ) ; } } else { console . warn ( `[proof-explorer] Warning: could not mark node as visited` ) ; } } param desc markAsVisited ( desc ) { if ( desc && desc . selected && desc . selected . id !== this . activeNode . id ) { if ( desc . selected . contextValue === "proof-branch" ) { desc . selected . treeVisited ( ) ; } else { desc . selected . visited ( ) ; } } else { console . warn ( `[proof-explorer] Warning: could not mark node as visited` ) ; } } param desc markAsNotVisited ( desc ) { if ( desc && desc . selected && desc . selected . id !== this . activeNode . id ) { if ( desc . selected . contextValue === "proof-branch" ) { desc . selected . treeNotVisited ( ) ; } else { desc . selected . notVisited ( ) ; } } else { console . warn ( `[proof-explorer] Warning: could not to mark node as not visited` ) ; } } param desc jumpTo ( desc , opt ? ) { if ( desc . selected . contextValue === "proof-branch" && desc . selected . children && desc . selected . children . length ) { desc . selected = desc . selected . children [ 0 ] ; } return this . markAsActive ( desc , opt ) ; } param desc protected markAsActive ( desc , opt ? ) { if ( desc && desc . selected ) { opt = opt || { } ; if ( opt . force || desc . selected !== this . activeNode ) { this . activeNode = desc . selected ; this . activeNode . active ( ) ; if ( this . activeNode . contextValue !== "ghost" ) { this . ghostNode . parent = this . activeNode . parent ; this . ghostNode . realNode = this . activeNode ; if ( this . activeNode . contextValue === "proof-command" ) { this . ghostNode . notActive ( ) ; } else { this . activeNode . pending ( ) ; this . ghostNode . active ( ) ; } } } } else { console . warn ( `[proof-explorer] Warning: failed to set active node` ) ; } return this . activeNode ; } /**
     * Internal function, marks a proof as dirty, indicating that the proof structure has changed and the proof needs re-saving and re-checking
     */ protected updateDirtyFlag ( flag ) { this . dirtyFlag = ! ! flag ; if ( flag && this . root && this . root . getProofStatus ( ) === "proved" ) { this . root . setProofStatus ( "unchecked" ) ; } if ( this . connection && ! this . autorunFlag ) { const evt = { action : "did-update-dirty-flag" , flag : this . dirtyFlag } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } /**
     * Reveals a node in the view.
     */ protected revealNode ( desc ) { } param desc param opt appendNode ( desc , opt ? ) { if ( desc && desc . selected && desc . elem ) { this . updateDirtyFlag ( true ) ; opt = opt || { } ; const selectedNode = ( desc . selected . contextValue === "ghost" ) ? ( < GhostNode > desc . selected ) . realNode : desc . selected ; const branchId = selectedNode . branchId ; let newNode = ( typeof desc . elem === "string" ) ? new ProofCommand ( desc . elem , branchId , selectedNode . parent , this . connection ) : desc . elem ; if ( newNode ) { switch ( selectedNode . contextValue ) { case "root" : case "proof-branch" : { selectedNode . appendChildAtBeginning ( newNode , opt ) ; newNode . updateSequent ( desc . sequent , { internalAction : this . autorunFlag } ) ; break ; } case "ghost" : case "proof-command" : { if ( newNode . getType ( ) === "proof-branch" ) { selectedNode . appendChildAtBeginning ( newNode , opt ) ; } else { selectedNode . appendSibling ( newNode , opt ) ; } newNode . updateSequent ( desc . sequent , { internalAction : this . autorunFlag } ) ; break ; } default : { } } return newNode ; } else { console . warn ( `[proof-explorer] Warning: failed to create new node` ) ; } } else { console . warn ( `[proof-explorer] Warning: failed to insert after (node is null)` ) ; } return null ; } appendNodeX ( desc , opt ? ) { if ( desc ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { const elem = this . appendNode ( { selected , elem : desc . name , sequent : this . proofState } , opt ) ; return elem ; } } else { console . warn ( `[proof-explorer] Warning: failed to complete proof edit/append (node is null)` ) ; } return null ; } param desc param opt appendBranch ( desc , opt ? ) { if ( desc && desc . selected ) { this . updateDirtyFlag ( true ) ; opt = opt || { } ; let selectedNode = desc . selected ; const branchId = selectedNode . branchId ; let newBranch = desc . elem ; switch ( selectedNode . contextValue ) { case "ghost" : { selectedNode = ( < GhostNode > selectedNode ) . realNode ; } case "proof-command" : { if ( ! newBranch ) { let firstGoal = 1 ; if ( opt . firstBranch && opt . firstBranch . indexOf ( "." ) > 0 ) { firstGoal = + opt . firstBranch . split ( "." ) . slice ( - 1 ) ; } let branchId = languageUtils . makeBranchId ( { branchId : selectedNode . branchId , goalId : firstGoal } ) ; let found = false ; for ( let i = 0 ; i < selectedNode . children . length && ! found ; i ++ ) { if ( selectedNode . children [ i ] . branchId !== branchId ) { found = true ; } else { branchId = languageUtils . makeBranchId ( { branchId : selectedNode . branchId , goalId : firstGoal + i + 1 } ) ; } } newBranch = new ProofBranch ( branchId , branchId , selectedNode , this . connection ) ; if ( opt . proofState ) { newBranch . updateSequent ( opt . proofState , { internalAction : this . autorunFlag } ) ; } } if ( newBranch ) { selectedNode . appendChild ( newBranch ) ; } break ; } case "root" : case "proof-branch" : { const parent = selectedNode ; if ( parent && parent . children ) { if ( ! newBranch ) { const branchName = ( opt . beforeSelected ) ? `` : `${ branchId }.${ parent . children . length }` ; newBranch = new ProofBranch ( branchName , branchId , parent , this . connection ) ; } if ( newBranch ) { newBranch . parent = parent ; const children = [ ] ; const n = parent . children . length ; let position = 0 ; for ( let i = 0 ; i < n ; i ++ ) { if ( ! opt . beforeSelected ) { children . push ( parent . children [ i ] ) ; } if ( parent . children [ i ] . id === selectedNode . id ) { children . push ( newBranch ) ; position = i ; } if ( opt . beforeSelected ) { children . push ( parent . children [ i ] ) ; } } parent . children = children ; if ( ! opt . internalAction && this . connection ) { const elem = newBranch . getNodeXStructure ( ) ; this . log ( `[proof-explorer] Appending branch ${ elem . name } (${ elem . id })` ) ; const evt = { action : "did-append-node" , elem , position } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } } } default : { return false ; } } return true ; } else { console . warn ( `[proof-explorer] Warning: failed to insert after (node is null)` ) ; } return false ; } appendBranchX ( desc , opt ? ) { if ( desc ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { this . appendBranch ( { selected } , opt ) ; return ; } } console . warn ( `[proof-explorer] Warning: failed to complete proof edit/append (node is null)` ) ; } param desc copyNode ( desc ) { if ( desc ?. selected ) { this . clipboard = desc . selected . clone ( ) ; this . clipboardTree = null ; return true ; } console . warn ( `[proof-explorer] Warning: unable to copy selected node` ) ; return false ; } copyNodeX ( desc ) { if ( desc ?. selected ) { if ( desc . data ) { const clips = this . convertNodeX2ProofItem ( desc . data . elem ) ; this . clipboard = clips ?. length ? clips [ 0 ] : null ; this . clipboardTree = null ; if ( this . connection && this . clipboard && ! this . autorunFlag ) { const evt = { action : "did-copy-node" , selected : { id : desc . selected . id , name : desc . selected . name } } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } } else { const selected = this . findNode ( desc . selected . id ) ; if ( selected && this . copyNode ( { selected } ) && ! this . autorunFlag ) { const evt = { action : "did-copy-node" , selected : { id : desc . selected . id , name : desc . selected . name } } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } } } else { console . warn ( `[proof-explorer] Warning: unable to copy selected node` ) ; } } param desc copyTree ( desc , opt ? ) { opt = opt || { } ; opt . updateClipboard = opt . updateClipboard === undefined ? true : false ; if ( desc && desc . selected ) { if ( desc . selected . getType ( ) === "root" ) { if ( desc . selected && desc . selected . children && desc . selected . children . length ) { desc . selected = desc . selected . children [ 0 ] ; } } const parent = desc . selected . parent ; if ( opt . updateClipboard ) { this . clipboard = null ; this . clipboardTree = [ ] ; } let seq = "" ; const n = desc . selected . getType ( ) === "proof-branch" ? parent . children . indexOf ( desc . selected ) + 1 : parent . children . length ; for ( let i = parent . children . indexOf ( desc . selected ) ; i >= 0 && i < n ; i ++ ) { const item = parent . children [ i ] ; if ( item ) { if ( opt . updateClipboard ) { this . clipboardTree . push ( item . cloneTree ( ) ) ; } seq += item . printProofCommands ( ) ; } } return seq ; } else { console . warn ( `[proof-explorer] Warning: unable to copy selected subtree` ) ; } return null ; } copyTreeX ( desc ) { if ( desc && desc . selected ) { if ( desc . data ) { this . clipboard = null ; this . clipboardTree = [ ] ; for ( let i = 0 ; i < desc . data . elems ?. length ; i ++ ) { const items = this . convertNodeX2ProofItem ( desc . data . elems [ i ] ) ; if ( items ?. length ) { this . clipboardTree = this . clipboardTree . concat ( items ) ; } } const evt = { action : "did-copy-tree" , selected : { id : desc . selected . id , name : desc . selected . name } , elems : desc . data . elems , clipboard : desc . data . seq } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } else { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { const seq = this . copyTree ( { selected } ) ; if ( this . connection && seq && ! this . autorunFlag ) { const elems = this . clipboardTree . map ( item => { return item . getNodeXStructure ( ) ; } ) ; const evt = { action : "did-copy-tree" , selected : { id : desc . selected . id , name : desc . selected . name } , elems , clipboard : seq } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } } else { console . warn ( `[proof-explorer] Warning: unable to copy selected node` ) ; } } /**
     * Converts a nodex structure sent by the servwr to a proof item for the tree view
     */ convertNodeX2ProofItem ( elem , parent ? ) { const fromNodeX2 = ( elem , parent ? ) : ProofItem => { const node = ( elem . type === "proof-command" ) ? new ProofCommand ( elem . name , elem . branch , parent , this . connection ) : new ProofBranch ( elem . name , elem . branch , parent , this . connection ) ; if ( parent ) { parent . appendChild ( node ) ; } if ( elem . rules && elem . rules . length ) { elem . rules . forEach ( child => { fromNodeX2 ( child , node ) ; } ) ; } return node ; } ; const items = [ ] ; if ( elem . type === "root" ) { for ( let i = 0 ; i < elem . rules . length ; i ++ ) { const item = fromNodeX2 ( elem . rules [ i ] , parent ) ; items . push ( item ) ; } } else { const item = fromNodeX2 ( elem , parent ) ; items . push ( item ) ; } return items ; } param desc param opt pasteNode ( desc , opt ? ) { if ( desc && desc . selected ) { opt = opt || { } ; if ( this . clipboard ) { this . appendNode ( { selected : desc . selected , elem : this . clipboard . clone ( ) , sequent : null } , opt ) ; return true ; } if ( this . clipboardTree && this . clipboardTree . length ) { this . appendNode ( { selected : desc . selected , elem : this . clipboardTree [ 0 ] . cloneTree ( ) , sequent : null } ) ; return true ; } console . warn ( `[proof-explorer] Warning: unable to paste (clipboard is empty)` ) ; } else { console . warn ( `[proof-explorer] Warning: unable to paste (selected node is null)` ) ; } return false ; } pasteNodeX ( desc , opt ? ) { if ( desc && desc . selected ) { let selected = this . findNode ( desc . selected . id ) || this . ghostNode ?. realNode ; if ( selected ) { this . pasteNode ( { selected } , opt ) ; if ( this . ghostNodeIsActive ( ) && this . ghostNode . realNode . id === selected . id ) { const sibling = selected . contextValue === "proof-command" ? selected . getNextSibling ( ) : selected . children ?. length ? selected . children [ 0 ] : null ; if ( sibling ) { this . ghostNode . notActive ( ) ; if ( sibling . contextValue === "proof-command" ) { this . markAsActive ( { selected : sibling } , { force : true } ) ; } else if ( sibling . children ?. length ) { this . markAsActive ( { selected : sibling . children [ 0 ] } , { force : true } ) ; } } } return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc param opt pasteTree ( desc , opt ? ) { if ( desc && desc . selected ) { opt = opt || { } ; const clips = this . clipboardTree ; if ( clips ) { for ( let i = 0 ; i < clips . length ; i ++ ) { this . appendNode ( { selected : desc . selected , elem : clips [ clips . length - i - 1 ] . cloneTree ( ) , sequent : null } , opt ) ; } return true ; } else { console . warn ( `[proof-explorer] Warning: unable to paste (clipboard is empty)` ) ; } } else { console . warn ( `[proof-explorer] Warning: unable to paste (selected node is null)` ) ; } return false ; } pasteTreeX ( desc , opt ? ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) || this . ghostNode ?. realNode ; if ( selected ) { this . pasteTree ( { selected } , opt ) ; if ( this . ghostNodeIsActive ( ) && this . ghostNode . realNode . id === selected . id ) { const sibling = selected . contextValue === "proof-command" ? selected . getNextSibling ( ) : selected . children ?. length ? selected . children [ 0 ] : null ; if ( sibling ) { this . ghostNode . notActive ( ) ; if ( sibling . contextValue === "proof-command" ) { this . markAsActive ( { selected : sibling } , { force : true } ) ; } else if ( sibling . children ?. length ) { this . markAsActive ( { selected : sibling . children [ 0 ] } , { force : true } ) ; } } } return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc cutNode ( desc ) { if ( desc && desc . selected ) { this . copyNode ( desc ) ; this . deleteNode ( desc ) ; return desc . selected . name ; } else { console . warn ( `[proof-explorer] Warning: unable to cut node (selected node is null)` ) ; } return null ; } cutNodeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { const clipboard = this . cutNode ( { selected } ) ; if ( clipboard && this . clipboard ) { const elem = this . clipboard . getNodeXStructure ( ) ; const evt = { action : "did-cut-node" , selected : { id : desc . selected . id , name : desc . selected . name } , clipboard , elem } ; if ( this . connection && ! this . autorunFlag ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc cutTree ( desc ) { if ( desc && desc . selected ) { const seq = this . copyTree ( desc ) ; this . deleteTree ( desc ) ; return seq ; } else { console . warn ( `[proof-explorer] Warning: unable to cut selected subtree` ) ; } return null ; } cutTreeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { const clipboard = this . cutTree ( { selected , keepRoot : desc . keepRoot } ) ; if ( clipboard && this . clipboardTree && this . clipboardTree . length ) { const elems = [ ] ; for ( let i = 0 ; i < this . clipboardTree . length ; i ++ ) { elems . push ( this . clipboardTree [ i ] . getNodeXStructure ( ) ) ; } const evt = { action : "did-cut-tree" , selected : { id : desc . selected . id , name : desc . selected . name } , clipboard , elems } ; if ( this . connection && ! this . autorunFlag ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc jumpHere ( desc ) { if ( desc && desc . selected ) { this . cutTree ( { selected : desc . selected } ) ; this . trimNode ( { selected : desc . active } , { willCutSelected : true } ) ; this . pasteTree ( { selected : desc . active } , { rebase : true } ) ; this . deleteNode ( { selected : desc . active } ) ; return true ; } else { console . warn ( `[proof-explorer] Warning: unable to cut selected subtree` ) ; } return false ; } jumpHereX ( desc ) { if ( desc ?. selected ) { const active = this . activeNode ; const selected = this . findNode ( desc . selected . id ) ; if ( selected && active && active . id !== selected . id && ! selected . isVisited ( ) ) { const success = this . jumpHere ( { active , selected } ) ; return success ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; return false ; } param desc param opt deleteTree ( desc ) { if ( desc && desc . selected && desc . selected . parent ) { const parent = ( desc . keepRoot ) ? desc . selected : desc . selected . parent ; const len = ( desc . keepRoot ) ? parent . children . length : parent . children . length - parent . children . indexOf ( desc . selected ) ; for ( let i = 0 ; i < len ; i ++ ) { this . deleteNode ( { selected : parent . children [ parent . children . length - 1 ] } ) ; } } else { console . warn ( `[proof-explorer] Warning: unable to delete selected node` ) ; } } deleteTreeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { return this . deleteTree ( { selected } ) ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc param opt deleteNode ( desc ) { if ( desc && desc . selected && desc . selected . parent ) { const selected = desc . selected ; this . updateDirtyFlag ( true ) ; if ( selected . contextValue === "root" ) { if ( this . connection && ! this . autorunFlag ) { for ( let i = 0 ; i < selected . children . length ; i ++ ) { this . log ( `[proof-explorer] Deleting node ${ selected . children [ i ] . name } (${ selected . children [ i ] . id })` ) ; const evt = { action : "did-delete-node" , selected : { id : selected . children [ i ] . id , name : selected . children [ i ] . name } } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } selected . children = [ ] ; this . markAsActive ( { selected : this . root } ) ; } else { const activeGhost = ( this . ghostNode && this . ghostNode . isActive ( ) ) ? this . ghostNode . realNode : null ; const wasActive = selected . isActiveTree ( { activeNode : activeGhost } ) ; if ( wasActive || this . ghostNode . realNode === selected ) { const sibling = selected . getNextSibling ( ) || this . ghostNode ; if ( sibling === this . ghostNode ) { this . ghostNode . parent = selected . parent ; this . ghostNode . realNode = selected . getSiblingOrParent ( ) || this . root ; this . ghostNode . updateSequent ( this . ghostNode . realNode . sequentDescriptor , { internalAction : this . autorunFlag } ) ; } this . markAsActive ( { selected : sibling } ) ; } selected . parent . deleteChild ( selected ) ; } } else { console . warn ( `[proof-explorer] Warning: unable to delete selected node` ) ; } } deleteNodeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { return this . deleteNode ( { selected } ) ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc param opt trimNode ( desc , opt ? ) { if ( desc && desc . selected && desc . selected . parent ) { opt = opt || { } ; const selected = desc . selected ; let items = null ; switch ( selected . contextValue ) { case "root" : { items = selected . children ; selected . children = [ ] ; this . markAsActive ( { selected : this . root } ) ; break ; } case "proof-branch" : { let childWasPendingOrActive = false ; for ( let i = 0 ; i < selected . children . length && ! childWasPendingOrActive ; i ++ ) { if ( selected . children [ i ] . isPending ( ) || selected . children [ i ] . isActive ( ) || ( selected . children [ i ] === this . ghostNode . realNode && this . ghostNode . isActive ( ) ) ) { childWasPendingOrActive = true ; } } if ( childWasPendingOrActive ) { this . markAsActive ( { selected : selected } ) ; } items = selected . children ; selected . children = [ ] ; break ; } case "proof-command" : { items = selected . children ; selected . children = [ ] ; const parent = selected . parent ; const idx = parent . children . indexOf ( selected ) ; items = items . concat ( parent . children . slice ( idx + 1 , parent . children . length + 1 ) ) ; parent . children = parent . children . slice ( 0 , idx + 1 ) ; if ( selected . isVisited ( ) ) { this . activeNode = this . ghostNode ; this . ghostNode . parent = selected . parent ; this . ghostNode . realNode = selected ; this . activeNode . active ( ) ; } break ; } default : { console . warn ( `[proof-explorer] Warning: unrecognized node type ${ selected . contextValue } detected while trimming ${ selected . name }` ) ; } } if ( items && items . length ) { this . updateDirtyFlag ( true ) ; if ( this . connection && ! this . autorunFlag ) { this . log ( `[proof-explorer] Trimming node ${ desc . selected . name } (${ desc . selected . id })` ) ; for ( let i = 0 ; i < items . length ; i ++ ) { const evt = { action : "did-delete-node" , selected : { id : items [ i ] . id , name : items [ i ] . name } } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } let elems = opt . willCutSelected ? [ selected ?. getNodeXStructure ( ) ] : [ ] ; elems = elems . concat ( items . map ( item => { return item . getNodeXStructure ( ) ; } ) ) ; const evt = { action : "did-trim-node" , elems } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } return items ; } else { console . warn ( `[proof-explorer] Warning: unable to trim selected node` ) ; } return null ; } trimNodeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { this . trimNode ( { selected } ) ; return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc removeNotVisited ( desc ) { if ( desc && desc . selected && desc . selected . parent ) { const node = desc . selected ; if ( node . children ) { const n = node . children . length ; for ( let i = 0 ; i < n ; i ++ ) { this . removeNotVisited ( { selected : node . children [ n - 1 - i ] } ) ; } } if ( ! node . isVisited ( ) && ! node . isActive ( ) && ( ! node . children || node . children . length === 0 ) ) { this . deleteNode ( { selected : node } ) ; } } else { console . warn ( `[proof-explorer] Warning: unable to delete selected node` ) ; } return null ; } removeNotVisitedX ( desc ) { if ( desc ) { const id = desc ?. selected ?. id || this . root . id ; const selected = this . findNode ( id ) ; if ( selected ) { this . removeNotVisited ( { selected } ) ; return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } param desc renameNode ( desc ) { if ( desc && desc . selected ) { this . updateDirtyFlag ( true ) ; const node = desc . selected ; let newName = desc . newName ; if ( newName ) { newName = newName . trim ( ) ; if ( ! ( newName . startsWith ( "(" ) && newName . endsWith ( ")" ) ) ) { newName = `(${ newName })` ; } node . rename ( newName ) ; } return true ; } else { console . warn ( `[proof-explorer] Warning: unable to edit selected node` ) ; } return false ; } renameNodeX ( desc ) { if ( desc && desc . selected ) { const selected = this . findNode ( desc . selected . id ) ; if ( selected ) { this . renameNode ( { selected , newName : desc . newName } ) ; return ; } } console . warn ( `[proof-explorer] Warning: unable to complete proof edit/paste (selected node is null)` ) ; } /**
     * Reset all flags
     */ resetFlags ( ) { this . autorunFlag = false ; this . runningFlag = false ; this . rewindingFlag = false ; } proofIsDirty ( ) { return this . dirtyFlag ; } /**
     * Utility function, marks the current proof as proved
     */ async proved ( ) { this . runningFlag = false ; this . activeNode . moveIndicatorForward ( ) ; this . removeNotVisited ( { selected : this . root } ) ; this . root . QED ( ) ; const fname = fsUtils . desc2fname ( { fileName : this . formula . theoryName , fileExtension : ".prf" , contextFolder : this . formula . contextFolder } ) ; if ( this . root . proofStatusChanged ( ) || this . dirtyFlag || this . origin !== ".prf" ) { await this . quitProofAndSave ( ) ; } else { this . proofDescriptor = this . makeProofDescriptor ( this . origin ) ; await this . quitProofAndSave ( { jprfOnly : true } ) ; } this . pvsLanguageServer . notifyServerMode ( "lisp" ) ; await this . proofCommandRequest ( { fileName : this . formula . fileName , fileExtension : this . formula . fileExtension , theoryName : this . formula . theoryName , formulaName : this . formula . formulaName , contextFolder : this . formula . contextFolder , line : this . formula . line , cmd : "Q.E.D." } ) ; if ( this . autorunFlag ) { this . autorunFlag = false ; this . autorunCallback ( this . root . getProofStatus ( ) ) ; this . root = null ; } } param proofState loadInitialSequent ( proofState ) { this . initialProofState = proofState ; const evt = { action : "did-load-sequent" , sequent : proofState } ; if ( this . connection && ! this . autorunFlag ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } /**
     * Utility function, activates the proof loaded in the view (i.e., sets the first node as active)
     */ startProof ( opt ? ) { opt = opt || { } ; if ( this . root ) { this . autorunFlag = ! ! opt . autorun ; this . autorunCallback = ( status ) => { if ( this . connection ) { this . connection . sendRequest ( serverEvent . autorunFormulaResponse , { status } ) ; this . connection . sendRequest ( serverEvent . serverModeUpdateEvent , { mode : "lisp" } ) ; } } ; this . qedNotificationSent = false ; this . root . updateSequent ( this . initialProofState , { internalAction : this . autorunFlag } ) ; this . root . pending ( ) ; this . root . setProofStatus ( this . proofDescriptor . info . status ) ; const selected = ( this . root . children && this . root . children . length ) ? this . root . children [ 0 ] : this . root ; this . initActiveNode ( { selected } ) ; this . markAsActive ( { selected : this . activeNode } , { force : true } ) ; if ( this . activeNode . id !== this . root . id ) { this . activeNode . updateSequent ( this . initialProofState , { internalAction : this . autorunFlag } ) ; } this . updateDirtyFlag ( false ) ; this . runningFlag = false ; if ( this . root . children && this . root . children . length ) { this . markAsActive ( { selected : this . root . children [ 0 ] } ) ; this . activeNode . sequentDescriptor = this . root . sequentDescriptor ; } if ( this . autorunFlag ) { this . run ( ) ; } else { const evt = { action : "did-start-proof" } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } } else { console . warn ( `[proof-explorer] Warning: unable to activate selected proof (root node is null)` ) ; } } param desc protected initActiveNode ( desc ) { if ( desc && desc . selected ) { switch ( desc . selected . contextValue ) { case "root" : { this . activeNode = this . ghostNode ; this . ghostNode . parent = this . root ; this . ghostNode . realNode = this . root ; break ; } default : { this . activeNode = desc . selected ; this . ghostNode . parent = this . activeNode . parent ; this . ghostNode . realNode = this . activeNode ; } } } else { console . warn ( `[proof-explorer] Warning: could not initialize active node (selected node is null)` ) ; } } param formula protected async openProofFile ( desc , formula , opt ? ) { opt = opt || { } ; this . formula = formula ; if ( this . pvsProxy ) { const pdesc = ( opt . newProof ) ? await this . pvsProxy . newProof ( formula ) : await this . pvsProxy . openProofFile ( desc , formula ) ; if ( pdesc ) { this . shasum = await fsUtils . shasumFile ( { ... formula , fileExtension : ".pvs" } ) ; this . origin = pdesc ?. origin || ".prf" ; this . loadProofDescriptor ( pdesc ) ; return pdesc ; } } console . error ( `[proof-explorer] Error: Could not load proof script (pvs-proxy is null)` ) ; return null ; } param req async loadProofRequest ( req , opt ? ) { opt = opt || { } ; this . formula = req ; if ( this . pvsProxy ) { const fdesc = req . proofFile || { fileName : req . fileName , fileExtension : ( opt . useJprf ) ? ".jprf" : ".prf" , contextFolder : req . contextFolder } ; const pdesc = ( opt . newProof ) ? await this . pvsProxy . newProof ( req ) : await this . openProofFile ( fdesc , req , opt ) ; const structure = this . root . getNodeXStructure ( ) ; const evt = { action : "did-load-proof" , formula : this . formula , desc : this . proofDescriptor , proof : structure } ; if ( this . connection && ! this . autorunFlag ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } return pdesc ; } console . error ( `[proof-explorer] Error: Could not load proof script (pvs-proxy is null)` ) ; return null ; } getFormula ( ) { return this . formula ; } param desc protected loadProofDescriptor ( desc ) { const createTree = ( elem , parent ) : void => { const node = ( elem . type === "proof-command" ) ? new ProofCommand ( elem . name , elem . branch , parent , this . connection ) : new ProofBranch ( elem . name , elem . branch , parent , this . connection ) ; parent . appendChild ( node , { internalAction : true } ) ; if ( elem . rules && elem . rules . length ) { elem . rules . forEach ( child => { createTree ( child , node ) ; } ) ; } } ; this . root = null ; if ( desc && desc . info ) { this . root = new RootNode ( { name : desc . info . formula , proofStatus : desc . info . status , connection : this . connection } ) ; this . ghostNode = new GhostNode ( { parent : this . root , node : this . root , connection : this . connection } ) ; if ( desc . proofTree && desc . proofTree . rules && desc . proofTree . rules . length && ! ( desc . proofTree . rules . length === 1 && isPostponeCommand ( desc . proofTree . rules [ 0 ] . name ) ) ) { desc . proofTree . rules . forEach ( ( child ) => { createTree ( child , this . root ) ; } ) ; } this . proofDescriptor = desc ; } else { console . warn ( `[proof-explorer] Warning: null descriptor` ) ; } } /**
     * Internal function, builds a proof descriptor based on the structure of the current proof tree known to proof-explorer (i.e, this.root)
     */ protected makeProofDescriptor ( origin ) { const makeProofStructure = ( node ) : ProofNode => { const res = { branch : node . branchId , name : node . name , type : < ProofNodeType > node . contextValue , rules : [ ] } ; for ( let i = 0 ; i < node . children . length ; i ++ ) { const child = makeProofStructure ( node . children [ i ] ) ; res . rules . push ( child ) ; } return res ; } ; const proofTree = makeProofStructure ( this . root ) ; const proofDescriptor = new ProofDescriptor ( { theory : this . formula . theoryName , formula : this . formula . formulaName , status : this . root . getProofStatus ( ) , prover : ( this . pvsProxy ) ? languageUtils . pvsVersionToString ( this . pvsProxy . getPvsVersionInfo ( ) ) : "PVS 7.x" , shasum : this . shasum } , origin , proofTree ) ; return proofDescriptor ; } protected isOnFirstCommand ( ) { return ! this . activeNode || this . activeNode . contextValue === "root" || ( this . root ?. children && this . root . children . length && this . activeNode === this . root . children [ 0 ] ) ; } /**
     * Loads a proof file in proof explorer
     */ async openProofRequest ( desc , formula ) { if ( desc && desc . fileName && desc . fileExtension && desc . contextFolder && formula && formula . fileName && formula . fileExtension && formula . theoryName && formula . formulaName ) { const pdesc = await this . pvsProxy . openProofFile ( desc , formula ) ; if ( pdesc ) { await saveProofDescriptor ( this . formula , pdesc , { saveProofTree : false } ) ; this . loadProofDescriptor ( pdesc ) ; const structure = this . root . getNodeXStructure ( ) ; const evt = { action : "did-open-proof" , proofFile : desc , formula : this . formula , desc : this . proofDescriptor , proof : structure } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } this . startProof ( ) ; return true ; } else { const evt = { action : "did-open-proof" , proofFile : desc , formula : this . formula , desc : this . proofDescriptor } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } } return false ; } /**
     * Imports the proof of a given formula in proof explorer
     */ async importProofRequest ( desc , formula ) { if ( desc && desc . fileName && desc . fileExtension && desc . contextFolder && formula && formula . fileName && formula . fileExtension && formula . theoryName && formula . formulaName ) { const pdesc = await this . pvsProxy . openProofFile ( desc , formula ) ; if ( pdesc ) { pdesc . info = this . proofDescriptor . info ; pdesc . info . status = "untried" ; if ( pdesc . proofTree ) { pdesc . proofTree . name = pdesc . info . formula ; } await saveProofDescriptor ( this . formula , pdesc , { saveProofTree : false } ) ; this . loadProofDescriptor ( pdesc ) ; this . updateDirtyFlag ( true ) ; const structure = this . root . getNodeXStructure ( ) ; const evt = { action : "did-import-proof" , proofFile : desc , formula : this . formula , importedFormula : formula , desc : this . proofDescriptor , proof : structure } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; this . startProof ( ) ; return true ; } else { const evt = { action : "did-import-proof" , proofFile : desc , formula : this . formula , importedFormula : formula , desc : this . proofDescriptor } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } } return false ; } /**
     * Export the current proof to a given file format
     * Only one supported format at the moment: .prl (prooflite)
     */ async exportProof ( desc ) { if ( desc ) { this . proofDescriptor = this . makeProofDescriptor ( this . origin ) ; const proofFile = { fileName : this . formula . fileName , fileExtension : desc . fileExtension , contextFolder : this . formula . contextFolder } ; let success = false ; let msg = null ; switch ( desc . fileExtension ) { case ".prl" : { const file = await this . pvsProxy . saveProoflite ( { fileName : proofFile . fileName , fileExtension : ".prl" , contextFolder : proofFile . contextFolder , theoryName : this . formula . theoryName , formulaName : this . formula . formulaName , proofDescriptor : this . proofDescriptor } ) ; success = ! ! file ; break ; } default : { const msg = `Warning: unsupported export format ${ desc . fileExtension }` ; console . warn ( `[proof-explorer] ${ msg }` ) ; this . connection . sendNotification ( "server.status.error" , msg ) ; return ; } } const script = this . copyTree ( { selected : this . root } ) ; this . connection . sendRequest ( serverRequest . saveProof , { response : { success , proofFile , msg , formula : this . formula , script } , args : this . formula } ) ; } else { this . connection . sendNotification ( "server.status.error" , `Error: could not save proof (null descriptor)` ) ; } } /**
     * Quit proof and save the current proof in a .prf format
     */ async quitProofAndSave ( opt ? ) { opt = opt || { } ; const proofFile = { fileName : this . formula . fileName , fileExtension : ".prf" , contextFolder : this . formula . contextFolder , } ; this . proofDescriptor = this . makeProofDescriptor ( this . origin ) ; await saveProofDescriptor ( this . formula , this . proofDescriptor , { saveProofTree : true } ) ; const script = this . copyTree ( { selected : this . root } , { updateClipboard : false } ) ; let success = true ; let msg = null ; await this . quitProof ( opt ) ; this . updateDirtyFlag ( false ) ; if ( ! opt . jprfOnly ) { const response = await this . pvsProxy ?. storeLastAttemptedProof ( this . formula ) ; success = ! ! ( response ?. result ) ; if ( ! success ) { msg = response ?. error ?. data ?. error_string ; console . error ( msg ) ; } } const response = { success , msg , proofFile , formula : this . formula , script } ; this . connection ?. sendNotification ( serverRequest . saveProof , { response , args : this . formula } ) ; return { success , msg } ; } async inChecker ( ) { if ( this . pvsProxy ) { const response = await this . pvsProxy . getProverStatus ( ) ; return response && response . result && response . result === "active" ; } return false ; } param opt async quitProof ( opt ? ) { opt = opt || { } ; this . runningFlag = false ; let res = await this . pvsProxy . interruptProver ( ) ; const inchecker = await this . inChecker ( ) ; if ( this . formula && inchecker ) { res = await this . proofCommand ( { fileName : this . formula . fileName , fileExtension : this . formula . fileExtension , theoryName : this . formula . theoryName , formulaName : this . formula . formulaName , contextFolder : this . formula . contextFolder , cmd : "quit" } ) ; this . updateDirtyFlag ( false ) ; } if ( this . autorunFlag ) { const status = ( this . root ) ? this . root . getProofStatus ( ) : "untried" ; this . autorunFlag = false ; this . autorunCallback ( status ) ; } if ( opt . notifyClient ) { const req = { ... this . formula , cmd : "quit" } ; const ans = { res : "bye!" , req } ; this . connection ?. sendRequest ( serverEvent . proofCommandResponse , ans ) ; if ( ! this . autorunFlag ) { const evt = { action : "did-quit-proof" } ; this . connection ?. sendNotification ( serverEvent . proverEvent , evt ) ; } } this . connection ?. sendRequest ( serverEvent . serverModeUpdateEvent , { mode : "lisp" } ) ; } /**
     * Utility function, interrupts a proof command
     */ async interruptProofCommand ( ) { if ( this . pvsProxy && ! this . interruptFlag ) { this . interruptFlag = true ; this . runningFlag = false ; return await this . pvsProxy . interruptProver ( ) ; } return null ; } /**
     * Utility function, interrupts the prover and quits
     */ async interruptAndQuitProof ( opt ? ) { await this . interruptProofCommand ( ) ; await this . quitProof ( opt ) ; } /**
     * Utility function, shows help for a given command
     */ async helpCommand ( cmd ) { await this . pvsProxy . showHelpBang ( cmd ) ; } param args async proofCommand ( args ) { if ( args ?. cmd ) { args = fsUtils . decodeURIComponents ( args ) ; const useLispInterface = true ; const start = new Date ( ) . getTime ( ) ; const response = await this . pvsProxy . proofCommand ( { cmd : args . cmd } , { useLispInterface } ) ; const ms = new Date ( ) . getTime ( ) - start ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . profilerData , `${ ms }ms ${ args . cmd }` ) ; } return response ; } else { console . error ( '[pvs-language-server] Error: proofCommand invoked with null descriptor' ) ; } return null ; } async proofCommandRequest ( request ) { request = fsUtils . decodeURIComponents ( request ) ; if ( isSaveThenQuitCommand ( request . cmd ) ) { await this . quitProofAndSave ( ) ; const ans = { res : "bye!" , req : request } ; this . connection ?. sendRequest ( serverEvent . proofCommandResponse , ans ) ; return ; } if ( isQuitCommand ( request . cmd ) ) { await this . quitProof ( { notifyClient : true } ) ; return ; } if ( isQuitDontSaveCommand ( request . cmd ) ) { await this . quitProof ( { notifyClient : true } ) ; return ; } if ( isFailCommand ( request . cmd ) ) { if ( this . activeNode . branchId === "" ) { await this . quitProof ( { notifyClient : true } ) ; return ; } else { request . cmd = "(postpone)" ; } } if ( languageUtils . isInterruptCommand ( request . cmd ) ) { await this . interruptProofCommand ( ) ; this . runningFlag = false ; request . cmd = "(skip)" ; } if ( isQEDCommand ( request . cmd ) ) { const ans = { res : "Q.E.D." , req : request } ; this . connection ?. sendRequest ( serverEvent . proofCommandResponse , ans ) ; if ( this . connection ) { if ( ! this . qedNotificationSent ) { this . connection . sendRequest ( serverEvent . QED , { response : { result : "Q.E.D." } , request } ) ; this . qedNotificationSent = true ; } this . connection . sendRequest ( serverEvent . serverModeUpdateEvent , { mode : "lisp" } ) ; } const cdesc = await this . pvsLanguageServer . getContextDescriptor ( { contextFolder : request . contextFolder } ) ; if ( this . connection ) { this . connection . sendRequest ( serverEvent . contextUpdate , cdesc ) ; } await this . pvsLanguageServer . generateTccsRequest ( request , { quiet : true } ) ; return ; } const cmdArray = splitCommands ( request . cmd ) ; if ( cmdArray ) { for ( let i = 0 ; i < cmdArray . length ; i ++ ) { const cmd = cmdArray [ i ] ; const req = { fileName : request . fileName , fileExtension : request . fileExtension , contextFolder : request . contextFolder , theoryName : request . theoryName , formulaName : request . formulaName , line : request . line , cmd } ; const response = await this . step ( { cmd } ) ; if ( response ) { if ( response . result ) { const result = response . result ; if ( result . length ) { const sequent = result [ result . length - 1 ] ; if ( sequent [ "prover-session-status" ] ) { console . dir ( sequent ) ; } else { const command = ( sequent && sequent [ "last-cmd" ] && ! isUndoCommand ( cmd ) && ! isUndoUndoCommand ( cmd ) && ! isPostponeCommand ( cmd ) ) ? sequent [ "last-cmd" ] : cmd ; if ( languageUtils . QED ( sequent ) ) { if ( this . connection ) { if ( ! this . qedNotificationSent ) { this . connection . sendRequest ( serverEvent . QED , { response : { result : sequent } , request : req } ) ; this . qedNotificationSent = true ; } const cdesc = await this . pvsLanguageServer . getContextDescriptor ( { contextFolder : req . contextFolder } ) ; this . connection . sendRequest ( serverEvent . contextUpdate , cdesc ) ; } await this . pvsLanguageServer . generateTccsRequest ( req , { quiet : true } ) ; return ; } if ( i === cmdArray . length - 1 ) { this . connection ?. sendRequest ( serverEvent . proofCommandResponse , { res : sequent , req : request } ) ; } } } } else { console . warn ( `[proof-explorer] Warning: Unable to execute proof command ${ cmd }` , response ) ; } } } } } } export const QED = "proved" ; /**
 * Definition of tree items
 */ export abstract class ProofItem extends TreeItem { contextValue : ProofNodeType | "ghost" = null ; name : string ; branchId : string = "" ; protected previousState : { tooltip ? : string ; } = { } ; children : ProofItem [ ] = [ ] ; parent : ProofItem ; protected activeFlag : boolean = false ; protected completeFlag : boolean = false ; protected visitedFlag : boolean = false ; protected pendingFlag : boolean = false ; protected noChangeFlag : boolean = false ; protected prevFlags : { activeFlag : boolean ; visitedFlag : boolean ; pendingFlag : boolean ; } = { activeFlag : false , visitedFlag : false , pendingFlag : false } ; sequentDescriptor : SequentDescriptor = null ; /**
     * Constructor
     */ constructor ( type , name , branchId , parent , connection ) { super ( type , connection ) ; this . contextValue = type ; this . id = fsUtils . get_fresh_id ( ) ; this . name = name ; this . branchId = branchId ; this . parent = parent ; this . notVisited ( { internalAction : true } ) ; } readonly DEBUG_LOG : boolean = false ; protected log ( ... args ) { if ( this . DEBUG_LOG ) { console . log ( args ) ; } } getNodeXStructure ( ) { const res = { id : this . id , branch : this . branchId , name : this . name , type : < ProofNodeType > this . contextValue , rules : [ ] , parent : this . parent . id } ; if ( this . children ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { const child = this . children [ i ] . getNodeXStructure ( ) ; res . rules . push ( { id : child . id , branch : child . branch , name : child . name , type : child . type , rules : child . rules , parent : this . id } ) ; } } return res ; } /**
     * Checks if two branch names are equivalent
     */ branchNameEquals ( b ) { const b1x = this . branchId . split ( "." ) . map ( ( elem ) => { return `${ parseInt ( elem ) }` ; } ) . join ( "." ) ; const b2x = b . split ( "." ) . map ( ( elem ) => { return `${ parseInt ( elem ) }` ; } ) . join ( "." ) ; return b1x === b2x ; } /**
     * Returns a clone of the current node
     */ abstract clone ( opt ? ) ; /**
     * Returns a clone of the current node.
     */ cloneTree ( opt ? ) { opt = opt || { } ; opt . parent = opt . parent || this . parent || null ; const clonedRoot = this . clone ( opt ) ; if ( this . children ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { const child = this . children [ i ] . cloneTree ( { parent : clonedRoot } ) ; clonedRoot . insertChild ( child , i , { internalAction : true } ) ; } } return clonedRoot ; } saveAttributes ( ) { this . prevFlags = { activeFlag : this . activeFlag , visitedFlag : this . visitedFlag , pendingFlag : this . pendingFlag } ; } restoreAttributes ( ) { if ( this . prevFlags . activeFlag ) { return this . active ( ) ; } if ( this . prevFlags . visitedFlag ) { return this . visited ( ) ; } if ( this . prevFlags . pendingFlag ) { return this . pending ( ) ; } } /**
     * Updates the sequent descriptor
     */ updateSequent ( sequent , opt ? ) { opt = opt || { } ; if ( sequent ) { this . sequentDescriptor = sequent ; if ( ! opt . internalAction && this . connection ) { const evt = { action : "did-update-sequent" , sequent : this . sequentDescriptor , selected : { id : this . id , name : this . name } } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } /**
     * Rename a node
     * If the node is a proof-branch, the branchId is also updated for the node and the entire subtree rooted at the node
     */ rename ( newName , opt ? ) { opt = opt || { } ; const oldName = this . name ; this . name = newName ; if ( this . contextValue === "proof-branch" ) { this . branchId = this . name . replace ( /[\(\)]/g , "" ) ; for ( let i = 0 ; i < this . children ?. length ; i ++ ) { this . children [ i ] . rebaseTree ( this . branchId , { internalAction : false } ) ; } } if ( ! opt . internalAction && this . connection ) { const evt = { action : "did-rename-node" , selected : { id : this . id , name : oldName } , newName : newName } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } /**
     * Mark node as pending
     */ pending ( ) { const changed = ! this . pendingFlag ; this . activeFlag = false ; this . visitedFlag = false ; this . pendingFlag = true ; this . completeFlag = false ; this . noChangeFlag = false ; if ( changed ) { this . connection ?. sendNotification ( serverEvent . proofNodeUpdate , { id : this . id , name : this . name , status : "pending" } ) ; } } /**
     * Mark node as visited
     */ visited ( ) { const changed = ! this . visitedFlag ; this . activeFlag = false ; this . visitedFlag = true ; this . pendingFlag = false ; this . noChangeFlag = false ; if ( changed ) { this . connection ?. sendNotification ( serverEvent . proofNodeUpdate , { id : this . id , name : this . name , status : "visited" } ) ; } } /**
     * Mark node as not visited
     */ notVisited ( opt ? ) { this . activeFlag = false ; this . visitedFlag = false ; this . pendingFlag = false ; this . noChangeFlag = false ; this . completeFlag = false ; opt = opt || { } ; if ( ! opt . internalAction ) { this . connection ?. sendNotification ( serverEvent . proofNodeUpdate , { id : this . id , name : this . name , status : "not-visited" } ) ; } } /**
     * Mark node as complete
     */ complete ( ) { const changed = ! this . completeFlag ; this . completeFlag = true ; if ( changed ) { this . connection ?. sendNotification ( serverEvent . proofNodeUpdate , { id : this . id , name : this . name , status : "complete" } ) ; } } /**
     * Mark node as not complete
     */ notComplete ( ) { const changed = this . completeFlag ; this . completeFlag = false ; if ( changed ) { this . connection ?. sendNotification ( serverEvent . proofNodeUpdate , { id : this . id , name : this . name , status : "not-complete" } ) ; } } /**
     * Mark node as active
     */ active ( ) { this . activeFlag = true ; this . visitedFlag = false ; this . pendingFlag = false ; this . noChangeFlag = false ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proofNodeUpdate , { id : this . id , name : this . name , status : "active" } ) ; } } /**
     * Mark subtree as not visited
     */ treeNotVisited ( ) { this . notVisited ( ) ; if ( this . children ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { this . children [ i ] . treeNotVisited ( ) ; } } } /**
     * Mark subtree as visited
     */ treeVisited ( ) { this . visited ( ) ; if ( this . children ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { this . children [ i ] . treeVisited ( ) ; } } } /**
     * Mark subtree as complete
     */ treeComplete ( ) { this . complete ( ) ; if ( this . children ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { this . children [ i ] . treeComplete ( ) ; } } } /**
     * Checks if all nodes in the subtree rooted at the current node are complete
     */ checkChildrenComplete ( ) { if ( this . children && this . children . length ) { const open = this . children . filter ( item => { return ! item . isVisited ( ) ; } ) ; return open . length === 0 ; } return true ; } /**
     * Propagates visited & complete attributes to all nodes in the subtree
     */ bubbleVisitedAndComplete ( ) { if ( this . contextValue !== "root" && this . parent && this . parent . checkChildrenComplete ( ) ) { this . parent . visited ( ) ; this . parent . treeComplete ( ) ; this . parent . bubbleVisitedAndComplete ( ) ; } } /**
     * Returns true if the current node is active
     */ isActive ( ) { return this . activeFlag ; } /**
     * Returns true if the current node or any node in the subtree rooted at the current node is active
     */ isActiveTree ( opt ? ) { opt = opt || { } ; let active = this . isActive ( ) ; if ( ! active && opt && opt . activeNode ) { active = this . id === opt . activeNode . id ; } if ( ! active && this . children && this . children . length ) { for ( let i = 0 ; i < this . children . length && ! active ; i ++ ) { active = this . children [ i ] . isActiveTree ( opt ) ; } } return active ; } /**
     * Returns true if this node has been visited or is pending execution
     */ isVisitedOrPending ( ) { return this . visitedFlag || this . pendingFlag ; } /**
     * Returns true if this node is pending execution
     */ isPending ( ) { return this . pendingFlag ; } /**
     * Returns true if this node has been visited
     */ isVisited ( ) { return this . visitedFlag ; } /**
     * Returns true if this node is complete
     */ isComplete ( ) { return this . completeFlag ; } restore ( ) { } restoreTreeAttributes ( ) { this . restoreAttributes ( ) ; if ( this . children && this . children . length ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { this . children [ i ] . restoreTreeAttributes ( ) ; } } } saveTreeAttributes ( ) { this . saveAttributes ( ) ; if ( this . children && this . children . length ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { this . children [ i ] . saveTreeAttributes ( ) ; } } } setChildren ( children ) { this . children = children ; } /**
     * Inserts a given child node at the given position in the list of children of the current node
     */ insertChild ( child , position , opt ? ) { if ( this . children && position < this . children . length - 1 ) { const children1 = this . children . slice ( 0 , position ) ; const children2 = this . children . slice ( position ) ; this . children = children1 . concat ( [ child ] ) . concat ( children2 ) ; } else { position = this . children . length ; this . children = this . children . concat ( [ child ] ) ; } if ( ! opt . internalAction && this . connection ) { const elem = child . getNodeXStructure ( ) ; this . log ( `[proof-explorer] Appending node ${ elem . name } (${ elem . id })` ) ; const evt = { action : "did-append-node" , elem , position } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } /**
     * Deletes a given child node
     */ deleteChild ( child , opt ? ) { opt = opt || { } ; this . children = this . children . filter ( ( ch ) => { return ch . id !== child . id ; } ) ; if ( ! opt . internalAction && this . connection ) { this . log ( `[proof-explorer] Deleting node ${ child . name } (${ child . id })` ) ; const evt = { action : "did-delete-node" , selected : { id : child . id , name : child . name } } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } /**
     * Deletes the tree rooted at the given child
     */ deleteTree ( child ) { const children = [ ] ; for ( let i in this . children ) { if ( this . children [ i ] . id !== child . id ) { children . push ( this . children [ i ] ) ; } else { this . children = children ; return ; } } } /**
     * Returns a sequence representing the execution order of the commands rooted at the current node
     */ getProofCommands ( ) { let ans = [ this ] ; if ( this . children ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { ans = ans . concat ( this . children [ i ] . getProofCommands ( ) ) ; } } return ans ; } /**
     * Utility function, moves the active node indicator forward
     */ moveIndicatorForward ( opt ? ) { opt = opt || { } ; if ( this . contextValue === "proof-command" ) { this . visited ( ) ; } else { this . completeFlag ? this . complete ( ) : this . pending ( ) ; } const proofState = opt . proofState || this . sequentDescriptor ; if ( this . children && this . children . length ) { if ( ! proofState ) { console . error ( `[pvs-explorer] Error: proofstate is null. Please restart the proof and report this error to the vscode-pvs developers.` , { opt , contextValue : this . contextValue , name : this . name } ) ; } const activeBranchId = languageUtils . getBranchId ( proofState ?. label ) ; if ( opt . keepSameBranch && ! this . children [ 0 ] . branchNameEquals ( activeBranchId ) ) { return null ; } if ( opt . lastVisitedChild ) { const idx = this . children . indexOf ( opt . lastVisitedChild ) ; const next = idx + 1 ; if ( next < this . children . length ) { return this . children [ next ] ; } } else { return this . children [ 0 ] ; } } if ( opt . keepSameBranch && ( ( this . contextValue === "proof-branch" && this . children . length === 0 ) || ( this . contextValue === "proof-command" && this . children . length > 0 ) ) ) { return null ; } this . visited ( ) ; if ( this . parent && this . contextValue === "proof-command" ) { return this . parent . moveIndicatorForward ( { lastVisitedChild : this , proofState : this . sequentDescriptor } ) ; } return null ; } /**
     * Returns the node type, one of "proof-command", "proof-branch", "root"
     */ getType ( ) { return this . contextValue === "ghost" ? null : this . contextValue ; } /**
     * Returns the last visited child
     */ getLastVisitedChild ( ) { if ( this . children ) { const children = this . children . filter ( item => { return item . isVisitedOrPending ( ) ; } ) ; if ( children && children . length ) { this . pending ( ) ; return children [ children . length - 1 ] . getLastVisitedChild ( ) ; } } return this ; } moveIndicatorBack ( opt ? ) { if ( this . parent ) { if ( this . parent . children && this . parent . children . length ) { const children = this . parent . children . filter ( ( item ) => { return item . isVisitedOrPending ( ) ; } ) ; if ( children && children . length ) { this . notVisited ( ) ; const candidate = children [ children . length - 1 ] ; if ( candidate . children && candidate . children . length ) { return candidate . getLastVisitedChild ( ) ; } return candidate ; } if ( this . parent . contextValue === "root" && this . parent . children && this . parent . children . length ) { return this ; } if ( opt . keepSameBranch && this . contextValue === "proof-command" ) { return this ; } this . notVisited ( ) ; return this . parent ; } } else { console . error ( `Error: Could not find parent for node ${ this . name }` ) ; } return this ; } getSiblingOrParent ( ) { if ( this . parent ) { const children = this . parent . children ; if ( children && children . length > 1 ) { const index = children . indexOf ( this ) ; const next = index + 1 ; const prev = index - 1 ; return ( next < children . length ) ? children [ next ] : children [ prev ] ; } return this . parent ; } else { console . error ( `[proof-explorer] Error: could not find sibling or parent for node ${ this . name }` ) ; } return null ; } getNextSibling ( ) { if ( this . parent ) { const children = this . parent . children ; if ( children && children . length > 1 ) { const index = children . indexOf ( this ) ; const next = index + 1 ; if ( next < children . length ) { return children [ next ] ; } } } return null ; } getPreviousSibling ( ) { if ( this . parent ) { const children = this . parent . children ; if ( children && children . length > 1 ) { const index = children . indexOf ( this ) ; const prev = index - 1 ; if ( prev >= 0 ) { return children [ prev ] ; } } } return null ; } /**
     * Utility function, used to rebase a tree rooted at a given node with the given targetId
     * - proof-branch: old branchId = baseId.branchName, new branchId = targetId.branchName
     * - proof-node: old branchId = baseId, new branchId = targetId
     * - root: old branchId = "", new branchId = targetId
     */ rebaseTree ( targetId , opt ? ) { opt = opt || { } ; opt . internalAction = opt . internalAction === undefined ? true : false ; switch ( this . contextValue ) { case "proof-command" : { this . branchId = targetId ; break ; } case "proof-branch" : { if ( opt . forceTargetId ) { opt . forceTargetId = false ; this . branchId = targetId ; } else { const branchName = this . branchId . split ( "." ) . slice ( - 1 ) [ 0 ] ; this . branchId = targetId ? `${ targetId }.${ branchName }` : branchName ; } const oldName = this . name ; this . name = `(${ this . branchId })` ; if ( ! opt . internalAction && this . connection ) { const evt = { action : "did-rename-node" , selected : { id : this . id , name : oldName } , newName : this . name } ; this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } break ; } case "root" : { this . branchId = targetId ; break ; } default : { break ; } } targetId = this . branchId ; for ( let i = 0 ; i < this . children ?. length ; i ++ ) { this . children [ i ] . rebaseTree ( targetId , opt ) ; } } /**
     * Appends the given node as a sibling of the current node
     */ appendSibling ( sib , opt ? ) { if ( sib ) { sib . parent = this . parent ; opt = opt || { } ; let children = [ ] ; const n = this . parent . children . length ; for ( let i = 0 ; i < n ; i ++ ) { if ( ! opt . beforeSelected ) { children . push ( this . parent . children [ i ] ) ; } if ( this . parent . children [ i ] . id === this . id ) { if ( opt . rebase ) { sib . rebaseTree ( this . parent . children [ i ] . branchId ) ; } if ( sib . contextValue === "root" ) { children = children . concat ( sib . children ) ; } else { children . push ( sib ) ; } } if ( opt . beforeSelected ) { children . push ( this . parent . children [ i ] ) ; } } this . parent . children = children ; if ( ! opt . internalAction && this . connection ) { const elem = sib . getNodeXStructure ( ) ; this . log ( `[proof-explorer] Appending node ${ elem . name } (${ elem . id })` ) ; const evt = { action : "did-append-node" , elem , position : this . parent . children . indexOf ( sib ) } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } } /**
     * Appends the given child as first child of the current node
     */ appendChildAtBeginning ( child , opt ? ) { if ( child ) { child . parent = this ; opt = opt || { } ; this . children = this . children || [ ] ; if ( opt . rebase ) { const n = this ?. children ?. filter ( elem => { return elem . getType ( ) === "proof-branch" ; } ) ?. length || 0 ; let targetId = child . getType ( ) === "proof-branch" ? `${ this . branchId }.${ n + 1 }` : this . branchId ; targetId = targetId . startsWith ( "." ) ? targetId . substring ( 1 ) : targetId ; child . rebaseTree ( targetId , { forceTargetId : child . getType ( ) === "proof-branch" } ) ; } if ( child . contextValue === "root" ) { this . children = child . children . concat ( this . children ) ; } else { this . children = [ child ] . concat ( this . children ) ; } if ( ! opt . internalAction && this . connection ) { const elem = child . getNodeXStructure ( ) ; this . log ( `[proof-explorer] Appending node ${ elem . name } (${ elem . id })` ) ; const evt = { action : "did-append-node" , elem , position : this . children . indexOf ( child ) } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } } /**
     * Appends the given child as last child of the current node
     */ appendChild ( child , opt ? ) { opt = opt || { } ; this . children = this . children || [ ] ; child . parent = this ; if ( child . contextValue === "root" ) { this . children = this . children . concat ( child . children ) ; } else { this . children . push ( child ) ; } this . children = this . children . sort ( ( a , b ) : number => { const aa = + ( a . branchId . split ( "." ) . slice ( - 1 ) ) ; const bb = + ( b . branchId . split ( "." ) . slice ( - 1 ) ) ; return ( aa < bb ) ? - 1 : 1 ; } ) ; if ( ! opt . internalAction && this . connection ) { const elem = child . getNodeXStructure ( ) ; this . log ( `[proof-explorer] Appending node ${ elem . name } (${ elem . id })` ) ; const evt = { action : "did-append-node" , elem , position : this . children . indexOf ( child ) } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } /**
     * Returns all branches of the node
     */ getChildren ( ) { return this . children ; } /**
     * Returns the last proof command in the subtree rooted at the node
     */ getLastChildInSubtree ( ) { if ( this . children ?. length ) { const candidate = this . children [ this . children . length - 1 ] ; return candidate . getLastChildInSubtree ( ) ; } return this ; } /**
     * Prints a string representing the sequence proof commands for the tree rooted at the node
     */ printProofCommands ( ) { let ans = ( this . contextValue === "proof-command" ) ? this . name : "" ; if ( this . children && this . children . length ) { for ( let i = 0 ; i < this . children . length ; i ++ ) { ans += this . children [ i ] . printProofCommands ( ) ; } } return ans ; } } /**
 * Proof commands
 */ class ProofCommand extends ProofItem { constructor ( cmd , branchId , parent , connection ) { super ( "proof-command" , cmd , branchId , parent , connection ) ; cmd = cmd . trim ( ) ; this . name = ( cmd && cmd . startsWith ( "(" ) && cmd . endsWith ( ")" ) ) || isUndoCommand ( cmd ) ? cmd : `(${ cmd })` ; this . notVisited ( { internalAction : true } ) ; } clone ( opt ? ) { opt = opt || { } ; const parent = opt . parent || this . parent ; const c = new ProofCommand ( this . name , this . branchId , parent , this . connection ) ; return c ; } } /**
 * Proof branches
 */ class ProofBranch extends ProofItem { constructor ( cmd , branchId , parent , connection ) { super ( "proof-branch" , cmd , branchId , parent , connection ) ; this . name = `(${ branchId })` ; this . notVisited ( { internalAction : true } ) ; } clone ( opt ? ) { opt = opt || { } ; const parent = opt . parent || this . parent ; const c = new ProofBranch ( this . name , this . branchId , parent , this . connection ) ; return c ; } } class WelcomeScreen extends TreeItem { constructor ( connection ) { super ( "welcome-screen" , connection ) ; } } class RootNode extends ProofItem { protected proofStatus : ProofStatus ; initialProofStatus : ProofStatus ; /**
     * Constructor
     */ constructor ( desc ) { super ( "root" , desc . name , "" , null , desc . connection ) ; this . parent = this ; this . proofStatus = desc . proofStatus || "untried" ; this . initialProofStatus = this . proofStatus ; this . notVisited ( { internalAction : true } ) ; } clone ( ) { const c = new RootNode ( { name : this . name , proofStatus : this . proofStatus , connection : this . connection } ) ; c . pending ( ) ; return c ; } visited ( ) { } pending ( ) { if ( this . proofStatus === "untried" ) { this . proofStatus = "unfinished" ; const evt = { action : "did-update-proof-status" , proofStatus : this . proofStatus } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } if ( this . proofStatus !== "proved" ) { super . pending ( ) ; } } /**
     * Sets the root status to QED
     */ QED ( ) { super . treeVisited ( ) ; super . treeComplete ( ) ; this . setProofStatus ( QED ) ; } /**
     * Returns true if the root is QED
     */ isQED ( ) { return this . proofStatus === QED ; } /**
     * Returns true if the proof status has changed
     */ proofStatusChanged ( ) { return this . initialProofStatus !== this . proofStatus ; } /**
     * Sets the proof status of the root
     */ setProofStatus ( proofStatus ) { if ( proofStatus ) { this . proofStatus = proofStatus ; const evt = { action : "did-update-proof-status" , proofStatus : this . proofStatus } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } } resetProofStatus ( ) { this . setProofStatus ( this . initialProofStatus ) ; } getProofStatus ( ) { return this . proofStatus ; } } class GhostNode extends ProofItem { realNode : ProofItem ; constructor ( desc ) { super ( "ghost" , "ghost" , "" , desc . parent , desc . connection ) ; this . realNode = desc . node ; } clone ( ) { const c = new GhostNode ( { parent : this . parent , node : this . realNode , connection : this . connection } ) ; return c ; } active ( ) { this . activeFlag = true ; if ( this . connection ) { if ( this . realNode ) { this . log ( `[proof-explorer] Activating cursor after node ${ this . realNode . name } (${ this . realNode . id })` ) ; const cursor = this . getNodeXStructure ( ) ; cursor . parent = this . realNode . id ; const evt = { action : "did-activate-cursor" , cursor } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } else { console . warn ( `[proof-explorer] Warning: trying to activate cursor node when realNode is null` ) ; } } } notActive ( ) { this . activeFlag = false ; if ( this . connection ) { if ( this . realNode ) { this . log ( `[proof-explorer] Activating cursor after node ${ this . realNode . name } (${ this . realNode . id })` ) ; const evt = { action : "did-deactivate-cursor" } ; if ( this . connection ) { this . connection . sendNotification ( serverEvent . proverEvent , evt ) ; } } else { console . warn ( `[proof-explorer] Warning: trying to activate cursor node when realNode is null` ) ; } } } moveIndicatorBack ( ) { if ( this . realNode . contextValue !== "root" ) { this . notActive ( ) ; this . realNode . active ( ) ; } return this . realNode ; } moveIndicatorForward ( ) { return null ; } appendSibling ( sib , opt ? ) { if ( this . realNode . contextValue === "root" ) { this . realNode . appendChild ( sib ) ; } else { this . realNode . appendSibling ( sib , opt ) ; } } appendChildAtBeginning ( child ) { this . realNode . appendChildAtBeginning ( child ) ; } appendChild ( child ) { this . realNode . appendChild ( child ) ; } }