public analyse()

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('&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)
        ) {
          // 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 === '&quot;') {
                nextState = [CharLevelState.lDqEnt, 0]
              } else if (ent === '&apos;') {
                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
  }