/**
 *  Copyright (c) 2020 DeltaXML Ltd. and others.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the MIT license
 *  which accompanies this distribution.
 *
 *  Contributors:
 *  DeltaXML Ltd. - XPath/XSLT Lexer/Syntax Highlighter
 */ import { Debug } from "./diagnostics" ; import { timeStamp } from "console" ; export enum CharLevelState { init , lB , rB , lC , rC , lSq , rSq , lDq , rDq , lBr , rBr , lPr , rPr , lWs , escSq , escDq , sep , lUri , rUri , lNl , rNl , dSep , lVar , exp , lName , lAttr , dSep2 , lEnt , rEnt , lSqEnt , rSqEnt , lDqEnt , rDqEnt , lLiteralSqEnt , rLiteralSqEnt , lLiteralDqEnt , rLiteralDqEnt , dot } export enum TokenLevelState { attributeNameTest , comment , number , Unset , operator , variable , Whitespace , string , uriLiteral , nodeType , simpleType , axisName , nodeNameTest , functionNameTest , complexExpression , function , entityRef , anonymousFunction , mapKey } export enum ExitCondition { None , SingleQuote , DoubleQuote , CurlyBrace } export interface LexPosition { line : number ; startCharacter : number ; documentOffset : number ; } export type TokenTypeStrings = keyof typeof TokenLevelState ; export enum ModifierState { UnusedVar = 0 , UnresolvedRef = 1 } export class Data { public static separators = [ '!' , '*' , '+' , ',' , '-' , '.' , '/' , ':' , '<' , '=' , '>' , '?' , '|' , '%' ] ; public static doubleSeps = [ '!=' , '*:' , '..' , '//' , '::' , ':=' , '<<' , '<=' , '=>' , '>=' , '>>' , '||' ] ; public static anySeps = [ '=' , ':' , '.' , '/' , '=' , '<' , '>' , '|' , '!' , '*' , '+' , ',' , '-' , '.' , '?' , '[' ] ; public static triggerWords = [ "and" , "array" , "as" , "div" , "else" , "eq" , "except" , "ge" , "gt" , "idiv" , "if" , "in" , "intersect" , "is" , "le" , "lt" , "mod" , "ne" , "of" , "or" , "otherwise" , "return" , "satisfies" , "then" , "to" , "union" , "&lt;" , "&gt;" ] ; public static axes = [ "ancestor" , "ancestor-or-self" , "attribute" , "child" , "descendant" , "descendant-or-self" , "following" , "following-sibling" , "namespace" , "parent" , "preceding" , "preceding-sibling" , "self" ] ; public static cAxes = [ "ancestor" , "ancestor-or-self" , "descendant" , "descendant-or-self" , "following" , "following-sibling" , "parent" , "preceding" , "preceding-sibling" , "self" ] ; public static nodeTypes = [ "attribute" , "comment" , "document-node" , "element" , "empty-sequence" , "item" , "namespace-node" , "node" , "processing-instruction" , "schema-attribute" , "schema-element" , "text" ] ; public static nodeTypesBrackets = Data . nodeTypes . map ( t => t + '()' ) ; public static cNodeTypes = [ "attribute" , "comment" , "element" , "item" , "namespace-node" , "node" , "processing-instruction" , "text" ] ; public static keywords = [ "and" , "array" , "as" , "div" , "else" , "eq" , "except" , "function" , "ge" , "gt" , "idiv" , "if" , "in" , "intersect" , "is" , "le" , "lt" , "map" , "mod" , "ne" , "of" , "or" , "otherwise" , "return" , "satisfies" , "then" , "to" , "treat" , "union" , "&lt;" , "&gt;" ] ; public static rangeVars = [ "every" , "for" , "let" , "member" , "some" , "return" ] ; public static firstParts = [ "cast" , "castable" , "instance" , "treat" ] ; public static secondParts = [ "as" , "of" ] ; public static nonFunctionConditional = [ "if" , "then" , "else" ] ; public static nonFunctionTypes = [ "map" , "array" , "function" ] ; public static nonFunctionTypesBrackets = Data . nonFunctionTypes . map ( t => t + '(*)' ) ; public static setAsOperatorIfKeyword ( token ) { if ( token . value === 'return' || token . value === 'satisfies' || token . value === 'in' || Data . nonFunctionConditional . indexOf ( token . value ) > - 1 ) { token . tokenType = TokenLevelState . complexExpression ; } else if ( Data . keywords . indexOf ( token . value ) > - 1 ) { token . tokenType = TokenLevelState . operator ; } else { } } public static isPart2andMatchesPart1 ( part1Token , part2Token ) { let p1 = part1Token . value ; let p2 = part2Token . value ; let isPart2 ; let matchesPart1 ; switch ( p2 ) { case "else" : isPart2 = true ; matchesPart1 = p1 === 'then' ; break ; case "," : matchesPart1 = p1 === 'in' || p1 === ':=' ; isPart2 = p1 === 'then' , p1 === 'in' || p1 === ':=' ; break ; default : isPart2 = false ; matchesPart1 = false ; break ; } return [ isPart2 , matchesPart1 ] ; } } export class XPathLexer { public debug : boolean = false ; public timerOn : boolean = false ; public entityRefOn : boolean = true ; public documentText : string = '' ; public documentTokens : BaseToken [ ] = [ ] ; public attributeNameTests : string [ ] | undefined ; public elementNameTests : string [ ] | undefined ; private latestRealToken : Token | null = null ; private lineNumber : number = 0 ; private wsCharNumber : number = 0 ; private tokenCharNumber : number = 0 ; private wsNewLine = false ; private deferWsNewLine = false ; public reset ( ) { this . documentText = '' ; this . documentTokens = [ ] ; this . attributeNameTests = undefined ; this . elementNameTests = undefined ; this . latestRealToken = null ; this . lineNumber = 0 ; this . wsCharNumber = 0 ; this . tokenCharNumber = 0 ; this . wsNewLine = false ; this . deferWsNewLine = false ; } public static getTextmateTypeLegend ( ) { let textmateTypes = [ ] ; let keyCount = Object . keys ( TokenLevelState ) . length / 2 ; for ( let i = 0 ; i < keyCount ; i ++ ) { textmateTypes . push ( TokenLevelState [ i ] ) ; } return textmateTypes ; } private static isPartOperator ( firstPart , secondPart ) { let result = false ; switch ( firstPart ) { case "cast" : case "castable" : case "treat" : result = secondPart === "as" ; break ; case "instance" : result = secondPart === "of" ; break ; } return result ; } private calcNewState ( isFirstChar , nesting , char , nextChar , existing ) { let rv ; let firstCharOfToken = true ; switch ( existing ) { case CharLevelState . lNl : let charCode = char . charCodeAt ( 0 ) ; let nextCharCode = ( nextChar ) ? nextChar . charCodeAt ( 0 ) : - 1 ; if ( XPathLexer . isDigit ( charCode ) || char === '.' ) { rv = existing ; } else if ( char === 'e' || char === 'E' ) { if ( nextChar === '-' || nextChar === '+' || XPathLexer . isDigit ( nextCharCode ) ) { rv = CharLevelState . exp ; } else { rv = existing ; } } else { ( { rv , nesting } = this . testChar ( existing , firstCharOfToken , char , nextChar , nesting ) ) ; } break ; case CharLevelState . exp : rv = CharLevelState . lNl ; break ; case CharLevelState . rDqEnt : rv = existing ; switch ( nesting ) { case 0 : case 2 : nesting ++ ; break ; case 1 : if ( char === 'u' && nextChar === 'o' ) { nesting ++ ; } else { nesting = 0 ; rv = CharLevelState . lDqEnt ; } break ; case 3 : if ( char === 't' ) { nesting ++ ; } else { rv = CharLevelState . lDqEnt ; nesting = 0 ; } break ; case 4 : if ( char === ';' ) { rv = CharLevelState . rDq ; } else { rv = CharLevelState . lDqEnt ; } nesting = 0 ; break ; } break ; case CharLevelState . rSqEnt : rv = existing ; switch ( nesting ) { case 0 : case 2 : nesting ++ ; break ; case 1 : if ( char === 'p' && nextChar === 'o' ) { nesting ++ ; } else { nesting = 0 ; rv = CharLevelState . lSqEnt ; } break ; case 3 : if ( char === 's' ) { nesting ++ ; } else { rv = CharLevelState . lSqEnt ; nesting = 0 ; } break ; case 4 : if ( char === ';' ) { rv = CharLevelState . rSq ; } else { rv = CharLevelState . lSqEnt ; } nesting = 0 ; break ; } break ; case CharLevelState . lWs : if ( char === ' ' || char === '\t' ) { rv = existing ; this . wsCharNumber ++ ; } else if ( char === '\n' ) { rv = existing ; this . wsCharNumber = 0 ; this . wsNewLine = true ; this . lineNumber ++ ; } else { ( { rv , nesting } = this . testChar ( existing , firstCharOfToken , char , nextChar , nesting ) ) ; } break ; case CharLevelState . lName : case CharLevelState . lVar : case CharLevelState . lAttr : if ( char === '-' || char === '.' || ( char === ':' && ! ( nextChar === ':' || nextChar === '*' || nextChar === ' ' || nextChar === '\n' || nextChar === '\r' || nextChar === '\t' ) ) ) { rv = existing ; } else { ( { rv , nesting } = this . testChar ( existing , isFirstChar , char , nextChar , nesting ) ) ; } break ; case CharLevelState . dSep : rv = CharLevelState . dSep2 ; break ; case CharLevelState . lUri : rv = ( char === '}' ) ? CharLevelState . rUri : existing ; break ; case CharLevelState . lSqEnt : rv = ( char === '&' && nextChar === 'a' ) ? CharLevelState . rSqEnt : existing ; break ; case CharLevelState . lDqEnt : rv = ( char === '&' && nextChar === 'q' ) ? CharLevelState . rDqEnt : existing ; break ; case CharLevelState . lSq : case CharLevelState . rLiteralSqEnt : if ( char === '\'' ) { if ( nextChar === '\'' ) { rv = CharLevelState . escSq ; } else { rv = CharLevelState . rSq ; } } else if ( char === '&' ) { rv = CharLevelState . lLiteralSqEnt ; } else { rv = CharLevelState . lSq ; } break ; case CharLevelState . lLiteralSqEnt : if ( char === '\'' ) { if ( nextChar === '\'' ) { rv = CharLevelState . escSq ; } else { rv = CharLevelState . rSq ; } } else if ( char === ';' ) { rv = CharLevelState . rLiteralSqEnt ; } else { rv = existing ; } break ; case CharLevelState . escSq : rv = CharLevelState . lSq ; break ; case CharLevelState . escDq : rv = CharLevelState . lDq ; break ; case CharLevelState . lDq : case CharLevelState . rLiteralDqEnt : if ( char === '\"' ) { if ( nextChar === '\"' ) { rv = CharLevelState . escDq ; } else { rv = CharLevelState . rDq ; } } else if ( char === '&' ) { rv = CharLevelState . lLiteralDqEnt ; } else { rv = CharLevelState . lDq ; } break ; case CharLevelState . lLiteralDqEnt : if ( char === '"' ) { if ( nextChar === '"' ) { rv = CharLevelState . escDq ; } else { rv = CharLevelState . rDq ; } } else if ( char === ';' ) { rv = CharLevelState . rLiteralDqEnt ; } else { rv = existing ; } break ; case CharLevelState . lC : if ( char === ':' && nextChar === ')' ) { rv = ( nesting === 1 ) ? CharLevelState . rC : existing ; nesting -- ; } else if ( char === '(' && nextChar === ':' ) { rv = existing ; nesting ++ ; } else { rv = existing ; } break ; case CharLevelState . lEnt : rv = ( char === ';' ) ? CharLevelState . rEnt : existing ; break ; default : ( { rv , nesting } = this . testChar ( existing , isFirstChar , char , nextChar , nesting ) ) ; } return [ rv , nesting ] ; } public analyse ( xpathArg , exitCondition , position ) { if ( this . timerOn ) { console . time ( 'xplexer.analyse' ) ; } this . latestRealToken = null ; this . lineNumber = position . line ; this . wsCharNumber = 0 ; this . tokenCharNumber = position . startCharacter ; this . wsNewLine = false ; this . deferWsNewLine = false ; let xpath = xpathArg . length === 0 ? this . documentText : xpathArg ; let currentState = [ CharLevelState . init , 0 ] ; let currentChar = '' ; let tokenChars = [ ] ; if ( exitCondition === ExitCondition . None ) { this . documentTokens . length = 0 ; } let result = this . documentTokens ; let nestedTokenStack = [ ] ; let poppedContext = null ; if ( this . debug ) { console . log ( "xpath:\n" + xpath ) ; Debug . debugHeading ( ) ; } if ( xpath . length === 0 ) { return [ ] ; } for ( let i = position . documentOffset ; i < xpath . length + 1 ; i ++ ) { let nextChar = xpath . charAt ( i ) ; let [ currentLabelState , nestingState ] = currentState ; let nextState ; let isFirstTokenChar = tokenChars . length === 0 ; if ( currentChar ) { let exitAnalysis = false ; switch ( exitCondition ) { case ExitCondition . None : exitAnalysis = false ; break ; case ExitCondition . CurlyBrace : if ( currentLabelState !== CharLevelState . lDq && currentLabelState !== CharLevelState . lSq && currentLabelState !== CharLevelState . lC && currentChar === "}" ) { let isNestedOk = false ; for ( var x = 0 ; x < nestedTokenStack . length ; x ++ ) { if ( nestedTokenStack [ x ] . value === '{' ) { isNestedOk = true ; break ; } } exitAnalysis = ! isNestedOk ; } break ; case ExitCondition . DoubleQuote : exitAnalysis = currentChar === "\"" ; break ; case ExitCondition . SingleQuote : exitAnalysis = currentChar === "'" ; break ; } if ( exitAnalysis ) { this . update ( poppedContext , result , tokenChars , currentLabelState ) ; if ( result . length > 0 ) { let lastToken = result [ result . length - 1 ] ; if ( lastToken . tokenType === TokenLevelState . string ) { XPathLexer . checkExitStringLiteralEnd ( lastToken , result ) ; } else if ( lastToken . tokenType === TokenLevelState . entityRef ) { if ( result . length > 1 ) { const nextLastToken = result [ result . length - 2 ] ; if ( nextLastToken . tokenType === TokenLevelState . string ) { if ( ! lastToken . value . endsWith ( '&quot;' ) && ! lastToken . value . startsWith ( '&apos;' ) ) { lastToken [ 'error' ] = ErrorType . XPathStringLiteral ; } } } } } position . line = this . lineNumber ; position . startCharacter = this . tokenCharNumber ; position . documentOffset = i ; return result ; } nextState = this . calcNewState ( isFirstTokenChar , nestingState , currentChar , nextChar , currentLabelState ) ; let [ nextLabelState ] = nextState ; if ( ( nextLabelState === currentLabelState && ! ( this . unChangedStateSignificant ( currentLabelState ) ) ) || ( currentLabelState === CharLevelState . exp && nextLabelState == CharLevelState . lNl ) ) { if ( currentChar == '\n' && ( currentLabelState === CharLevelState . lSq || currentLabelState === CharLevelState . lDq || currentLabelState === CharLevelState . lC || currentLabelState === CharLevelState . lSqEnt || currentLabelState === CharLevelState . lDqEnt ) ) { this . update ( poppedContext , result , tokenChars , currentLabelState ) ; this . lineNumber ++ ; this . tokenCharNumber = 0 ; } else { tokenChars . push ( currentChar ) ; } } else { switch ( nextLabelState ) { case CharLevelState . lNl : case CharLevelState . lVar : case CharLevelState . lName : case CharLevelState . lEnt : case CharLevelState . lLiteralSqEnt : case CharLevelState . lLiteralDqEnt : this . update ( poppedContext , result , tokenChars , currentLabelState ) ; tokenChars = [ ] ; tokenChars . push ( currentChar ) ; break ; case CharLevelState . exp : case CharLevelState . rSqEnt : case CharLevelState . rDqEnt : tokenChars . push ( currentChar ) ; break ; case CharLevelState . dSep : this . update ( poppedContext , result , tokenChars , currentLabelState ) ; let bothChars = currentChar + nextChar ; this . updateResult ( poppedContext , result , new BasicToken ( bothChars , nextLabelState ) ) ; break ; case CharLevelState . dSep2 : break ; case CharLevelState . sep : case CharLevelState . dot : this . update ( poppedContext , result , tokenChars , currentLabelState ) ; this . updateResult ( poppedContext , result , new BasicToken ( currentChar , nextLabelState ) ) ; break ; case CharLevelState . escSq : case CharLevelState . escDq : tokenChars . push ( currentChar ) ; break ; case CharLevelState . rC : tokenChars . push ( ':)' ) ; this . update ( poppedContext , result , tokenChars , currentLabelState ) ; break ; case CharLevelState . lB : case CharLevelState . lBr : case CharLevelState . lPr : this . update ( poppedContext , result , tokenChars , currentLabelState ) ; let currentToken ; currentToken = new FlattenedToken ( currentChar , nextLabelState , this . latestRealToken ) ; this . updateResult ( poppedContext , result , currentToken ) ; nestedTokenStack . push ( currentToken ) ; this . latestRealToken = null ; break ; case CharLevelState . rB : case CharLevelState . rBr : case CharLevelState . rPr : if ( currentLabelState !== CharLevelState . rC ) { let prevToken = new BasicToken ( tokenChars . join ( '' ) , currentLabelState ) ; this . updateResult ( poppedContext , result , prevToken ) ; let newToken = new BasicToken ( currentChar , nextLabelState ) ; this . updateResult ( poppedContext , result , newToken ) ; if ( nestedTokenStack . length > 0 ) { if ( XPathLexer . closeMatchesOpen ( nextLabelState , nestedTokenStack ) ) { poppedContext = nestedTokenStack . pop ( ) ?. context ; } else { newToken [ 'error' ] = ErrorType . BracketNesting ; } } else { newToken [ 'error' ] = ErrorType . BracketNesting ; } tokenChars = [ ] ; } break ; case CharLevelState . rEnt : tokenChars . push ( currentChar ) ; let ent = tokenChars . join ( '' ) ; if ( ent === '&quot;' ) { nextState = [ CharLevelState . lDqEnt , 0 ] ; } else if ( ent === '&apos;' ) { nextState = [ CharLevelState . lSqEnt , 0 ] ; } else { let entToken = new BasicToken ( ent , CharLevelState . lName ) ; this . updateResult ( poppedContext , result , entToken ) ; tokenChars . length = 0 ; } break ; case CharLevelState . rSq : case CharLevelState . rDq : case CharLevelState . rUri : case CharLevelState . rLiteralSqEnt : case CharLevelState . rLiteralDqEnt : tokenChars . push ( currentChar ) ; this . update ( poppedContext , result , tokenChars , currentLabelState ) ; break ; case CharLevelState . lSq : case CharLevelState . lDq : case CharLevelState . lC : case CharLevelState . lWs : case CharLevelState . lUri : if ( currentLabelState !== CharLevelState . escSq && currentLabelState !== CharLevelState . escDq ) { this . update ( poppedContext , result , tokenChars , currentLabelState ) ; } tokenChars . push ( currentChar ) ; break ; default : if ( currentLabelState === CharLevelState . rC ) { tokenChars = [ ] ; } else if ( currentLabelState === CharLevelState . lWs ) { this . update ( poppedContext , result , tokenChars , currentLabelState ) ; tokenChars . push ( currentChar ) ; } else { tokenChars . push ( currentChar ) ; } break ; } } if ( ! nextChar && tokenChars . length > 0 ) { this . update ( poppedContext , result , tokenChars , nextLabelState ) ; } currentState = nextState ; } currentChar = nextChar ; } if ( this . timerOn ) { console . timeEnd ( 'xplexer.analyse' ) ; } return result ; } public static checkExitStringLiteralEnd ( lastToken , result ) { let followsEntityRef = false ; let followsString = false ; if ( result . length > 1 ) { const nextLastToken = result [ result . length - 2 ] ; followsEntityRef = nextLastToken . tokenType === TokenLevelState . entityRef ; followsString = nextLastToken . tokenType === TokenLevelState . string ; } if ( followsEntityRef ) { let lastChar = lastToken . value . charAt ( lastToken . value . length - 1 ) ; const lastCharIsSingleQuote = lastChar === "'" ; if ( lastChar !== '"' && ! lastCharIsSingleQuote && ! lastToken . value . endsWith ( '&quot;' ) && ! lastToken . value . startsWith ( '&apos;' ) ) { lastToken [ 'error' ] = ErrorType . XPathStringLiteral ; } else if ( lastChar === '"' || lastChar === "'" && lastToken . length > 1 ) { const mod2Chars = [ ... lastToken . value ] . filter ( l => l === lastChar ) . length % 2 ; if ( mod2Chars === 0 ) { lastToken [ 'error' ] = ErrorType . XPathStringLiteral ; } } } else if ( ! followsString ) { this . checkStringLiteralEnd ( lastToken ) ; } } public static checkStringLiteralEnd ( lastToken ) { let lastChar = lastToken . value . charAt ( lastToken . value . length - 1 ) ; let firstChar = lastToken . value . charAt ( 0 ) ; if ( ! ( ( lastChar === firstChar && lastToken . value . length > 1 ) || ( lastToken . value . length > 6 && ( lastToken . value . startsWith ( '&quot;' ) && lastToken . value . endsWith ( '&quot;' ) ) || ( lastToken . value . startsWith ( '&apos;' ) && lastToken . value . endsWith ( '&apos;' ) ) ) ) ) { lastToken [ 'error' ] = ErrorType . XPathStringLiteral ; } } private static closeMatchesOpen ( close , stack ) { let open = stack [ stack . length - 1 ] . charType ; let result = false ; switch ( close ) { case CharLevelState . rB : result = open === CharLevelState . lB ; break ; case CharLevelState . rBr : result = open === CharLevelState . lBr ; break ; case CharLevelState . rPr : result = open === CharLevelState . lPr ; } return result ; } private update ( poppedContext , result , tokenChars , charState ) { if ( tokenChars . length > 0 ) { this . updateResult ( poppedContext , result , new BasicToken ( tokenChars . join ( '' ) , charState ) ) ; } tokenChars . length = 0 ; } private unChangedStateSignificant ( charState ) { let result = false ; switch ( charState ) { case CharLevelState . lB : case CharLevelState . lBr : case CharLevelState . lPr : case CharLevelState . rB : case CharLevelState . rBr : case CharLevelState . rPr : case CharLevelState . sep : case CharLevelState . dot : result = true ; } return result ; } private updateResult ( poppedContext , result , newToken ) { let cachedRealToken = this . latestRealToken ; let state = newToken . charType ; let newTokenValue = newToken . value ; if ( newTokenValue !== '' ) { newToken . length = newTokenValue . length ; newToken . line = this . lineNumber ; newToken . startCharacter = this . tokenCharNumber ; if ( newToken . tokenType === TokenLevelState . nodeNameTest ) { if ( newTokenValue . includes ( '#' ) ) { newToken . tokenType = TokenLevelState . functionNameTest ; } } let isWhitespace = newToken . charType === CharLevelState . lWs ; if ( this . deferWsNewLine ) { if ( isWhitespace ) { this . lineNumber ++ ; newToken . line = this . lineNumber ; this . tokenCharNumber = this . wsCharNumber ; } else { this . tokenCharNumber = 0 ; this . lineNumber ++ ; this . wsNewLine = true ; } this . deferWsNewLine = false ; } else if ( this . wsNewLine ) { if ( isWhitespace ) { this . tokenCharNumber = this . wsCharNumber ; } else { this . tokenCharNumber += newTokenValue . length ; } this . wsNewLine = false ; } else { this . tokenCharNumber += newTokenValue . length ; } if ( ! isWhitespace ) { this . wsCharNumber = 0 ; } if ( ! isWhitespace ) { result . push ( newToken ) ; } let prevToken = this . latestRealToken ; this . setLabelForLastTokenOnly ( prevToken , newToken ) ; this . setLabelsUsingCurrentToken ( poppedContext , prevToken , newToken ) ; if ( prevToken && ! isWhitespace && this . elementNameTests && this . attributeNameTests ) { if ( prevToken . tokenType === TokenLevelState . nodeNameTest ) { if ( this . elementNameTests . indexOf ( prevToken . value ) < 0 ) { this . elementNameTests . push ( prevToken . value ) ; } } else if ( prevToken . tokenType === TokenLevelState . attributeNameTest ) { if ( this . attributeNameTests . indexOf ( prevToken . value ) < 0 ) { this . attributeNameTests . push ( prevToken . value ) ; } } } if ( ! ( state === CharLevelState . lC || state === CharLevelState . lWs ) ) { this . latestRealToken = newToken ; } if ( this . debug ) { Debug . printDebugOutput ( cachedRealToken , newToken , newToken . line , newToken . startCharacter ) ; } } } private conditionallyPopStack ( stack , token ) { if ( stack . length > 0 ) { let [ isPart2 , matchesPart1 ] = Data . isPart2andMatchesPart1 ( stack [ stack . length - 1 ] , token ) ; const initStackVal = stack . length > 0 ? stack [ stack . length - 1 ] . value : '' ; if ( ( initStackVal === 'return' || initStackVal === 'satisfies' ) && token . value === ',' ) { stack . pop ( ) ; let validStackValue = null ; let init = true ; while ( stack . length > 0 ) { if ( init ) { init = false ; let v = stack [ stack . length - 1 ] . value ; if ( v === ':=' || v === 'in' ) { validStackValue = v ; stack . pop ( ) ; } } else if ( stack [ stack . length - 1 ] . value === validStackValue ) { stack . pop ( ) ; } else { break ; } } } else if ( isPart2 ) { if ( matchesPart1 ) { stack . pop ( ) ; } else token [ 'error' ] = ErrorType . XPathUnexpected ; } } } private updateTokenBeforeBrackets ( prevToken ) { if ( prevToken . tokenType === TokenLevelState . nodeNameTest ) { if ( Data . nodeTypes . indexOf ( prevToken . value ) > - 1 ) { prevToken . tokenType = TokenLevelState . nodeType ; } else if ( Data . nonFunctionConditional . indexOf ( prevToken . value ) > - 1 ) { prevToken . tokenType = TokenLevelState . complexExpression ; } else if ( prevToken . value === 'function' ) { prevToken . tokenType = TokenLevelState . anonymousFunction ; } else { prevToken . tokenType = TokenLevelState . function ; } } else if ( prevToken . tokenType === TokenLevelState . simpleType && ( Data . nodeTypes . indexOf ( prevToken . value ) > - 1 ) ) { prevToken . tokenType = TokenLevelState . nodeType ; } } private setLabelForLastTokenOnly ( prevToken , currentToken ) { let currentState = currentToken . charType ; if ( prevToken ) { if ( prevToken . charType === CharLevelState . lName ) { switch ( currentState ) { case CharLevelState . lVar : if ( Data . rangeVars . indexOf ( prevToken . value ) > - 1 ) { prevToken . tokenType = TokenLevelState . complexExpression ; } break ; case CharLevelState . lB : this . updateTokenBeforeBrackets ( prevToken ) ; break ; case CharLevelState . dSep : if ( currentToken . value === '::' ) { if ( Data . axes . indexOf ( prevToken . value ) < 0 ) { prevToken [ 'error' ] = ErrorType . AxisName ; } prevToken . tokenType = TokenLevelState . axisName ; } else if ( currentToken . value === '()' ) { this . updateTokenBeforeBrackets ( prevToken ) ; } else if ( currentToken . value === '{}' && ( prevToken . value === 'map' || prevToken . value === 'array' ) ) { prevToken . tokenType = TokenLevelState . operator ; } break ; case CharLevelState . lBr : if ( prevToken . value === 'map' || prevToken . value === 'array' ) { prevToken . tokenType = TokenLevelState . operator ; } break ; case CharLevelState . lName : if ( currentToken . value === 'member' && prevToken . value === 'for' ) { prevToken . tokenType = TokenLevelState . complexExpression ; } break ; } } else if ( currentState === CharLevelState . sep && prevToken . tokenType === TokenLevelState . string && currentToken . value === ':' ) { prevToken . tokenType = TokenLevelState . mapKey ; } } } private setLabelsUsingCurrentToken ( poppedContext , prevToken , currentToken ) { if ( ! ( prevToken ) ) { prevToken = new BasicToken ( ',' , CharLevelState . sep ) ; prevToken . tokenType = TokenLevelState . operator ; } let currentValue = currentToken . value ; switch ( currentToken . charType ) { case CharLevelState . lName : switch ( prevToken . charType ) { case CharLevelState . lName : if ( Data . secondParts . indexOf ( currentValue ) > - 1 && XPathLexer . isPartOperator ( prevToken . value , currentValue ) ) { prevToken . tokenType = TokenLevelState . operator ; currentToken . tokenType = TokenLevelState . operator ; } else if ( XPathLexer . isTokenTypeEqual ( prevToken , TokenLevelState . operator ) ) { if ( prevToken . value === 'as' || prevToken . value === 'of' ) { currentToken . tokenType = TokenLevelState . simpleType ; } else if ( prevToken . value === '&gt;' && currentToken . value === '&gt;' ) { currentToken . tokenType = TokenLevelState . operator ; } else if ( prevToken . value === '&lt;' && ( currentToken . value === '&lt;' || currentToken . value === '&gt;' ) ) { currentToken . tokenType = TokenLevelState . operator ; } } else if ( prevToken . tokenType === TokenLevelState . nodeNameTest || prevToken . tokenType === TokenLevelState . functionNameTest || XPathLexer . isTokenTypeAType ( prevToken ) ) { Data . setAsOperatorIfKeyword ( currentToken ) ; } break ; case CharLevelState . rB : case CharLevelState . rBr : case CharLevelState . rPr : case CharLevelState . lAttr : case CharLevelState . lNl : case CharLevelState . lVar : case CharLevelState . lSq : case CharLevelState . lDq : case CharLevelState . rLiteralSqEnt : case CharLevelState . rLiteralDqEnt : case CharLevelState . rDqEnt : case CharLevelState . rSqEnt : case CharLevelState . dot : Data . setAsOperatorIfKeyword ( currentToken ) ; break ; case CharLevelState . dSep : if ( prevToken . value === '()' || prevToken . value === '..' || prevToken . value === '[]' || prevToken . value === '{}' ) { Data . setAsOperatorIfKeyword ( currentToken ) ; } break ; default : if ( XPathLexer . isTokenTypeUnset ( prevToken ) && Data . keywords . indexOf ( currentValue ) > - 1 ) { currentToken . tokenType = TokenLevelState . operator ; } else if ( XPathLexer . isCharTypeEqual ( prevToken , CharLevelState . dSep ) && prevToken . value === '()' && Data . keywords . indexOf ( currentValue ) > - 1 ) { currentToken . tokenType = TokenLevelState . operator ; } else if ( XPathLexer . isTokenTypeEqual ( prevToken , TokenLevelState . operator ) && ( prevToken . value === 'as' || prevToken . value === 'of' ) ) { currentToken . tokenType = TokenLevelState . simpleType ; } break ; } break ; case CharLevelState . sep : let prevTokenT = prevToken . tokenType ; let isStar = currentToken . value === '*' ; if ( isStar && ( prevTokenT === TokenLevelState . attributeNameTest || prevTokenT === TokenLevelState . uriLiteral || ( prevTokenT === TokenLevelState . operator && prevToken . value === '?' ) || prevTokenT === TokenLevelState . nodeType && prevToken . value === '()' ) ) { currentToken . charType = CharLevelState . lName ; currentToken . tokenType = TokenLevelState . nodeType ; } else { let possOccurrentIndicator = currentToken . value === '?' || currentToken . value === '+' || isStar ; if ( possOccurrentIndicator ) { if ( prevTokenT === TokenLevelState . simpleType && prevToken . length > 1 ) { currentToken . charType = CharLevelState . lName ; currentToken . tokenType = TokenLevelState . simpleType ; } else if ( prevTokenT === TokenLevelState . operator && ( prevToken . value === ')' ) || prevToken . value === ']' ) { if ( poppedContext && poppedContext . tokenType === TokenLevelState . simpleType ) { currentToken . charType = CharLevelState . lName ; currentToken . tokenType = TokenLevelState . nodeType ; } } else if ( isStar && ( prevTokenT === TokenLevelState . operator || prevTokenT === TokenLevelState . complexExpression ) ) { if ( prevTokenT === TokenLevelState . operator && prevToken . charType === CharLevelState . dSep && prevToken . value === '()' ) { } else { currentToken . charType = CharLevelState . lName ; currentToken . tokenType = TokenLevelState . nodeType ; } } } } break ; case CharLevelState . dSep : if ( currentToken . value === ':*' || currentToken . value === '..' ) { currentToken . charType = CharLevelState . lName ; currentToken . tokenType = TokenLevelState . nodeType ; } else if ( currentToken . value === '()' && prevToken . tokenType === TokenLevelState . nodeType ) { currentToken . charType = CharLevelState . lName ; currentToken . tokenType = TokenLevelState . nodeType ; } break ; } } testChar ( existingState , isFirstChar , char , nextChar , nesting ) { let rv ; if ( isFirstChar && char === 'Q' && nextChar === '{' ) { rv = CharLevelState . lUri ; return { rv , nesting } ; } switch ( char ) { case '(' : if ( nextChar === ':' ) { rv = CharLevelState . lC ; nesting ++ ; } else if ( nextChar == ')' ) { rv = CharLevelState . dSep ; } else { rv = CharLevelState . lB ; } break ; case '{' : if ( nextChar === '}' ) { rv = CharLevelState . dSep ; } else { rv = CharLevelState . lBr ; } break ; case '[' : if ( nextChar === ']' ) { rv = CharLevelState . dSep ; } else { rv = CharLevelState . lPr ; } break ; case ')' : rv = CharLevelState . rB ; break ; case ']' : rv = CharLevelState . rPr ; break ; case '}' : rv = CharLevelState . rBr ; break ; case '\'' : rv = CharLevelState . lSq ; break ; case '\"' : rv = CharLevelState . lDq ; break ; case ' ' : case '\t' : rv = CharLevelState . lWs ; this . wsCharNumber ++ ; break ; case '\r' : rv = CharLevelState . lWs ; break ; case '\n' : this . deferWsNewLine = true ; rv = CharLevelState . lWs ; break ; case '+' : case '-' : rv = CharLevelState . sep ; break ; case '&' : rv = ( this . entityRefOn ) ? CharLevelState . lEnt : existingState ; break ; default : let doubleChar = char + nextChar ; if ( ( nextChar ) && Data . doubleSeps . indexOf ( doubleChar ) > - 1 ) { rv = CharLevelState . dSep ; break ; } else if ( Data . separators . indexOf ( char ) > - 1 ) { let isDot = char === '.' ; if ( isDot && ! ! ( nextChar ) && XPathLexer . isDigit ( nextChar . charCodeAt ( 0 ) ) ) { rv = CharLevelState . lNl ; } else if ( isDot ) { rv = CharLevelState . dot ; } else { rv = CharLevelState . sep ; } } else if ( isFirstChar ) { let charCode = char . charCodeAt ( 0 ) ; let nextCharCode = ( nextChar ) ? nextChar . charCodeAt ( 0 ) : - 1 ; if ( charCode === 46 ) { if ( nextCharCode === 46 ) { rv = CharLevelState . dSep ; } else { rv = XPathLexer . isDigit ( nextCharCode ) ? CharLevelState . lNl : CharLevelState . dot ; } } else if ( XPathLexer . isDigit ( charCode ) ) { rv = CharLevelState . lNl ; } else if ( char === '$' ) { rv = CharLevelState . lVar ; } else if ( char === '@' ) { rv = CharLevelState . lAttr ; } else { rv = CharLevelState . lName ; } } else { rv = existingState ; } } return { rv , nesting } ; } private static isDigit ( charCode ) { return charCode > 47 && charCode < 58 ; } public static isCharTypeEqual ( token , type2 ) { return token ? token . charType === type2 : false ; } private static isTokenTypeEqual ( token , type2 ) { return token . tokenType === type2 ; } private static isTokenTypeAType ( token ) { return token . tokenType === TokenLevelState . simpleType || token . tokenType === TokenLevelState . nodeType ; } private static isTokenTypeUnset ( token ) { return token . tokenType . valueOf ( ) === TokenLevelState . Unset . valueOf ( ) ; } } export enum ErrorType { None , AxisName , UnusedVariable , UnresolvedVarReference , DuplicateVarName , DuplicateFnName , DuplicateParameterName , DuplicateTemplateName , DuplicateAccumulatorName , ElementNesting , ElementNestingX , ParentLessText , ProcessingInstructionName , MultiRoot , XMLName , XMLSyntax , XSLTName , XSLTInstrUnexpected , XSLTAttrUnexpected , XPathTypeName , XSLTPrefix , MissingTemplateParam , IterateParamInvalid , MissingPrefixInList , TemplateNameUnresolved , TemplateModeUnresolved , AttributeSetUnresolved , AccumulatorNameUnresolved , XMLDeclaration , XPathName , EntityName , XPathFunction , XSLTFunctionNamePrefix , XPathEmpty , XPathFunctionNamespace , XPathFunctionUnexpected , XPathOperatorUnexpected , XPathPrefix , XPathKeyword , XPathExpectedComplex , XMLXMLNS , XMLAttributeName , XSLTNamesapce , XMLAttributeXMLNS , XMLAttNameSyntax , XMLAttEqualExpected , XMLDupllicateAtt , XPathUnexpected , XPathAwaiting , XPathStringLiteral , BracketNesting , ExpectedElseAfterThen , ExpectedDollarAfterComma , PopNesting , XSLTKeyUnresolved , XMLRootMissing , DTD } export interface BaseToken { line : number ; startCharacter : number ; length : number ; value : string ; charType ? : number ; tokenType : number ; context ? : BaseToken | null ; error ? : ErrorType ; nesting ? : number ; referenced ? : boolean ; } export interface Token extends BaseToken { charType ? : CharLevelState ; tokenType : TokenLevelState ; context ? : Token | null ; children ? : Token [ ] ; error ? : ErrorType ; } export class Utilities { public static minimiseTokens ( tokens ) { let r = new Array ( ) ; for ( let token of tokens ) { if ( token . charType !== CharLevelState . lWs ) { delete token . charType ; delete token . context ; r . push ( token ) ; } if ( token . children ) { token . children = this . minimiseTokens ( token . children ) ; } } return r ; } public static minimiseTokens2 ( tokens ) { let r = new Array ( ) ; for ( let token of tokens ) { if ( token . charType !== CharLevelState . lWs ) { delete token . charType ; delete token . context ; r . push ( token ) ; } if ( token . children ) { token . children = this . minimiseTokens2 ( token . children ) ; } } return r ; } } class BasicToken implements Token { line = 0 ; startCharacter = 0 ; length = 0 ; value : string ; charType : CharLevelState ; tokenType : TokenLevelState ; constructor ( value , type ) { this . value = value ; this . charType = type ; switch ( type ) { case CharLevelState . lWs : this . tokenType = TokenLevelState . Whitespace ; break ; case CharLevelState . lName : this . tokenType = TokenLevelState . nodeNameTest ; break ; case CharLevelState . dSep : this . tokenType = value === ':=' ? TokenLevelState . complexExpression : TokenLevelState . operator ; break ; case CharLevelState . dot : this . tokenType = TokenLevelState . nodeType ; break ; case CharLevelState . sep : case CharLevelState . lB : case CharLevelState . lBr : case CharLevelState . lPr : case CharLevelState . rB : case CharLevelState . rBr : case CharLevelState . rPr : this . tokenType = TokenLevelState . operator ; break ; case CharLevelState . lAttr : this . tokenType = TokenLevelState . attributeNameTest ; break ; case CharLevelState . lNl : this . tokenType = TokenLevelState . number ; break ; case CharLevelState . lVar : this . tokenType = TokenLevelState . variable ; break ; case CharLevelState . lSq : case CharLevelState . lDq : case CharLevelState . lSqEnt : case CharLevelState . lDqEnt : case CharLevelState . rDqEnt : case CharLevelState . rSqEnt : case CharLevelState . rLiteralSqEnt : case CharLevelState . rLiteralDqEnt : this . tokenType = TokenLevelState . string ; break ; case CharLevelState . lUri : this . tokenType = TokenLevelState . uriLiteral ; break ; case CharLevelState . lC : this . tokenType = TokenLevelState . comment ; break ; case CharLevelState . lLiteralSqEnt : case CharLevelState . lLiteralDqEnt : this . tokenType = TokenLevelState . entityRef ; break ; default : this . tokenType = TokenLevelState . Unset ; break ; } } } class FlattenedToken implements Token { line = 0 ; startCharacter = 0 ; length = 0 ; value : string ; charType : CharLevelState ; tokenType : TokenLevelState ; context : Token | null ; constructor ( value , type , context ) { this . value = value ; this . charType = type ; this . tokenType = TokenLevelState . operator ; this . context = context ; } }