import { NameToken , ValueToken , Repeater , AllTokens , BracketType , Bracket , Operator , OperatorType , Quote , WhiteSpace , Literal } from '../tokenizer' ; import tokenScanner , { TokenScanner , peek , consume , readable , next , error , slice } from './TokenScanner' ; import { ParserOptions } from '../types' ; export type TokenStatement = TokenElement | TokenGroup ; export interface TokenAttribute { name ? : ValueToken [ ] ; value ? : ValueToken [ ] ; expression ? : boolean ; } export interface TokenElement { type : 'TokenElement' ; name ? : NameToken [ ] ; attributes ? : TokenAttribute [ ] ; value ? : ValueToken [ ] ; repeat ? : Repeater ; selfClose : boolean ; elements : TokenStatement [ ] ; } export interface TokenGroup { type : 'TokenGroup' ; elements : TokenStatement [ ] ; repeat ? : Repeater ; } export default function abbreviation ( abbr , options = { } ) { const scanner = tokenScanner ( abbr ) ; const result = statements ( scanner , options ) ; if ( readable ( scanner ) ) { throw error ( scanner , 'Unexpected character' ) ; } return result ; } function statements ( scanner , options ) { const result = { type : 'TokenGroup' , elements : [ ] } ; let ctx = result ; let node ; const stack = [ ] ; while ( readable ( scanner ) ) { if ( node = element ( scanner , options ) || group ( scanner , options ) ) { ctx . elements . push ( node ) ; if ( consume ( scanner , isChildOperator ) ) { stack . push ( ctx ) ; ctx = node ; } else if ( consume ( scanner , isSiblingOperator ) ) { continue ; } else if ( consume ( scanner , isClimbOperator ) ) { do { if ( stack . length ) { ctx = stack . pop ( ) ! ; } } while ( consume ( scanner , isClimbOperator ) ) ; } } else { break ; } } return result ; } /**
 * Consumes group from given scanner
 */ function group ( scanner , options ) { if ( consume ( scanner , isGroupStart ) ) { const result = statements ( scanner , options ) ; const token = next ( scanner ) ; if ( isBracket ( token , 'group' , false ) ) { result . repeat = repeater ( scanner ) ; } return result ; } } /**
 * Consumes single element from given scanner
 */ function element ( scanner , options ) { let attr ; const elem = { type : 'TokenElement' , name : void 0 , attributes : void 0 , value : void 0 , repeat : void 0 , selfClose : false , elements : [ ] } ; if ( elementName ( scanner , options ) ) { elem . name = slice ( scanner ) as NameToken [ ] ; } while ( readable ( scanner ) ) { scanner . start = scanner . pos ; if ( ! elem . repeat && ! isEmpty ( elem ) && consume ( scanner , isRepeater ) ) { elem . repeat = scanner . tokens [ scanner . pos - 1 ] as Repeater ; } else if ( ! elem . value && text ( scanner ) ) { elem . value = getText ( scanner ) ; } else if ( attr = shortAttribute ( scanner , 'id' , options ) || shortAttribute ( scanner , 'class' , options ) || attributeSet ( scanner ) ) { if ( ! elem . attributes ) { elem . attributes = Array . isArray ( attr ) ? attr . slice ( ) : [ attr ] ; } else { elem . attributes = elem . attributes . concat ( attr ) ; } } else { if ( ! isEmpty ( elem ) && consume ( scanner , isCloseOperator ) ) { elem . selfClose = true ; if ( ! elem . repeat && consume ( scanner , isRepeater ) ) { elem . repeat = scanner . tokens [ scanner . pos - 1 ] as Repeater ; } } break ; } } return ! isEmpty ( elem ) ? elem : void 0 ; } /**
 * Consumes attribute set from given scanner
 */ function attributeSet ( scanner ) { if ( consume ( scanner , isAttributeSetStart ) ) { const attributes = [ ] ; let attr ; while ( readable ( scanner ) ) { if ( attr = attribute ( scanner ) ) { attributes . push ( attr ) ; } else if ( consume ( scanner , isAttributeSetEnd ) ) { break ; } else if ( ! consume ( scanner , isWhiteSpace ) ) { throw error ( scanner , `Unexpected "${ peek ( scanner ) ! . type }" token` ) ; } } return attributes ; } } /**
 * Consumes attribute shorthand (class or id) from given scanner
 */ function shortAttribute ( scanner , type , options ) { if ( isOperator ( peek ( scanner ) , type ) ) { scanner . pos ++ ; const attr = { name : [ createLiteral ( type ) ] } ; if ( options . jsx && text ( scanner ) ) { attr . value = getText ( scanner ) ; attr . expression = true ; } else { attr . value = literal ( scanner ) ? slice ( scanner ) as ValueToken [ ] : void 0 ; } return attr ; } } /**
 * Consumes single attribute from given scanner
 */ function attribute ( scanner ) { if ( quoted ( scanner ) ) { return { value : slice ( scanner ) as ValueToken [ ] } ; } if ( literal ( scanner , true ) ) { return { name : slice ( scanner ) as NameToken [ ] , value : consume ( scanner , isEquals ) && ( quoted ( scanner ) || literal ( scanner , true ) ) ? slice ( scanner ) as ValueToken [ ] : void 0 } ; } } function repeater ( scanner ) { return isRepeater ( peek ( scanner ) ) ? scanner . tokens [ scanner . pos ++ ] as Repeater : void 0 ; } /**
 * Consumes quoted value from given scanner, if possible
 */ function quoted ( scanner ) { const start = scanner . pos ; const quote = peek ( scanner ) ; if ( isQuote ( quote ) ) { scanner . pos ++ ; while ( readable ( scanner ) ) { if ( isQuote ( next ( scanner ) , quote . single ) ) { scanner . start = start ; return true ; } } throw error ( scanner , 'Unclosed quote' , quote ) ; } return false ; } /**
 * Consumes literal (unquoted value) from given scanner
 */ function literal ( scanner , allowBrackets ? ) { const start = scanner . pos ; const brackets = { attribute : 0 , expression : 0 , group : 0 } ; while ( readable ( scanner ) ) { const token = peek ( scanner ) ; if ( brackets . expression ) { if ( isBracket ( token , 'expression' ) ) { brackets [ token . context ] += token . open ? 1 : - 1 ; } } else if ( isQuote ( token ) || isOperator ( token ) || isWhiteSpace ( token ) || isRepeater ( token ) ) { break ; } else if ( isBracket ( token ) ) { if ( ! allowBrackets ) { break ; } if ( token . open ) { brackets [ token . context ] ++ ; } else if ( ! brackets [ token . context ] ) { break ; } else { brackets [ token . context ] -- ; } } scanner . pos ++ ; } if ( start !== scanner . pos ) { scanner . start = start ; return true ; } return false ; } /**
 * Consumes element name from given scanner
 */ function elementName ( scanner , options ) { const start = scanner . pos ; if ( options . jsx && consume ( scanner , isCapitalizedLiteral ) ) { while ( readable ( scanner ) ) { const { pos } = scanner ; if ( ! consume ( scanner , isClassNameOperator ) || ! consume ( scanner , isCapitalizedLiteral ) ) { scanner . pos = pos ; break ; } } } while ( readable ( scanner ) && consume ( scanner , isElementName ) ) { } if ( scanner . pos !== start ) { scanner . start = start ; return true ; } return false ; } /**
 * Consumes text value from given scanner
 */ function text ( scanner ) { const start = scanner . pos ; if ( consume ( scanner , isTextStart ) ) { let brackets = 0 ; while ( readable ( scanner ) ) { const token = next ( scanner ) ; if ( isBracket ( token , 'expression' ) ) { if ( token . open ) { brackets ++ ; } else if ( ! brackets ) { break ; } else { brackets -- ; } } } scanner . start = start ; return true ; } return false ; } function getText ( scanner ) { let from = scanner . start ; let to = scanner . pos ; if ( isBracket ( scanner . tokens [ from ] , 'expression' , true ) ) { from ++ ; } if ( isBracket ( scanner . tokens [ to - 1 ] , 'expression' , false ) ) { to -- ; } return slice ( scanner , from , to ) as ValueToken [ ] ; } export function isBracket ( token , context ? , isOpen ? ) { return Boolean ( token && token . type === 'Bracket' && ( ! context || token . context === context ) && ( isOpen == null || token . open === isOpen ) ) ; } export function isOperator ( token , type ? ) { return Boolean ( token && token . type === 'Operator' && ( ! type || token . operator === type ) ) ; } export function isQuote ( token , isSingle ? ) { return Boolean ( token && token . type === 'Quote' && ( isSingle == null || token . single === isSingle ) ) ; } function isWhiteSpace ( token ? ) { return Boolean ( token && token . type === 'WhiteSpace' ) ; } function isEquals ( token ) { return isOperator ( token , 'equal' ) ; } function isRepeater ( token ? ) { return Boolean ( token && token . type === 'Repeater' ) ; } function isLiteral ( token ) { return token . type === 'Literal' ; } function isCapitalizedLiteral ( token ) { if ( isLiteral ( token ) ) { const ch = token . value . charCodeAt ( 0 ) ; return ch >= 65 && ch <= 90 ; } return false ; } function isElementName ( token ) { return token . type === 'Literal' || token . type === 'RepeaterNumber' || token . type === 'RepeaterPlaceholder' ; } function isClassNameOperator ( token ) { return isOperator ( token , 'class' ) ; } function isAttributeSetStart ( token ? ) { return isBracket ( token , 'attribute' , true ) ; } function isAttributeSetEnd ( token ? ) { return isBracket ( token , 'attribute' , false ) ; } function isTextStart ( token ) { return isBracket ( token , 'expression' , true ) ; } function isGroupStart ( token ) { return isBracket ( token , 'group' , true ) ; } function createLiteral ( value ) { return { type : 'Literal' , value } ; } function isEmpty ( elem ) { return ! elem . name && ! elem . value && ! elem . attributes ; } function isChildOperator ( token ) { return isOperator ( token , 'child' ) ; } function isSiblingOperator ( token ) { return isOperator ( token , 'sibling' ) ; } function isClimbOperator ( token ) { return isOperator ( token , 'climb' ) ; } function isCloseOperator ( token ) { return isOperator ( token , 'close' ) ; }