'use strict' ; export enum TokenType { Ident , AtKeyword , String , BadString , UnquotedString , Hash , Num , Percentage , Dimension , UnicodeRange , CDO , CDC , Colon , SemiColon , CurlyL , CurlyR , ParenthesisL , ParenthesisR , BracketL , BracketR , Whitespace , Includes , Dashmatch , SubstringOperator , PrefixOperator , SuffixOperator , Delim , EMS , EXS , Length , Angle , Time , Freq , Exclamation , Resolution , Comma , Charset , EscapedJavaScript , BadEscapedJavaScript , Comment , SingleLineComment , EOF , CustomToken } export interface IToken { type : TokenType ; text : string ; offset : number ; len : number ; } export class MultiLineStream { private source : string ; private len : number ; private position : number ; constructor ( source ) { this . source = source ; this . len = source . length ; this . position = 0 ; } public substring ( from , to = this . position ) { return this . source . substring ( from , to ) ; } public eos ( ) { return this . len <= this . position ; } public pos ( ) { return this . position ; } public goBackTo ( pos ) { this . position = pos ; } public goBack ( n ) { this . position -= n ; } public advance ( n ) { this . position += n ; } public nextChar ( ) { return this . source . charCodeAt ( this . position ++ ) || 0 ; } public peekChar ( n = 0 ) { return this . source . charCodeAt ( this . position + n ) || 0 ; } public lookbackChar ( n = 0 ) { return this . source . charCodeAt ( this . position - n ) || 0 ; } public advanceIfChar ( ch ) { if ( ch === this . source . charCodeAt ( this . position ) ) { this . position ++ ; return true ; } return false ; } public advanceIfChars ( ch ) { if ( this . position + ch . length > this . source . length ) { return false ; } let i = 0 ; for ( ; i < ch . length ; i ++ ) { if ( this . source . charCodeAt ( this . position + i ) !== ch [ i ] ) { return false ; } } this . advance ( i ) ; return true ; } public advanceWhileChar ( condition ) { const posNow = this . position ; while ( this . position < this . len && condition ( this . source . charCodeAt ( this . position ) ) ) { this . position ++ ; } return this . position - posNow ; } } const _a = 'a' . charCodeAt ( 0 ) ; const _f = 'f' . charCodeAt ( 0 ) ; const _z = 'z' . charCodeAt ( 0 ) ; const _A = 'A' . charCodeAt ( 0 ) ; const _F = 'F' . charCodeAt ( 0 ) ; const _Z = 'Z' . charCodeAt ( 0 ) ; const _0 = '0' . charCodeAt ( 0 ) ; const _9 = '9' . charCodeAt ( 0 ) ; const _TLD = '~' . charCodeAt ( 0 ) ; const _HAT = '^' . charCodeAt ( 0 ) ; const _EQS = '=' . charCodeAt ( 0 ) ; const _PIP = '|' . charCodeAt ( 0 ) ; const _MIN = '-' . charCodeAt ( 0 ) ; const _USC = '_' . charCodeAt ( 0 ) ; const _PRC = '%' . charCodeAt ( 0 ) ; const _MUL = '*' . charCodeAt ( 0 ) ; const _LPA = '(' . charCodeAt ( 0 ) ; const _RPA = ')' . charCodeAt ( 0 ) ; const _LAN = '<' . charCodeAt ( 0 ) ; const _RAN = '>' . charCodeAt ( 0 ) ; const _ATS = '@' . charCodeAt ( 0 ) ; const _HSH = '#' . charCodeAt ( 0 ) ; const _DLR = '$' . charCodeAt ( 0 ) ; const _BSL = '\\' . charCodeAt ( 0 ) ; const _FSL = '/' . charCodeAt ( 0 ) ; const _NWL = '\n' . charCodeAt ( 0 ) ; const _CAR = '\r' . charCodeAt ( 0 ) ; const _LFD = '\f' . charCodeAt ( 0 ) ; const _DQO = '"' . charCodeAt ( 0 ) ; const _SQO = '\'' . charCodeAt ( 0 ) ; const _WSP = ' ' . charCodeAt ( 0 ) ; const _TAB = '\t' . charCodeAt ( 0 ) ; const _SEM = ';' . charCodeAt ( 0 ) ; const _COL = ':' . charCodeAt ( 0 ) ; const _CUL = '{' . charCodeAt ( 0 ) ; const _CUR = '}' . charCodeAt ( 0 ) ; const _BRL = '[' . charCodeAt ( 0 ) ; const _BRR = ']' . charCodeAt ( 0 ) ; const _CMA = ',' . charCodeAt ( 0 ) ; const _DOT = '.' . charCodeAt ( 0 ) ; const _BNG = '!' . charCodeAt ( 0 ) ; const staticTokenTable = { } ; staticTokenTable [ _SEM ] = TokenType . SemiColon ; staticTokenTable [ _COL ] = TokenType . Colon ; staticTokenTable [ _CUL ] = TokenType . CurlyL ; staticTokenTable [ _CUR ] = TokenType . CurlyR ; staticTokenTable [ _BRR ] = TokenType . BracketR ; staticTokenTable [ _BRL ] = TokenType . BracketL ; staticTokenTable [ _LPA ] = TokenType . ParenthesisL ; staticTokenTable [ _RPA ] = TokenType . ParenthesisR ; staticTokenTable [ _CMA ] = TokenType . Comma ; const staticUnitTable = { } ; staticUnitTable [ 'em' ] = TokenType . EMS ; staticUnitTable [ 'ex' ] = TokenType . EXS ; staticUnitTable [ 'px' ] = TokenType . Length ; staticUnitTable [ 'cm' ] = TokenType . Length ; staticUnitTable [ 'mm' ] = TokenType . Length ; staticUnitTable [ 'in' ] = TokenType . Length ; staticUnitTable [ 'pt' ] = TokenType . Length ; staticUnitTable [ 'pc' ] = TokenType . Length ; staticUnitTable [ 'deg' ] = TokenType . Angle ; staticUnitTable [ 'rad' ] = TokenType . Angle ; staticUnitTable [ 'grad' ] = TokenType . Angle ; staticUnitTable [ 'ms' ] = TokenType . Time ; staticUnitTable [ 's' ] = TokenType . Time ; staticUnitTable [ 'hz' ] = TokenType . Freq ; staticUnitTable [ 'khz' ] = TokenType . Freq ; staticUnitTable [ '%' ] = TokenType . Percentage ; staticUnitTable [ 'fr' ] = TokenType . Percentage ; staticUnitTable [ 'dpi' ] = TokenType . Resolution ; staticUnitTable [ 'dpcm' ] = TokenType . Resolution ; export class Scanner { public stream : MultiLineStream = new MultiLineStream ( '' ) ; public ignoreComment = true ; public ignoreWhitespace = true ; public inURL = false ; public setSource ( input ) { this . stream = new MultiLineStream ( input ) ; } public finishToken ( offset , type , text ? ) { return { offset : offset , len : this . stream . pos ( ) - offset , type : type , text : text || this . stream . substring ( offset ) } ; } public substring ( offset , len ) { return this . stream . substring ( offset , offset + len ) ; } public pos ( ) { return this . stream . pos ( ) ; } public goBackTo ( pos ) { this . stream . goBackTo ( pos ) ; } public scanUnquotedString ( ) { const offset = this . stream . pos ( ) ; const content = [ ] ; if ( this . _unquotedString ( content ) ) { return this . finishToken ( offset , TokenType . UnquotedString , content . join ( '' ) ) ; } return null ; } public scan ( ) { const triviaToken = this . trivia ( ) ; if ( triviaToken !== null ) { return triviaToken ; } const offset = this . stream . pos ( ) ; if ( this . stream . eos ( ) ) { return this . finishToken ( offset , TokenType . EOF ) ; } return this . scanNext ( offset ) ; } protected scanNext ( offset ) { if ( this . stream . advanceIfChars ( [ _LAN , _BNG , _MIN , _MIN ] ) ) { return this . finishToken ( offset , TokenType . CDO ) ; } if ( this . stream . advanceIfChars ( [ _MIN , _MIN , _RAN ] ) ) { return this . finishToken ( offset , TokenType . CDC ) ; } let content = [ ] ; if ( this . ident ( content ) ) { return this . finishToken ( offset , TokenType . Ident , content . join ( '' ) ) ; } if ( this . stream . advanceIfChar ( _ATS ) ) { content = [ '@' ] ; if ( this . _name ( content ) ) { const keywordText = content . join ( '' ) ; if ( keywordText === '@charset' ) { return this . finishToken ( offset , TokenType . Charset , keywordText ) ; } return this . finishToken ( offset , TokenType . AtKeyword , keywordText ) ; } else { return this . finishToken ( offset , TokenType . Delim ) ; } } if ( this . stream . advanceIfChar ( _HSH ) ) { content = [ '#' ] ; if ( this . _name ( content ) ) { return this . finishToken ( offset , TokenType . Hash , content . join ( '' ) ) ; } else { return this . finishToken ( offset , TokenType . Delim ) ; } } if ( this . stream . advanceIfChar ( _BNG ) ) { return this . finishToken ( offset , TokenType . Exclamation ) ; } if ( this . _number ( ) ) { const pos = this . stream . pos ( ) ; content = [ this . stream . substring ( offset , pos ) ] ; if ( this . stream . advanceIfChar ( _PRC ) ) { return this . finishToken ( offset , TokenType . Percentage ) ; } else if ( this . ident ( content ) ) { const dim = this . stream . substring ( pos ) . toLowerCase ( ) ; const tokenType = < TokenType > staticUnitTable [ dim ] ; if ( typeof tokenType !== 'undefined' ) { return this . finishToken ( offset , tokenType , content . join ( '' ) ) ; } else { return this . finishToken ( offset , TokenType . Dimension , content . join ( '' ) ) ; } } return this . finishToken ( offset , TokenType . Num ) ; } content = [ ] ; let tokenType = this . _string ( content ) ; if ( tokenType !== null ) { return this . finishToken ( offset , tokenType , content . join ( '' ) ) ; } tokenType = < TokenType > staticTokenTable [ this . stream . peekChar ( ) ] ; if ( typeof tokenType !== 'undefined' ) { this . stream . advance ( 1 ) ; return this . finishToken ( offset , tokenType ) ; } if ( this . stream . peekChar ( 0 ) === _TLD && this . stream . peekChar ( 1 ) === _EQS ) { this . stream . advance ( 2 ) ; return this . finishToken ( offset , TokenType . Includes ) ; } if ( this . stream . peekChar ( 0 ) === _PIP && this . stream . peekChar ( 1 ) === _EQS ) { this . stream . advance ( 2 ) ; return this . finishToken ( offset , TokenType . Dashmatch ) ; } if ( this . stream . peekChar ( 0 ) === _MUL && this . stream . peekChar ( 1 ) === _EQS ) { this . stream . advance ( 2 ) ; return this . finishToken ( offset , TokenType . SubstringOperator ) ; } if ( this . stream . peekChar ( 0 ) === _HAT && this . stream . peekChar ( 1 ) === _EQS ) { this . stream . advance ( 2 ) ; return this . finishToken ( offset , TokenType . PrefixOperator ) ; } if ( this . stream . peekChar ( 0 ) === _DLR && this . stream . peekChar ( 1 ) === _EQS ) { this . stream . advance ( 2 ) ; return this . finishToken ( offset , TokenType . SuffixOperator ) ; } this . stream . nextChar ( ) ; return this . finishToken ( offset , TokenType . Delim ) ; } protected trivia ( ) { while ( true ) { const offset = this . stream . pos ( ) ; if ( this . _whitespace ( ) ) { if ( ! this . ignoreWhitespace ) { return this . finishToken ( offset , TokenType . Whitespace ) ; } } else if ( this . comment ( ) ) { if ( ! this . ignoreComment ) { return this . finishToken ( offset , TokenType . Comment ) ; } } else { return null ; } } } protected comment ( ) { if ( this . stream . advanceIfChars ( [ _FSL , _MUL ] ) ) { let success = false , hot = false ; this . stream . advanceWhileChar ( ( ch ) => { if ( hot && ch === _FSL ) { success = true ; return false ; } hot = ch === _MUL ; return true ; } ) ; if ( success ) { this . stream . advance ( 1 ) ; } return true ; } return false ; } private _number ( ) { let npeek = 0 , ch ; if ( this . stream . peekChar ( ) === _DOT ) { npeek = 1 ; } ch = this . stream . peekChar ( npeek ) ; if ( ch >= _0 && ch <= _9 ) { this . stream . advance ( npeek + 1 ) ; this . stream . advanceWhileChar ( ( ch ) => { return ch >= _0 && ch <= _9 || npeek === 0 && ch === _DOT ; } ) ; return true ; } return false ; } private _newline ( result ) { const ch = this . stream . peekChar ( ) ; switch ( ch ) { case _CAR : case _LFD : case _NWL : this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; if ( ch === _CAR && this . stream . advanceIfChar ( _NWL ) ) { result . push ( '\n' ) ; } return true ; } return false ; } private _escape ( result , includeNewLines ? ) { let ch = this . stream . peekChar ( ) ; if ( ch === _BSL ) { this . stream . advance ( 1 ) ; ch = this . stream . peekChar ( ) ; let hexNumCount = 0 ; while ( hexNumCount < 6 && ( ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F ) ) { this . stream . advance ( 1 ) ; ch = this . stream . peekChar ( ) ; hexNumCount ++ ; } if ( hexNumCount > 0 ) { try { const hexVal = parseInt ( this . stream . substring ( this . stream . pos ( ) - hexNumCount ) , 16 ) ; if ( hexVal ) { result . push ( String . fromCharCode ( hexVal ) ) ; } } catch ( e ) { } if ( ch === _WSP || ch === _TAB ) { this . stream . advance ( 1 ) ; } else { this . _newline ( [ ] ) ; } return true ; } if ( ch !== _CAR && ch !== _LFD && ch !== _NWL ) { this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; return true ; } else if ( includeNewLines ) { return this . _newline ( result ) ; } } return false ; } private _stringChar ( closeQuote , result ) { const ch = this . stream . peekChar ( ) ; if ( ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL ) { this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; return true ; } return false ; } private _string ( result ) { if ( this . stream . peekChar ( ) === _SQO || this . stream . peekChar ( ) === _DQO ) { const closeQuote = this . stream . nextChar ( ) ; result . push ( String . fromCharCode ( closeQuote ) ) ; while ( this . _stringChar ( closeQuote , result ) || this . _escape ( result , true ) ) { } if ( this . stream . peekChar ( ) === closeQuote ) { this . stream . nextChar ( ) ; result . push ( String . fromCharCode ( closeQuote ) ) ; return TokenType . String ; } else { return TokenType . BadString ; } } return null ; } private _unquotedChar ( result ) { const ch = this . stream . peekChar ( ) ; if ( ch !== 0 && ch !== _BSL && ch !== _SQO && ch !== _DQO && ch !== _LPA && ch !== _RPA && ch !== _WSP && ch !== _TAB && ch !== _NWL && ch !== _LFD && ch !== _CAR ) { this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; return true ; } return false ; } protected _unquotedString ( result ) { let hasContent = false ; while ( this . _unquotedChar ( result ) || this . _escape ( result ) ) { hasContent = true ; } return hasContent ; } private _whitespace ( ) { const n = this . stream . advanceWhileChar ( ( ch ) => { return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR ; } ) ; return n > 0 ; } private _name ( result ) { let matched = false ; while ( this . _identChar ( result ) || this . _escape ( result ) ) { matched = true ; } return matched ; } protected ident ( result ) { const pos = this . stream . pos ( ) ; const hasMinus = this . _minus ( result ) ; if ( hasMinus ) { if ( this . _minus ( result ) || this . _identFirstChar ( result ) || this . _escape ( result ) ) { while ( this . _identChar ( result ) || this . _escape ( result ) ) { } return true ; } } else if ( this . _identFirstChar ( result ) || this . _escape ( result ) ) { while ( this . _identChar ( result ) || this . _escape ( result ) ) { } return true ; } this . stream . goBackTo ( pos ) ; return false ; } private _identFirstChar ( result ) { const ch = this . stream . peekChar ( ) ; if ( ch === _USC || ch >= _a && ch <= _z || ch >= _A && ch <= _Z || ch >= 0x80 && ch <= 0xFFFF ) { this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; return true ; } return false ; } private _minus ( result ) { const ch = this . stream . peekChar ( ) ; if ( ch === _MIN ) { this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; return true ; } return false ; } private _identChar ( result ) { const ch = this . stream . peekChar ( ) ; if ( ch === _USC || ch === _MIN || ch >= _a && ch <= _z || ch >= _A && ch <= _Z || ch >= _0 && ch <= _9 || ch >= 0x80 && ch <= 0xFFFF ) { this . stream . advance ( 1 ) ; result . push ( String . fromCharCode ( ch ) ) ; return true ; } return false ; } }