in src/language/semantics/xpLexer.ts [643:932]
public analyse(
xpathArg: string,
exitCondition: ExitCondition | null,
position: LexPosition
): Token[] {
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, number] = [CharLevelState.init, 0]
let currentChar: string = ''
let tokenChars: string[] = []
if (exitCondition === ExitCondition.None) {
this.documentTokens.length = 0
}
let result = this.documentTokens
let nestedTokenStack: Token[] = []
let poppedContext: Token | undefined | null = null
if (xpath.length === 0) {
return []
}
for (let i = position.documentOffset; i < xpath.length + 1; i++) {
let nextChar: string = xpath.charAt(i)
// deconstruct state:
let [currentLabelState, nestingState] = currentState
let nextState: [CharLevelState, number]
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('"') &&
!lastToken.value.startsWith(''')
) {
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)
) {
// do nothing if state has not changed
// or we're within a number with an exponent
if (
currentChar === '\n' &&
(currentLabelState === CharLevelState.lSq ||
currentLabelState === CharLevelState.lDq ||
currentLabelState === CharLevelState.lC ||
currentLabelState === CharLevelState.lSqEnt ||
currentLabelState === CharLevelState.lDqEnt)
) {
// split multi-line strings or comments - don't include newline char
this.update(poppedContext, result, tokenChars, currentLabelState)
this.lineNumber++
this.tokenCharNumber = 0
} else {
tokenChars.push(currentChar)
}
} else {
// state has changed, so save token and start new token
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: Token
currentToken = new FlattenedToken(
currentChar,
nextLabelState,
this.latestRealToken
)
this.updateResult(poppedContext, result, currentToken)
// add to nesting level
nestedTokenStack.push(currentToken)
this.latestRealToken = null
break
case CharLevelState.rB:
case CharLevelState.rBr:
case CharLevelState.rPr:
if (currentLabelState !== CharLevelState.rC) {
let prevToken: Token = new BasicToken(
tokenChars.join(''),
currentLabelState
)
this.updateResult(poppedContext, result, prevToken)
let newToken: Token = new BasicToken(
currentChar,
nextLabelState
)
this.updateResult(poppedContext, result, newToken)
if (nestedTokenStack.length > 0) {
// remove from nesting level
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 === '"') {
nextState = [CharLevelState.lDqEnt, 0]
} else if (ent === ''') {
nextState = [CharLevelState.lSqEnt, 0]
} else {
let entToken: Token = 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) {
// in this case, don't include ')' as it is part of last token
tokenChars = []
} else if (currentLabelState === CharLevelState.lWs) {
// set whitespace token and then initial with currentChar
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
} // end if(currentChar)
currentChar = nextChar
} // end iteration over chars
if (this.timerOn) {
console.timeEnd('xplexer.analyse')
}
return result
}