import evalExpr = require ( './eval-expr' ) ; import getExpr = require ( './get-expr' ) ; import R = require ( './regexes' ) ; interface StateInfo { state : State ; block : Block ; } type IfDirective = 'if' | 'ifset' | 'ifnset' ; const enum Block { NONE , IF , ELSE } const enum State { WORKING , TESTING , ENDING } const ENDIF_MASK = Block . IF | Block . ELSE ; /**
 * Matches a line with a directive without its line-ending because it can be
 * at the end of the file with no last EOL.
 *
 * $1: Directive without the '#' ('if', 'elif', 'else', etc)
 * $2: Possible expression (can be empty or have a comment)
 */ const S_RE_BASE = /^[ \t\f\v]*(?:@)#(if|ifn?set|elif|else|endif|set|unset|error)(?:(?=[ \t])(.*)|\/\/.*)?$/ . source ; param { object } props class Parser { private _cc = [ { block : Block . NONE , state : State . WORKING , } ] ; constructor ( private options ) { } returns { RegExp } public getRegex ( ) { return RegExp ( S_RE_BASE . replace ( '@' , this . options . prefixes ) , 'gm' ) ; } param { Array } match returns { boolean } public parse ( match ) { const key = match [ 1 ] ; const expr = this . _normalize ( key , match [ 2 ] ) ; let ccInfo = this . _cc [ this . _cc . length - 1 ] ; switch ( key ) { case 'if' : case 'ifset' : case 'ifnset' : ccInfo = this . _pushState ( ccInfo , key , expr ) ; break ; case 'elif' : case 'else' : this . _handleElses ( ccInfo , key , expr ) ; break ; case 'endif' : ccInfo = this . _popState ( ccInfo , key ) ; break ; default : this . _handleInstruction ( key , expr , ccInfo . state ) ; } return ccInfo . state === State . WORKING ; } returns { boolean } public close ( ) { const cc = this . _cc ; const err = cc . length !== 1 || cc [ 0 ] . state !== State . WORKING ; if ( err ) { this . _emitError ( 'Unexpected end of file' ) ; } } param { string } message private _emitError ( message ) { this . options . errorHandler ( new Error ( message ) ) ; } param { string } key param { string } expr returns { string } private _normalize ( key , expr ) { if ( key === 'else' || key === 'endif' ) { return '' ; } if ( ! expr ) { this . _emitError ( `Expression expected for #${ key }` ) ; } return getExpr ( key , expr ) ; } /**
     * Throws if the current block is not of the expected type.
     */ private _checkBlock ( ccInfo , key ) { const block = ccInfo . block ; const mask = key === 'endif' ? ENDIF_MASK : Block . IF ; if ( block === Block . NONE || block !== ( block & mask ) ) { this . _emitError ( `Unexpected #${ key }` ) ; } } /**
     * Push a `#if`, `#ifset`, or `#ifnset` directive
     */ private _pushState ( ccInfo , key , expr ) { ccInfo = { block : Block . IF , state : ccInfo . state === State . ENDING ? State . ENDING : this . _getIfValue ( key , expr ) ? State . WORKING : State . TESTING , } ; this . _cc . push ( ccInfo ) ; return ccInfo ; } /**
     * Handles `#elif` and `#else` directives.
     */ private _handleElses ( ccInfo , key , expr ) { this . _checkBlock ( ccInfo , key ) ; if ( key === 'else' ) { ccInfo . block = Block . ELSE ; ccInfo . state = ccInfo . state === State . TESTING ? State . WORKING : State . ENDING ; } else if ( ccInfo . state === State . WORKING ) { ccInfo . state = State . ENDING ; } else if ( ccInfo . state === State . TESTING && this . _getIfValue ( 'if' , expr ) ) { ccInfo . state = State . WORKING ; } } /**
     * Pop the if, ifset, or ifnset directives after endif.
     */ private _popState ( ccInfo , key ) { this . _checkBlock ( ccInfo , key ) ; const cc = this . _cc ; cc . pop ( ) ; return cc [ cc . length - 1 ] ; } param expr private _handleInstruction ( key , expr , state ) { if ( state === State . WORKING ) { switch ( key ) { case 'set' : this . _set ( expr ) ; break ; case 'unset' : this . _unset ( expr ) ; break ; case 'error' : expr = String ( evalExpr ( this . options , expr ) ) ; this . _emitError ( expr ) ; } } } param expr private _set ( expr ) { const match = expr . match ( R . ASSIGNMENT ) ; if ( match ) { const varname = match [ 1 ] ; const exprStr = match [ 2 ] || '' ; this . options . values [ varname ] = exprStr ? evalExpr ( this . options , exprStr . trim ( ) ) : undefined ; } else { this . _emitError ( `Invalid memvar name or assignment: ${ expr }` ) ; } } param varname private _unset ( varname ) { if ( varname . match ( R . VARNAME ) ) { delete this . options . values [ varname ] ; } else { this . _emitError ( `Invalid memvar name "${ varname }"` ) ; } } param key param expr returns private _getIfValue ( key , expr ) { if ( key === 'if' ) { return evalExpr ( this . options , expr ) ? 1 : 0 ; } let yes = expr in this . options . values ? 1 : 0 ; if ( key === 'ifnset' ) { yes ^= 1 ; } return yes ; } } export = Parser ;