import Tokenizer from './tokenizer' ; import ErrorCodes from '../errors/io-error-codes' ; import { InternetObjectSyntaxError } from '../errors/io-error' ; import { isKeyVal , isToken } from '../utils/is' ; import { ASTParserTree , Token } from './' ; import { COMMA , CURLY_CLOSED , SQUARE_CLOSED , TILDE , DATASEP , SQUARE_OPEN , CURLY_OPEN , COLON } from './constants' ; const NOT_STARTED = 'not-started' ; const STARTED = 'started' ; const FINISHED = 'finished' ; const ERROR = 'error' ; internal export default class ASTParser { private _isCollection = false ; private _isHeaderOpen = true ; private _lastToken ? : Token = undefined ; private _isHeader : boolean ; private _stack : ASTParserTree [ ] = [ ] ; private _text : string ; private _tokenizer : Tokenizer ; private _tree : any = { } ; private _isFinalToken = false ; public status = NOT_STARTED ; param text { string } param isHeader { boolean } constructor ( text , isHeader = false ) { this . _text = text ; this . _tokenizer = new Tokenizer ( text ) ; this . _isHeader = isHeader ; } /**
     * Parses the text.
     */ parse = ( ) => { const tokenizer = this . _tokenizer ; while ( ! tokenizer . done ) { let token = tokenizer . read ( ) ; this . _isFinalToken = tokenizer . done ; if ( token !== null ) { this . _process ( token ) ; } } this . _finalize ( ) ; } ; /**
     * Returns the header part. Throws an error if invoked
     * before the parsing is finished!
     */ public get header ( ) { if ( this . status !== FINISHED ) { throw new Error ( 'parsing-not-finished' ) ; } return this . _tree . header ; } /**
     * Returns the data part. Throws an error if invoked
     * before the parsing is finished!
     */ public get data ( ) { if ( this . status !== FINISHED ) { throw new Error ( 'parsing-not-finished' ) ; } return this . _tree . data ; } /**
     * Returns the text this object has parsed or about
     * to be parsed.
     */ public get text ( ) { return this . _text ; } private _process = ( token ) => { const isFinalToken = this . _isFinalToken ; let stack = this . _stack ; if ( token . type === 'comment' ) { return ; } this . status = STARTED ; if ( token . type === 'sep' || token . type === 'datasep' ) { if ( token . value === '{' ) { this . _push ( 'object' , token . index , token . row , token . col ) ; } else if ( token . value === '[' ) { this . _push ( 'array' , token . index , token . row , token . col ) ; } else if ( token . value === COLON ) { const obj = this . _getObject ( ) ; let lastToken = obj . values [ obj . values . length - 1 ] ; if ( isToken ( lastToken ) && [ 'string' , 'number' , 'boolean' ] . indexOf ( typeof lastToken . value ) > - 1 ) { obj . values [ obj . values . length - 1 ] = { key : lastToken . value , value : undefined , index : lastToken . index , row : lastToken . row , col : lastToken . col } ; } else { throw new InternetObjectSyntaxError ( ErrorCodes . unexpectedColon , '' , token ) ; } } else if ( token . value === COMMA ) { if ( isFinalToken && this . _lastToken !== undefined && ( this . _lastToken . value === COMMA || this . _lastToken . value === TILDE ) ) { this . _addValue ( undefined , this . _lastToken . index , this . _lastToken . row , this . _lastToken . col ) ; this . _addValue ( undefined , token . index , token . row , token . col ) ; } else if ( isFinalToken || this . _lastToken === undefined || [ CURLY_OPEN , SQUARE_OPEN , COLON , DATASEP ] . indexOf ( this . _lastToken . value ) > - 1 || this . _lastToken . value === COMMA || this . _lastToken . value === TILDE ) { this . _addValue ( undefined , token . index , token . row , token . col ) ; } } else if ( token . value === CURLY_CLOSED || token . value === SQUARE_CLOSED ) { if ( this . _lastToken && this . _lastToken . value === COMMA ) { this . _addValue ( undefined , token . index , token . row , token . col ) ; } this . _pop ( token . value === CURLY_CLOSED ? 'object' : 'array' , token ) ; } else if ( token . value === TILDE ) { if ( this . _stack . length === 0 ) { this . _isCollection = true ; } else if ( ! this . _isCollection && ! this . _tree . data ) { throw new InternetObjectSyntaxError ( ErrorCodes . invalidCollection , undefined , token ) ; } else { this . _processObject ( ) ; } } else if ( token . value === DATASEP ) { if ( ! this . _isHeaderOpen ) { throw new InternetObjectSyntaxError ( ErrorCodes . multipleHeaders , 'Multiple headers are not allowed.' , token ) ; } this . _processObject ( ) ; this . _isHeaderOpen = false ; this . _isCollection = false ; this . _stack . length = 0 ; } } else { this . _addValue ( token , token . index , token . row , token . col ) ; } this . _lastToken = token ; } ; private _processObject = ( ) => { const isCollection = this . _isCollection ; const isHeaderOpen = this . _isHeaderOpen ; const data = this . _stack [ 0 ] ; if ( this . _stack . length === 0 ) { return ; } if ( this . _stack . length > 1 ) { throw new InternetObjectSyntaxError ( ErrorCodes . openBracket , 'Expecting bracket to be closed' , this . _lastToken || undefined ) ; } if ( isHeaderOpen ) { if ( isCollection ) { if ( ! this . _tree . header ) { this . _tree . header = { type : 'collection' , values : [ data ] } ; } else { this . _tree . header . values . push ( data ) ; } } else { this . _tree . header = data ; } } else { if ( isCollection ) { if ( ! this . _tree . data ) { this . _tree . data = { type : 'collection' , values : [ data ] } ; } else { this . _tree . data . values . push ( data ) ; } } else { this . _tree . data = data ; } } this . _stack . length = 0 ; } ; private _finalize = ( ) => { this . status = FINISHED ; this . _processObject ( ) ; if ( this . _stack . length > 1 ) { console . warn ( 'Invalid Stack' , this . _stack ) ; } if ( this . _isHeaderOpen ) { this . _tree . data = this . _tree . header ; delete this . _tree . header ; if ( this . _tree . data ) { if ( this . data . values . length === 1 && this . _isCollection === false && isToken ( this . data . values [ 0 ] ) ) { this . _tree . data . type = 'scalar' ; } else if ( this . _tree . data . type === 'object' && this . _tree . data . values . length === 1 ) { this . _tree . data = this . _tree . data . values [ 0 ] ; } } } if ( this . _isHeader ) { this . _tree . header = this . _tree . data ; this . _tree . data = undefined ; } } ; private _getOrCreateObject = ( index , row , col ) => { if ( this . _stack . length === 0 ) { this . _stack . push ( { type : 'object' , values : [ ] , index , row , col } ) ; } let obj = this . _stack [ this . _stack . length - 1 ] ; return obj ; } ; private _getObject = ( ) => { let obj = this . _stack [ this . _stack . length - 1 ] ; return obj ; } ; private _addValue = ( value , index , row , col ) => { let obj = this . _getOrCreateObject ( index , row , col ) ; if ( this . _lastToken && this . _lastToken . value === ':' ) { let lastVal = obj . values [ obj . values . length - 1 ] ; if ( lastVal && isKeyVal ( lastVal ) ) { lastVal . value = value ; } else { console . warn ( 'Verify this case!' ) ; } } else { this . _checkValueSlot ( value ) ; obj . values . push ( value ) ; } } ; private _checkValueSlot ( value ) { const token = this . _lastToken ; if ( token === undefined ) return ; if ( value === undefined ) return ; if ( isToken ( value ) && value . value === '' ) return ; if ( [ ',' , ':' , '~' , '{' , '[' , '---' ] . indexOf ( token . value ) === - 1 ) { throw new InternetObjectSyntaxError ( ErrorCodes . expectingSeparator , '' , value ) ; } } private _push = ( type = 'object' , index , row , col , values = [ ] ) => { let object = this . _getOrCreateObject ( index , row , col ) ; let newObject = { type , values , index , row , col } ; this . _addValue ( newObject , index , row , col ) ; this . _stack . push ( newObject ) ; } ; private _pop = ( type , token ) => { const obj = this . _getObject ( ) ; if ( obj . type !== type ) { const message = `Expecting "${ obj . type === 'object' ? '}' : ']' }" but found "${ obj . type === 'object' ? ']' : '}' }"` ; this . status = ERROR ; throw new InternetObjectSyntaxError ( ErrorCodes . invalidBracket , message , token ) ; } this . _stack . pop ( ) ; } ; }