import { AsyncCallbackFn , CallbackFn , Context , PartialConfig } from "./types" ; import { _buildContext } from "./defaults" ; import { WalkNode } from "./node" ; import { _CallbackStacker } from "./callback" ; import { Break } from "./break" ; class NodeQueue { private queue : WalkNode [ ] = [ ] ; public add : ( nodes : WalkNode [ ] ) => void ; constructor ( private depthFirst ) { this . add = depthFirst ? ( nodes ) => this . queue . unshift ( ... nodes ) : ( nodes ) => this . queue . push ( ... nodes ) ; } public shift ( ) { return this . queue . shift ( ) ; } public get more ( ) : boolean { return this . queue . length > 0 ; } } class Walker < T extends CallbackFn > { private depthFirst : boolean ; constructor ( private ctx ) { this . depthFirst = ctx . config . traversalMode === 'depth' ; } shouldSkipVisitation ( node ) { if ( ! node . canBeCompared ( ) ) return false ; if ( ! this . ctx . seenObjects . has ( node . val ) ) this . ctx . seenObjects . add ( node . val ) ; else if ( this . ctx . config . graphMode === 'graph' ) return true ; else if ( this . ctx . config . graphMode === 'finiteTree' ) throw "The object violates the defined structure. Override 'graphMode' in the config to allow parsing different object structures." ; return false ; } * walk ( target ) { const queue = new NodeQueue ( this . depthFirst ) ; const stacker = _CallbackStacker . forSync ( this . ctx ) ; try { queue . add ( [ WalkNode . fromRoot ( target ) ] ) ; do { const node = queue . shift ( ) ! ; if ( this . shouldSkipVisitation ( node ) ) continue ; const children = node . children ; queue . add ( children ) ; stacker . executeOne ( node , 'preWalk' ) ; yield node ; if ( this . depthFirst && children . length ) stacker . pushToStack ( node , 'postWalk' ) ; else { stacker . executeOne ( node , 'postWalk' ) ; for ( let _ of stacker . execute ( node . id ) ) { } } } while ( queue . more ) ; } catch ( err ) { if ( ! ( err instanceof Break ) ) throw err ; } } async * walkAsync ( target ) { const queue = new NodeQueue ( this . depthFirst ) ; const stacker = _CallbackStacker . forAsync ( this . ctx ) ; try { queue . add ( [ WalkNode . fromRoot ( target ) ] ) ; do { const node = queue . shift ( ) ! ; if ( this . shouldSkipVisitation ( node ) ) continue ; const children = node . children ; queue . add ( children ) ; await stacker . executeOne ( node , 'preWalk' ) ; yield node ; if ( this . depthFirst && children . length ) stacker . pushToStack ( node , 'postWalk' ) ; else { await stacker . executeOne ( node , 'postWalk' ) ; for await ( const _ of stacker . execute ( node . id ) ) { } } } while ( queue . more ) ; } catch ( err ) { if ( ! ( err instanceof Break ) ) throw err ; } } } export function * walkStep ( target , config = { } ) { const ctx = _buildContext ( config ) ; const walker = new Walker < CallbackFn > ( ctx ) ; yield * walker . walk ( target ) ; } export async function * walkAsyncStep ( target , config = { } ) { const ctx = _buildContext ( config ) ; const walker = new Walker < AsyncCallbackFn > ( ctx ) ; yield * walker . walkAsync ( target ) ; } export function walk ( target , config = { } ) { for ( let _ of walkStep ( target , config ) ) { } } export async function walkAsync ( target , config = { } ) { for await ( let _ of walkAsyncStep ( target , config ) ) { } }