import { tokenize } from './tokenizer' ; import { ICommand , ICommands } from './types' ; import { IArgFlagNode , IArgNode , ICmdNode , IAst } from './ast' ; const flagPrefix = /^-(-?)/ ; export const parse = ( source , program , cacheGet ? ) : IAst => { const tokens = tokenize ( source ) . filter ( ( t ) => t . kind !== 'WHITESPACE' ) ; const ast = { source } ; let cmdNodeCtx = null ; let argNodeCtx = null ; const queue = [ ... tokens ] ; let done = false ; while ( queue . length && ! done ) { const token = queue . shift ( ) ; if ( ! token ) { throw new Error ( 'Expected token' ) ; } const isFlag = flagPrefix . test ( token . value ) ; const argKey = isFlag ? token . value . replace ( /^-(-?)/ , '' ) : null ; if ( ! cmdNodeCtx ) { if ( typeof program . commands === 'object' && program . commands [ token . value ] ) { cmdNodeCtx = { kind : 'COMMAND' , ref : program . commands [ token . value ] , token , } ; ast . command = cmdNodeCtx ; } else if ( typeof program . commands === 'function' ) { const key = source . slice ( 0 , token . end ) ; const hit = cacheGet ? cacheGet ( key ) : null ; if ( hit && hit [ token . value ] ) { const ref = hit [ token . value ] ; cmdNodeCtx = { ref , token , kind : 'COMMAND' } ; ast . command = cmdNodeCtx ; } else if ( ! hit ) { ast . pending = { kind : 'PENDING' , token , resolve : program . commands , key , } ; done = true ; } } else { ast . remainder = { kind : 'REMAINDER' , token , cmdNodeCtx : { ref : program , token , kind : 'COMMAND' } , } ; } } else if ( argNodeCtx ) { argNodeCtx . value = { kind : 'ARG_VALUE' , parent : argNodeCtx , token , } ; argNodeCtx = null ; } else if ( argKey && cmdNodeCtx && cmdNodeCtx . ref . args && cmdNodeCtx . ref . args [ argKey ] ) { const argCtx = cmdNodeCtx . ref . args [ argKey ] ; let argNode ; if ( argCtx . type === 'boolean' ) { argNode = { parent : cmdNodeCtx , ref : argCtx , kind : 'ARG_FLAG' , token , name : token . value . replace ( /^-(-?)/ , '' ) , } as IArgFlagNode ; } else { argNode = { parent : cmdNodeCtx , ref : argCtx , kind : 'ARG' , } as IArgNode ; argNode . key = { parent : argNode , token , kind : 'ARG_KEY' , name : token . value . replace ( /^-(-?)/ , '' ) , } ; argNodeCtx = argNode ; } if ( cmdNodeCtx . args ) { cmdNodeCtx . args . push ( argNode ) ; } else { cmdNodeCtx . args = [ argNode ] ; } } else if ( cmdNodeCtx && typeof cmdNodeCtx . ref . commands === 'object' && cmdNodeCtx . ref . commands [ token . value ] ) { const ref = cmdNodeCtx . ref . commands [ token . value ] ; cmdNodeCtx . command = { ref , token , parent : cmdNodeCtx , kind : 'COMMAND' , } ; cmdNodeCtx = cmdNodeCtx . command ; } else if ( cmdNodeCtx && typeof cmdNodeCtx . ref . commands === 'function' ) { const key = source . slice ( 0 , token . end ) ; const hit = cacheGet ? cacheGet ( key ) : null ; if ( hit && hit [ token . value ] ) { const ref = hit [ token . value ] ; cmdNodeCtx . command = { ref , token , parent : cmdNodeCtx , kind : 'COMMAND' , } ; cmdNodeCtx = cmdNodeCtx . command ; } else if ( ! hit ) { ast . pending = { kind : 'PENDING' , token , resolve : cmdNodeCtx . ref . commands , key , } ; done = true ; } else { ast . remainder = { kind : 'REMAINDER' , token , cmdNodeCtx : cmdNodeCtx || undefined , argNodeCtx : argNodeCtx || undefined , } ; } } else { if ( token ) { ast . remainder = { kind : 'REMAINDER' , token , cmdNodeCtx : cmdNodeCtx || undefined , argNodeCtx : argNodeCtx || undefined , } ; } done = true ; } } return ast ; } ;