// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:dataparser

#include "qqmljslexer_p.h"
#include "qqmljsengine_p.h"
#include "qqmljskeywords_p.h"

#include <private/qqmljsdiagnosticmessage_p.h>
#include <private/qqmljsmemorypool_p.h>
#include <private/qlocale_tools_p.h>


#include <QtCore/qcoreapplication.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qdebug.h>
#include <QtCore/QScopedValueRollback>

#include <optional>

QT_BEGIN_NAMESPACE
using namespace QQmlJS;
using namespace Qt::StringLiterals;

static inline int regExpFlagFromChar(const QChar &ch)
{
    switch (ch.unicode()) {
    case 'g': return Lexer::RegExp_Global;
    case 'i': return Lexer::RegExp_IgnoreCase;
    case 'm': return Lexer::RegExp_Multiline;
    case 'u': return Lexer::RegExp_Unicode;
    case 'y': return Lexer::RegExp_Sticky;
    }
    return 0;
}

static inline unsigned char convertHex(ushort c)
{
    if (c >= '0' && c <= '9')
        return (c - '0');
    else if (c >= 'a' && c <= 'f')
        return (c - 'a' + 10);
    else
        return (c - 'A' + 10);
}

static inline QChar convertHex(QChar c1, QChar c2)
{
    return QChar((convertHex(c1.unicode()) << 4) + convertHex(c2.unicode()));
}

Lexer::Lexer(Engine *engine, LexMode lexMode)
    : _engine(engine), _lexMode(lexMode), _endPtr(nullptr), _qmlMode(true)
{
    if (engine)
        engine->setLexer(this);
}

bool Lexer::qmlMode() const
{
    return _qmlMode;
}

QString Lexer::code() const
{
    return _code;
}

std::optional<DiagnosticMessage> Lexer::illegalFileLengthError() const
{
    Q_ASSERT(_currentOffset >= 0);

    constexpr bool quint32IsBigger = sizeof(qsizetype) <= sizeof(quint32);
    using BiggerInt = std::conditional_t<quint32IsBigger, quint32, qsizetype>;
    using SmallerInt = std::conditional_t<!quint32IsBigger, quint32, qsizetype>;

    const BiggerInt codeLength = BiggerInt(_currentOffset) + BiggerInt(_code.size());
    const BiggerInt maxLength = BiggerInt(std::numeric_limits<SmallerInt>::max());
    if (codeLength < maxLength)
        return {};

    constexpr int limit = quint32IsBigger ? 2 : 4;
    return DiagnosticMessage{ u"File exceeds maximum length (%1GB)."_s.arg(limit), QtCriticalMsg,
                              SourceLocation{ 0, 1, 1, 1 } };
}

void Lexer::setCode(const QString &code, int lineno, bool qmlMode,
                    Lexer::CodeContinuation codeContinuation)
{
    if (codeContinuation == Lexer::CodeContinuation::Continue)
        _currentOffset += _code.size();
    else
        _currentOffset = 0;
    if (_engine)
        _engine->setCode(code);

    _qmlMode = qmlMode;
    _code = code;
    _skipLinefeed = false;

    _tokenText.clear();
    _tokenText.reserve(1024);
    _errorMessage.clear();
    _tokenSpell = QStringView();
    _rawString = QStringView();

    _codePtr = code.unicode();
    _endPtr = _codePtr + code.size();
    _tokenStartPtr = _codePtr;

    if (lineno >= 0)
        _currentLineNumber = lineno;
    _currentColumnNumber = 0;
    _tokenLine = _currentLineNumber;
    _tokenColumn = 0;
    _tokenLength = 0;

    if (codeContinuation == Lexer::CodeContinuation::Reset)
        _state = State {};
}

void Lexer::scanChar()
{
    if (_skipLinefeed) {
        Q_ASSERT(*_codePtr == u'\n');
        ++_codePtr;
        _skipLinefeed = false;
    }
    _state.currentChar = *_codePtr++;
    ++_currentColumnNumber;

    if (isLineTerminator()) {
        if (_state.currentChar == u'\r') {
            if (_codePtr < _endPtr && *_codePtr == u'\n')
                _skipLinefeed = true;
            _state.currentChar = u'\n';
        }
        ++_currentLineNumber;
        _currentColumnNumber = 0;
    }
}

QChar Lexer::peekChar()
{
    auto peekPtr = _codePtr;
    if (peekPtr < _endPtr)
        return *peekPtr;
    return QChar();
}

namespace {
inline bool isBinop(int tok)
{
    switch (tok) {
    case Lexer::T_AND:
    case Lexer::T_AND_AND:
    case Lexer::T_AND_EQ:
    case Lexer::T_DIVIDE_:
    case Lexer::T_DIVIDE_EQ:
    case Lexer::T_EQ:
    case Lexer::T_EQ_EQ:
    case Lexer::T_EQ_EQ_EQ:
    case Lexer::T_GE:
    case Lexer::T_GT:
    case Lexer::T_GT_GT:
    case Lexer::T_GT_GT_EQ:
    case Lexer::T_GT_GT_GT:
    case Lexer::T_GT_GT_GT_EQ:
    case Lexer::T_LE:
    case Lexer::T_LT:
    case Lexer::T_LT_LT:
    case Lexer::T_LT_LT_EQ:
    case Lexer::T_MINUS:
    case Lexer::T_MINUS_EQ:
    case Lexer::T_NOT_EQ:
    case Lexer::T_NOT_EQ_EQ:
    case Lexer::T_OR:
    case Lexer::T_OR_EQ:
    case Lexer::T_OR_OR:
    case Lexer::T_PLUS:
    case Lexer::T_PLUS_EQ:
    case Lexer::T_REMAINDER:
    case Lexer::T_REMAINDER_EQ:
    case Lexer::T_RETURN:
    case Lexer::T_STAR:
    case Lexer::T_STAR_EQ:
    case Lexer::T_XOR:
    case Lexer::T_XOR_EQ:
        return true;

    default:
        return false;
    }
}

int hexDigit(QChar c)
{
    if (c >= u'0' && c <= u'9')
        return c.unicode() - u'0';
    if (c >= u'a' && c <= u'f')
        return c.unicode() - u'a' + 10;
    if (c >= u'A' && c <= u'F')
        return c.unicode() - u'A' + 10;
    return -1;
}

int octalDigit(QChar c)
{
    if (c >= u'0' && c <= u'7')
        return c.unicode() - u'0';
    return -1;
}

} // anonymous namespace

int Lexer::lex()
{
    const int previousTokenKind = _state.tokenKind;
    int tokenKind;
    bool firstPass = true;

  again:
      tokenKind = T_ERROR;
      _tokenSpell = QStringView();
      _rawString = QStringView();
      if (firstPass && _state.stackToken == -1) {
          firstPass = false;
          if (_codePtr > _endPtr && _lexMode == LexMode::LineByLine && !_code.isEmpty())
              return T_EOL;

          if (_state.comments == CommentState::InMultilineComment) {
              scanChar();
              _tokenStartPtr = _codePtr - 1;
              _tokenLine = _currentLineNumber;
              _tokenColumn = _currentColumnNumber;
              while (_codePtr <= _endPtr) {
                  if (_state.currentChar == u'*') {
                      scanChar();
                      if (_state.currentChar == u'/') {
                          scanChar();
                          if (_engine) {
                              _engine->addComment(tokenOffset() + 2,
                                                  _codePtr - _tokenStartPtr - 1 - 4,
                                                  tokenStartLine(), tokenStartColumn() + 2);
                          }
                          tokenKind = T_COMMENT;
                          break;
                      }
                  } else {
                      scanChar();
                  }
              }
              if (tokenKind == T_ERROR)
                  tokenKind = T_PARTIAL_COMMENT;
          } else {
              // handle multiline continuation
              std::optional<ScanStringMode> scanMode;
              switch (previousTokenKind) {
              case T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL:
                  scanMode = ScanStringMode::SingleQuote;
                  break;
              case T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL:
                  scanMode = ScanStringMode::DoubleQuote;
                  break;
              case T_PARTIAL_TEMPLATE_HEAD:
                  scanMode = ScanStringMode::TemplateHead;
                  break;
              case T_PARTIAL_TEMPLATE_MIDDLE:
                  scanMode = ScanStringMode::TemplateContinuation;
                  break;
              default:
                  break;
              }
              if (scanMode) {
                  scanChar();
                  _tokenStartPtr = _codePtr - 1;
                  _tokenLine = _currentLineNumber;
                  _tokenColumn = _currentColumnNumber;
                  tokenKind = scanString(*scanMode);
              }
          }
      }
      if (tokenKind == T_ERROR)
          tokenKind = scanToken();
      _tokenLength = _codePtr - _tokenStartPtr - 1;
      switch (tokenKind) {
      // end of line and comments should not "overwrite" the old token type...
      case T_EOL:
          return tokenKind;
      case T_COMMENT:
          _state.comments = CommentState::HadComment;
          return tokenKind;
      case T_PARTIAL_COMMENT:
          _state.comments = CommentState::InMultilineComment;
          return tokenKind;
      default:
          _state.comments = CommentState::NoComment;
          break;
      }
      _state.tokenKind = tokenKind;

      _state.delimited = false;
      _state.restrictedKeyword = false;
      _state.followsClosingBrace = (previousTokenKind == T_RBRACE);

      // update the flags
      switch (_state.tokenKind) {
      case T_LBRACE:
          if (_state.bracesCount > 0)
              ++_state.bracesCount;
          Q_FALLTHROUGH();
      case T_SEMICOLON:
          _state.importState = ImportState::NoQmlImport;
          Q_FALLTHROUGH();
      case T_QUESTION:
      case T_COLON:
      case T_TILDE:
          _state.delimited = true;
          break;
      case T_AUTOMATIC_SEMICOLON:
      case T_AS:
          _state.importState = ImportState::NoQmlImport;
          Q_FALLTHROUGH();
      default:
          if (isBinop(_state.tokenKind))
              _state.delimited = true;
          break;

      case T_IMPORT:
          if (qmlMode() || (_state.handlingDirectives && previousTokenKind == T_DOT))
              _state.importState = ImportState::SawImport;
          if (isBinop(_state.tokenKind))
              _state.delimited = true;
          break;

      case T_IF:
      case T_FOR:
      case T_WHILE:
      case T_WITH:
          _state.parenthesesState = CountParentheses;
          _state.parenthesesCount = 0;
          break;

      case T_ELSE:
      case T_DO:
          _state.parenthesesState = BalancedParentheses;
          break;

      case T_CONTINUE:
      case T_BREAK:
      case T_RETURN:
      case T_YIELD:
      case T_THROW:
          _state.restrictedKeyword = true;
          break;
      case T_RBRACE:
          if (_state.bracesCount > 0)
              --_state.bracesCount;
          if (_state.bracesCount == 0)
              goto again;
      } // switch

    // update the parentheses state
      switch (_state.parenthesesState) {
      case IgnoreParentheses:
          break;

      case CountParentheses:
          if (_state.tokenKind == T_RPAREN) {
              --_state.parenthesesCount;
              if (_state.parenthesesCount == 0)
                  _state.parenthesesState = BalancedParentheses;
          } else if (_state.tokenKind == T_LPAREN) {
              ++_state.parenthesesCount;
          }
          break;

      case BalancedParentheses:
          if (_state.tokenKind != T_DO && _state.tokenKind != T_ELSE)
              _state.parenthesesState = IgnoreParentheses;
          break;
      } // switch

      return _state.tokenKind;
}

uint Lexer::decodeUnicodeEscapeCharacter(bool *ok)
{
    Q_ASSERT(_state.currentChar == u'u');
    scanChar(); // skip u
    constexpr int distanceFromFirstHexToLastHex = 3;
    if (_codePtr + distanceFromFirstHexToLastHex <= _endPtr && isHexDigit(_state.currentChar)) {
        uint codePoint = 0;
        for (int i = 0; i < 4; ++i) {
            int digit = hexDigit(_state.currentChar);
            if (digit < 0)
                goto error;
            codePoint *= 16;
            codePoint += digit;
            scanChar();
        }

        *ok = true;
        return codePoint;
    } else if (_codePtr < _endPtr && _state.currentChar == u'{') {
        scanChar(); // skip '{'
        uint codePoint = 0;
        if (!isHexDigit(_state.currentChar))
            // need at least one hex digit
            goto error;

        while (_codePtr <= _endPtr) {
            int digit = hexDigit(_state.currentChar);
            if (digit < 0)
                break;
            codePoint *= 16;
            codePoint += digit;
            if (codePoint > 0x10ffff)
                goto error;
            scanChar();
        }

        if (_state.currentChar != u'}')
            goto error;

        scanChar(); // skip '}'


        *ok = true;
        return codePoint;
    }

error:
    _state.errorCode = IllegalUnicodeEscapeSequence;
    _errorMessage = QCoreApplication::translate("QQmlParser", "Illegal unicode escape sequence");

    *ok = false;
    return 0;
}

QChar Lexer::decodeHexEscapeCharacter(bool *ok)
{
    if (isHexDigit(_codePtr[0]) && isHexDigit(_codePtr[1])) {
        scanChar();

        const QChar c1 = _state.currentChar;
        scanChar();

        const QChar c2 = _state.currentChar;
        scanChar();

        if (ok)
            *ok = true;

        return convertHex(c1, c2);
    }

    *ok = false;
    return QChar();
}

namespace QQmlJS {
QDebug operator<<(QDebug dbg, const Lexer &l)
{
    dbg << "{\n"
        << "  engine:" << qsizetype(l._engine) << ",\n"
        << "  lexMode:" << int(l._lexMode) << ",\n"
        << "  code.size:" << qsizetype(l._code.unicode()) << "+" << l._code.size() << ",\n"
        << "  endPtr: codePtr + " << (l._endPtr - l._codePtr) << ",\n"
        << "  qmlMode:" << l._qmlMode << ",\n"
        << "  staticIsKeyword:" << l._staticIsKeyword << ",\n"
        << "  currentLineNumber:" << l._currentLineNumber << ",\n"
        << "  currentColumnNumber:" << l._currentColumnNumber << ",\n"
        << "  currentOffset:" << l._currentOffset << ",\n"
        << "  tokenLength:" << l._tokenLength << ",\n"
        << "  tokenLine:" << l._tokenLine << ",\n"
        << "  tokenColumn:" << l._tokenColumn << ",\n"
        << "  tokenText:" << l._tokenText << ",\n"
        << "  skipLinefeed:" << l._skipLinefeed << ",\n"
        << "  errorMessage:" << l._errorMessage << ",\n"
        << "  tokenSpell:" << l._tokenSpell << ",\n"
        << "  rawString:" << l._rawString << ",\n";
    if (l._codePtr)
        dbg << "  codePtr: code.unicode()+" << (l._codePtr - l._code.unicode()) << ",\n";
    else
        dbg << "  codePtr: *null*,\n";
    if (l._tokenStartPtr)
        dbg << "  tokenStartPtr: codePtr " << (l._tokenStartPtr - l._codePtr) << ",\n";
    else
        dbg << "  tokenStartPtr: *null*,\n";
    dbg << "  state:" << l._state << "\n}";
    return dbg;
}
}

static inline bool isIdentifierStart(uint ch)
{
    // fast path for ascii
    if ((ch >= u'a' && ch <= u'z') ||
        (ch >= u'A' && ch <= u'Z') ||
        ch == u'$' || ch == u'_')
        return true;

    switch (QChar::category(ch)) {
    case QChar::Number_Letter:
    case QChar::Letter_Uppercase:
    case QChar::Letter_Lowercase:
    case QChar::Letter_Titlecase:
    case QChar::Letter_Modifier:
    case QChar::Letter_Other:
        return true;
    default:
        break;
    }
    return false;
}

static bool isIdentifierPart(uint ch)
{
    // fast path for ascii
    if ((ch >= u'a' && ch <= u'z') ||
        (ch >= u'A' && ch <= u'Z') ||
        (ch >= u'0' && ch <= u'9') ||
        ch == u'$' || ch == u'_' ||
        ch == 0x200c /* ZWNJ */ || ch == 0x200d /* ZWJ */)
        return true;

    switch (QChar::category(ch)) {
    case QChar::Mark_NonSpacing:
    case QChar::Mark_SpacingCombining:

    case QChar::Number_DecimalDigit:
    case QChar::Number_Letter:

    case QChar::Letter_Uppercase:
    case QChar::Letter_Lowercase:
    case QChar::Letter_Titlecase:
    case QChar::Letter_Modifier:
    case QChar::Letter_Other:

    case QChar::Punctuation_Connector:
        return true;
    default:
        break;
    }
    return false;
}

int Lexer::scanToken()
{
    if (_state.stackToken != -1) {
        int tk = _state.stackToken;
        _state.stackToken = -1;
        return tk;
    }

    if (_state.bracesCount == 0) {
        // we're inside a Template string
        return scanString(TemplateContinuation);
    }

    if (_state.comments == CommentState::NoComment)
        _state.terminator = false;

again:
    _state.validTokenText = false;

    while (_state.currentChar.isSpace()) {
        if (isLineTerminator()) {
            bool isAtEnd = (_codePtr + (_skipLinefeed ? 1 : 0)) == _endPtr;
            if (_state.restrictedKeyword) {
                // automatic semicolon insertion
                _tokenLine = _currentLineNumber;
                _tokenColumn = _currentColumnNumber;
                _tokenStartPtr = _codePtr - 1;
                return T_SEMICOLON;
            } else if (_lexMode == LexMode::WholeCode || !isAtEnd) {
                _state.terminator = true;
                syncProhibitAutomaticSemicolon();
            } // else we will do the previous things at the start of next line...
        }

        scanChar();
    }

    _tokenStartPtr = _codePtr - 1;
    _tokenLine = _currentLineNumber;
    _tokenColumn = _currentColumnNumber;

    if (_codePtr >= _endPtr) {
        if (_lexMode == LexMode::LineByLine) {
            if (!_code.isEmpty()) {
                _state.currentChar = *(_codePtr - 2);
                return T_EOL;
            } else {
                return EOF_SYMBOL;
            }
        } else if (_codePtr > _endPtr) {
            return EOF_SYMBOL;
        }
    }

    const QChar ch = _state.currentChar;
    scanChar();

    switch (ch.unicode()) {
    case u'~': return T_TILDE;
    case u'}': return T_RBRACE;

    case u'|':
        if (_state.currentChar == u'|') {
            scanChar();
            return T_OR_OR;
        } else if (_state.currentChar == u'=') {
            scanChar();
            return T_OR_EQ;
        }
        return T_OR;

    case u'{': return T_LBRACE;

    case u'^':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_XOR_EQ;
        }
        return T_XOR;

    case u']': return T_RBRACKET;
    case u'[': return T_LBRACKET;
    case u'?': {
        if (_state.currentChar == u'?') {
            scanChar();
            return T_QUESTION_QUESTION;
        }
        if (_state.currentChar == u'.' && !peekChar().isDigit()) {
            scanChar();
            return T_QUESTION_DOT;
        }

        return T_QUESTION;
    }

    case u'>':
        if (_state.currentChar == u'>') {
            scanChar();
            if (_state.currentChar == u'>') {
                scanChar();
                if (_state.currentChar == u'=') {
                    scanChar();
                    return T_GT_GT_GT_EQ;
                }
                return T_GT_GT_GT;
            } else if (_state.currentChar == u'=') {
                scanChar();
                return T_GT_GT_EQ;
            }
            return T_GT_GT;
        } else if (_state.currentChar == u'=') {
            scanChar();
            return T_GE;
        }
        return T_GT;

    case u'=':
        if (_state.currentChar == u'=') {
            scanChar();
            if (_state.currentChar == u'=') {
                scanChar();
                return T_EQ_EQ_EQ;
            }
            return T_EQ_EQ;
        } else if (_state.currentChar == u'>') {
            scanChar();
            return T_ARROW;
        }
        return T_EQ;

    case u'<':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_LE;
        } else if (_state.currentChar == u'<') {
            scanChar();
            if (_state.currentChar == u'=') {
                scanChar();
                return T_LT_LT_EQ;
            }
            return T_LT_LT;
        }
        return T_LT;

    case u';': return T_SEMICOLON;
    case u':': return T_COLON;

    case u'/':
        switch (_state.currentChar.unicode()) {
        case u'*':
            scanChar();
            while (_codePtr <= _endPtr) {
                if (_state.currentChar == u'*') {
                    scanChar();
                    if (_state.currentChar == u'/') {
                        scanChar();
                        if (_engine) {
                            _engine->addComment(tokenOffset() + 2,
                                                _codePtr - _tokenStartPtr - 1 - 4, tokenStartLine(),
                                                tokenStartColumn() + 2);
                        }
                        if (_lexMode == LexMode::LineByLine)
                            return T_COMMENT;
                        else
                            goto again;
                    }
                } else {
                    scanChar();
                }
            }
            if (_lexMode == LexMode::LineByLine)
                return T_PARTIAL_COMMENT;
            else
                goto again;
        case u'/':
            while (_codePtr <= _endPtr && !isLineTerminator()) {
                scanChar();
            }
            if (_engine) {
                _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 2,
                                    tokenStartLine(), tokenStartColumn() + 2);
            }
            if (_lexMode == LexMode::LineByLine)
                return T_COMMENT;
            else
                goto again;
        case u'=':
            scanChar();
            return T_DIVIDE_EQ;
        default:
            return T_DIVIDE_;
        }
    case u'.':
        if (_state.importState == ImportState::SawImport)
            return T_DOT;
        if (isDecimalDigit(_state.currentChar.unicode()))
            return scanNumber(ch);
        if (_state.currentChar == u'.') {
            scanChar();
            if (_state.currentChar == u'.') {
                scanChar();
                return T_ELLIPSIS;
            } else {
                _state.errorCode = IllegalCharacter;
                _errorMessage = QCoreApplication::translate("QQmlParser", "Unexpected token '.'");
                return T_ERROR;
            }
        }
        return T_DOT;

    case u'-':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_MINUS_EQ;
        } else if (_state.currentChar == u'-') {
            scanChar();

            if (_state.terminator && !_state.delimited && !_state.prohibitAutomaticSemicolon
                && _state.tokenKind != T_LPAREN) {
                _state.stackToken = T_MINUS_MINUS;
                return T_SEMICOLON;
            }

            return T_MINUS_MINUS;
        }
        return T_MINUS;

    case u',': return T_COMMA;

    case u'+':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_PLUS_EQ;
        } else if (_state.currentChar == u'+') {
            scanChar();

            if (_state.terminator && !_state.delimited && !_state.prohibitAutomaticSemicolon
                && _state.tokenKind != T_LPAREN) {
                _state.stackToken = T_PLUS_PLUS;
                return T_SEMICOLON;
            }

            return T_PLUS_PLUS;
        }
        return T_PLUS;

    case u'*':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_STAR_EQ;
        } else if (_state.currentChar == u'*') {
            scanChar();
            if (_state.currentChar == u'=') {
                scanChar();
                return T_STAR_STAR_EQ;
            }
            return T_STAR_STAR;
        }
        return T_STAR;

    case u')': return T_RPAREN;
    case u'(': return T_LPAREN;

    case u'@': return T_AT;

    case u'&':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_AND_EQ;
        } else if (_state.currentChar == u'&') {
            scanChar();
            return T_AND_AND;
        }
        return T_AND;

    case u'%':
        if (_state.currentChar == u'=') {
            scanChar();
            return T_REMAINDER_EQ;
        }
        return T_REMAINDER;

    case u'!':
        if (_state.currentChar == u'=') {
            scanChar();
            if (_state.currentChar == u'=') {
                scanChar();
                return T_NOT_EQ_EQ;
            }
            return T_NOT_EQ;
        }
        return T_NOT;

    case u'`':
        _state.outerTemplateBraceCount.push(_state.bracesCount);
        Q_FALLTHROUGH();
    case u'\'':
    case u'"':
        return scanString(ScanStringMode(ch.unicode()));
    case u'0':
    case u'1':
    case u'2':
    case u'3':
    case u'4':
    case u'5':
    case u'6':
    case u'7':
    case u'8':
    case u'9':
        if (_state.importState == ImportState::SawImport)
            return scanVersionNumber(ch);
        else
            return scanNumber(ch);

    case '#':
        if (_currentLineNumber == 1 && _currentColumnNumber == 2) {
            // shebang support
            while (_codePtr <= _endPtr && !isLineTerminator()) {
                scanChar();
            }
            if (_engine) {
                _engine->addComment(tokenOffset(), _codePtr - _tokenStartPtr - 1, tokenStartLine(),
                                    tokenStartColumn());
            }
            if (_lexMode == LexMode::LineByLine)
                return T_COMMENT;
            else
                goto again;
        }
        Q_FALLTHROUGH();

    default: {
        uint c = ch.unicode();
        bool identifierWithEscapeChars = false;
        if (QChar::isHighSurrogate(c) && QChar::isLowSurrogate(_state.currentChar.unicode())) {
            c = QChar::surrogateToUcs4(ushort(c), _state.currentChar.unicode());
            scanChar();
        } else if (c == '\\' && _state.currentChar == u'u') {
            identifierWithEscapeChars = true;
            bool ok = false;
            c = decodeUnicodeEscapeCharacter(&ok);
            if (!ok)
                return T_ERROR;
        }
        if (isIdentifierStart(c)) {
            if (identifierWithEscapeChars) {
                _tokenText.resize(0);
                if (QChar::requiresSurrogates(c)) {
                    _tokenText += QChar(QChar::highSurrogate(c));
                    _tokenText += QChar(QChar::lowSurrogate(c));
                } else {
                    _tokenText += QChar(c);
                }
                _state.validTokenText = true;
            }
            while (_codePtr <= _endPtr) {
                c = _state.currentChar.unicode();
                if (QChar::isHighSurrogate(c) && QChar::isLowSurrogate(_codePtr->unicode())) {
                    scanChar();
                    c = QChar::surrogateToUcs4(ushort(c), _state.currentChar.unicode());
                } else if (_state.currentChar == u'\\' && _codePtr[0] == u'u') {
                    if (!identifierWithEscapeChars) {
                        identifierWithEscapeChars = true;
                        _tokenText.resize(0);
                        _tokenText.insert(0, _tokenStartPtr, _codePtr - _tokenStartPtr - 1);
                        _state.validTokenText = true;
                    }

                    scanChar(); // skip '\\'
                    bool ok = false;
                    c = decodeUnicodeEscapeCharacter(&ok);
                    if (!ok)
                        return T_ERROR;

                    if (!isIdentifierPart(c))
                        break;

                    if (QChar::requiresSurrogates(c)) {
                        _tokenText += QChar(QChar::highSurrogate(c));
                        _tokenText += QChar(QChar::lowSurrogate(c));
                    } else {
                        _tokenText += QChar(c);
                    }
                    continue;
                }

                if (!isIdentifierPart(c))
                    break;

                if (identifierWithEscapeChars) {
                    if (QChar::requiresSurrogates(c)) {
                        _tokenText += QChar(QChar::highSurrogate(c));
                        _tokenText += QChar(QChar::lowSurrogate(c));
                    } else {
                        _tokenText += QChar(c);
                    }
                }
                scanChar();
            }

            const auto token = QStringView(_tokenStartPtr, _codePtr - 1);
            _tokenLength = token.size();
            int kind = T_IDENTIFIER;

            if (!identifierWithEscapeChars)
                kind = classify(token, parseModeFlags());

            if (_engine) {
                if (kind == T_IDENTIFIER && identifierWithEscapeChars)
                    _tokenSpell = _engine->newStringRef(_tokenText);
                else
                    _tokenSpell = _engine->midRef(_tokenStartPtr - _code.unicode(), _tokenLength);
            }

            return kind;
        }
        }

        break;
    }

    return T_ERROR;
}

int Lexer::scanString(ScanStringMode mode)
{
    const char16_t quote = mode == TemplateContinuation ? TemplateHead : mode;
    // we actually use T_STRING_LITERAL also for multiline strings, should we want to
    // change that we should set it to:
    //     _state.tokenKind == T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL ||
    //     _state.tokenKind == T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL
    // here and uncomment the multilineStringLiteral = true below.
    bool multilineStringLiteral = false;

    const QChar *startCode = _codePtr - 1;
    // in case we just parsed a \r, we need to reset this flag to get things working
    // correctly in the loop below and afterwards
    _skipLinefeed = false;
    bool first = true;

    if (_engine) {
        while (_codePtr <= _endPtr) {
            if (isLineTerminator()) {
                if ((quote == u'`' || qmlMode())) {
                    if (first)
                        --_currentLineNumber; // will be read again in scanChar()
                    break;
                }
                _state.errorCode = IllegalCharacter;
                _errorMessage = QCoreApplication::translate("QQmlParser",
                                                            "Stray newline in string literal");
                return T_ERROR;
            } else if (_state.currentChar == u'\\') {
                break;
            } else if (_state.currentChar == u'$' && quote == u'`') {
                break;
            } else if (_state.currentChar == quote) {
                _tokenSpell =
                        _engine->midRef(startCode - _code.unicode(), _codePtr - startCode - 1);
                _rawString = _tokenSpell;
                scanChar();

                if (quote == u'`')
                    _state.bracesCount = _state.outerTemplateBraceCount.pop();
                if (mode == TemplateHead)
                    return T_NO_SUBSTITUTION_TEMPLATE;
                else if (mode == TemplateContinuation)
                    return T_TEMPLATE_TAIL;
                else if (multilineStringLiteral)
                    return T_MULTILINE_STRING_LITERAL;
                else
                    return T_STRING_LITERAL;
            }
            // don't use scanChar() here, that would transform \r sequences and the midRef() call would create the wrong result
            _state.currentChar = *_codePtr++;
            ++_currentColumnNumber;
            first = false;
        }
    }

    // rewind by one char, so things gets scanned correctly
    --_codePtr;
    --_currentColumnNumber;

    _state.validTokenText = true;
    _tokenText = QString(startCode, _codePtr - startCode);

    auto setRawString = [&](const QChar *end) {
        QString raw(startCode, end - startCode - 1);
        raw.replace(QLatin1String("\r\n"), QLatin1String("\n"));
        raw.replace(u'\r', u'\n');
        _rawString = _engine->newStringRef(raw);
    };

    scanChar();

    while (_codePtr <= _endPtr) {
        if (_state.currentChar == quote) {
            scanChar();

            if (_engine) {
                _tokenSpell = _engine->newStringRef(_tokenText);
                if (quote == u'`')
                    setRawString(_codePtr - 1);
            }

            if (quote == u'`')
                _state.bracesCount = _state.outerTemplateBraceCount.pop();

            if (mode == TemplateContinuation)
                return T_TEMPLATE_TAIL;
            else if (mode == TemplateHead)
                return T_NO_SUBSTITUTION_TEMPLATE;

            return multilineStringLiteral ? T_MULTILINE_STRING_LITERAL : T_STRING_LITERAL;
        } else if (quote == u'`' && _state.currentChar == u'$' && *_codePtr == u'{') {
            scanChar();
            scanChar();
            _state.bracesCount = 1;
            if (_engine) {
                _tokenSpell = _engine->newStringRef(_tokenText);
                setRawString(_codePtr - 2);
            }

            return (mode == TemplateHead ? T_TEMPLATE_HEAD : T_TEMPLATE_MIDDLE);
        } else if (_state.currentChar == u'\\') {
            scanChar();
            if (_codePtr > _endPtr) {
                _state.errorCode = IllegalEscapeSequence;
                _errorMessage = QCoreApplication::translate(
                        "QQmlParser", "End of file reached at escape sequence");
                return T_ERROR;
            }

            QChar u;

            switch (_state.currentChar.unicode()) {
            // unicode escape sequence
            case u'u': {
                bool ok = false;
                uint codePoint = decodeUnicodeEscapeCharacter(&ok);
                if (!ok)
                    return T_ERROR;
                if (QChar::requiresSurrogates(codePoint)) {
                    // need to use a surrogate pair
                    _tokenText += QChar(QChar::highSurrogate(codePoint));
                    u = QChar::lowSurrogate(codePoint);
                } else {
                    u = QChar(codePoint);
                }
            } break;

            // hex escape sequence
            case u'x': {
                bool ok = false;
                u = decodeHexEscapeCharacter(&ok);
                if (!ok) {
                    _state.errorCode = IllegalHexadecimalEscapeSequence;
                    _errorMessage = QCoreApplication::translate(
                            "QQmlParser", "Illegal hexadecimal escape sequence");
                    return T_ERROR;
                }
            } break;

            // single character escape sequence
            case u'\\': u = u'\\'; scanChar(); break;
            case u'\'': u = u'\''; scanChar(); break;
            case u'\"': u = u'\"'; scanChar(); break;
            case u'b':  u = u'\b'; scanChar(); break;
            case u'f':  u = u'\f'; scanChar(); break;
            case u'n':  u = u'\n'; scanChar(); break;
            case u'r':  u = u'\r'; scanChar(); break;
            case u't':  u = u'\t'; scanChar(); break;
            case u'v':  u = u'\v'; scanChar(); break;

            case u'0':
                if (!_codePtr->isDigit()) {
                    scanChar();
                    u = u'\0';
                    break;
                }
                Q_FALLTHROUGH();
            case u'1':
            case u'2':
            case u'3':
            case u'4':
            case u'5':
            case u'6':
            case u'7':
            case u'8':
            case u'9':
                _state.errorCode = IllegalEscapeSequence;
                _errorMessage = QCoreApplication::translate(
                        "QQmlParser", "Octal escape sequences are not allowed");
                return T_ERROR;

            case u'\r':
            case u'\n':
            case 0x2028u:
            case 0x2029u:
                // uncomment the following to use T_MULTILINE_STRING_LITERAL
                // multilineStringLiteral = true;
                scanChar();
                continue;

            default:
                // non escape character
                u = _state.currentChar;
                scanChar();
            }

            _tokenText += u;
        } else {
            _tokenText += _state.currentChar;
            scanChar();
        }
    }
    if (_lexMode == LexMode::LineByLine && !_code.isEmpty()) {
        if (mode == TemplateContinuation)
            return T_PARTIAL_TEMPLATE_MIDDLE;
        else if (mode == TemplateHead)
            return T_PARTIAL_TEMPLATE_HEAD;
        else if (mode == SingleQuote)
            return T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL;
        return T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL;
    }
    _state.errorCode = UnclosedStringLiteral;
    _errorMessage = QCoreApplication::translate("QQmlParser", "Unclosed string at end of line");
    return T_ERROR;
}

int Lexer::scanNumber(QChar ch)
{
    auto scanOptionalNumericSeparator = [this](auto isNextCharacterValid){
        if (_state.currentChar == u'_') {
            if (peekChar() == u'_') {
                _state.errorCode = IllegalNumber;
                _errorMessage = QCoreApplication::translate(
                    "QQmlParser",
                    "There can be at most one numeric separator between digits"
                );
                return false;
            }

            if (!isNextCharacterValid()) {
                _state.errorCode = IllegalNumber;
                _errorMessage = QCoreApplication::translate(
                    "QQmlParser",
                    "A trailing numeric separator is not allowed in numeric literals"
                );
                return false;
            }

            scanChar();
        }

        return true;
    };

    if (ch == u'0') {
        if (_state.currentChar == u'x' || _state.currentChar == u'X') {
            ch = _state.currentChar; // remember the x or X to use it in the error message below.

            // parse hex integer literal
            scanChar(); // consume 'x'

            if (!isHexDigit(_state.currentChar)) {
                _state.errorCode = IllegalNumber;
                _errorMessage = QCoreApplication::translate(
                                        "QQmlParser",
                                        "At least one hexadecimal digit is required after '0%1'")
                                        .arg(ch);
                return T_ERROR;
            }

            double d = 0.;
            while (1) {
                int digit = ::hexDigit(_state.currentChar);
                if (digit < 0)
                    break;
                d *= 16;
                d += digit;
                scanChar();

                if (!scanOptionalNumericSeparator([this](){ return isHexDigit(peekChar()); }))
                    return T_ERROR;
            }

            _state.tokenValue = d;
            return T_NUMERIC_LITERAL;
        } else if (_state.currentChar == u'o' || _state.currentChar == u'O') {
            ch = _state.currentChar; // remember the o or O to use it in the error message below.

            // parse octal integer literal
            scanChar(); // consume 'o'

            if (!isOctalDigit(_state.currentChar.unicode())) {
                _state.errorCode = IllegalNumber;
                _errorMessage =
                        QCoreApplication::translate(
                                "QQmlParser", "At least one octal digit is required after '0%1'")
                                .arg(ch);
                return T_ERROR;
            }

            double d = 0.;
            while (1) {
                int digit = ::octalDigit(_state.currentChar);
                if (digit < 0)
                    break;
                d *= 8;
                d += digit;
                scanChar();

                if (!scanOptionalNumericSeparator([this](){
                    return isOctalDigit(peekChar().unicode());
                })) {
                    return T_ERROR;
                }
            }

            _state.tokenValue = d;
            return T_NUMERIC_LITERAL;
        } else if (_state.currentChar == u'b' || _state.currentChar == u'B') {
            ch = _state.currentChar; // remember the b or B to use it in the error message below.

            // parse binary integer literal
            scanChar(); // consume 'b'

            if (_state.currentChar.unicode() != u'0' && _state.currentChar.unicode() != u'1') {
                _state.errorCode = IllegalNumber;
                _errorMessage =
                        QCoreApplication::translate(
                                "QQmlParser", "At least one binary digit is required after '0%1'")
                                .arg(ch);
                return T_ERROR;
            }

            double d = 0.;
            while (1) {
                int digit = 0;
                if (_state.currentChar.unicode() == u'1')
                    digit = 1;
                else if (_state.currentChar.unicode() != u'0')
                    break;
                d *= 2;
                d += digit;
                scanChar();

                if (!scanOptionalNumericSeparator([this](){
                    return peekChar().unicode() == u'0' || peekChar().unicode() == u'1';
                })) {
                    return T_ERROR;
                }
            }

            _state.tokenValue = d;
            return T_NUMERIC_LITERAL;
        } else if (_state.currentChar.isDigit() && !qmlMode()) {
            _state.errorCode = IllegalCharacter;
            _errorMessage = QCoreApplication::translate("QQmlParser",
                                                        "Decimal numbers can't start with '0'");
            return T_ERROR;
        }
    }

    // decimal integer literal
    QVarLengthArray<char,32> chars;
    chars.append(ch.unicode());

    if (ch != u'.') {
        if (!scanOptionalNumericSeparator([this](){ return peekChar().isDigit(); }))
            return T_ERROR;

        while (_state.currentChar.isDigit()) {
            chars.append(_state.currentChar.unicode());
            scanChar(); // consume the digit

            if (!scanOptionalNumericSeparator([this](){ return peekChar().isDigit(); }))
                return T_ERROR;
        }

        if (_state.currentChar == u'.') {
            chars.append(_state.currentChar.unicode());
            scanChar(); // consume `.'
        }
    }

    while (_state.currentChar.isDigit()) {
        chars.append(_state.currentChar.unicode());
        scanChar();

        if (!scanOptionalNumericSeparator([this](){ return peekChar().isDigit(); }))
            return T_ERROR;
    }

    if (_state.currentChar == u'e' || _state.currentChar == u'E') {
        if (_codePtr[0].isDigit()
            || ((_codePtr[0] == u'+' || _codePtr[0] == u'-') && _codePtr[1].isDigit())) {

            chars.append(_state.currentChar.unicode());
            scanChar(); // consume `e'

            if (_state.currentChar == u'+' || _state.currentChar == u'-') {
                chars.append(_state.currentChar.unicode());
                scanChar(); // consume the sign
            }

            while (_state.currentChar.isDigit()) {
                chars.append(_state.currentChar.unicode());
                scanChar();

                if (!scanOptionalNumericSeparator([this](){ return peekChar().isDigit(); }))
                    return T_ERROR;
            }
        }
    }

    const char *begin = chars.constData();
    const char *end = nullptr;
    bool ok = false;

    _state.tokenValue = qstrntod(begin, chars.size(), &end, &ok);

    if (end - begin != chars.size()) {
        _state.errorCode = IllegalExponentIndicator;
        _errorMessage =
                QCoreApplication::translate("QQmlParser", "Illegal syntax for exponential number");
        return T_ERROR;
    }

    return T_NUMERIC_LITERAL;
}

int Lexer::scanVersionNumber(QChar ch)
{
    if (ch == u'0') {
        _state.tokenValue = 0;
        return T_VERSION_NUMBER;
    }

    int acc = 0;
    acc += ch.digitValue();

    while (_state.currentChar.isDigit()) {
        acc *= 10;
        acc += _state.currentChar.digitValue();
        scanChar(); // consume the digit
    }

    _state.tokenValue = acc;
    return T_VERSION_NUMBER;
}

bool Lexer::scanRegExp(RegExpBodyPrefix prefix)
{
    _tokenText.resize(0);
    _state.validTokenText = true;
    _state.patternFlags = 0;

    if (prefix == EqualPrefix)
        _tokenText += u'=';

    while (true) {
        switch (_state.currentChar.unicode()) {
        case u'/':
            scanChar();

            // scan the flags
            _state.patternFlags = 0;
            while (isIdentLetter(_state.currentChar)) {
                int flag = regExpFlagFromChar(_state.currentChar);
                if (flag == 0 || _state.patternFlags & flag) {
                    _errorMessage = QCoreApplication::translate(
                                            "QQmlParser", "Invalid regular expression flag '%0'")
                                            .arg(QChar(_state.currentChar));
                    return false;
                }
                _state.patternFlags |= flag;
                scanChar();
            }

            _tokenLength = _codePtr - _tokenStartPtr - 1;
            return true;

        case u'\\':
            // regular expression backslash sequence
            _tokenText += _state.currentChar;
            scanChar();

            if (_codePtr > _endPtr || isLineTerminator()) {
                _errorMessage = QCoreApplication::translate(
                        "QQmlParser", "Unterminated regular expression backslash sequence");
                return false;
            }

            _tokenText += _state.currentChar;
            scanChar();
            break;

        case u'[':
            // regular expression class
            _tokenText += _state.currentChar;
            scanChar();

            while (_codePtr <= _endPtr && !isLineTerminator()) {
                if (_state.currentChar == u']')
                    break;
                else if (_state.currentChar == u'\\') {
                    // regular expression backslash sequence
                    _tokenText += _state.currentChar;
                    scanChar();

                    if (_codePtr > _endPtr || isLineTerminator()) {
                        _errorMessage = QCoreApplication::translate(
                                "QQmlParser", "Unterminated regular expression backslash sequence");
                        return false;
                    }

                    _tokenText += _state.currentChar;
                    scanChar();
                } else {
                    _tokenText += _state.currentChar;
                    scanChar();
                }
            }

            if (_state.currentChar != u']') {
                _errorMessage = QCoreApplication::translate(
                        "QQmlParser", "Unterminated regular expression class");
                return false;
            }

            _tokenText += _state.currentChar;
            scanChar(); // skip ]
            break;

        default:
            if (_codePtr > _endPtr || isLineTerminator()) {
                _errorMessage = QCoreApplication::translate(
                        "QQmlParser", "Unterminated regular expression literal");
                return false;
            } else {
                _tokenText += _state.currentChar;
                scanChar();
            }
        } // switch
    } // while

    return false;
}

bool Lexer::isLineTerminator() const
{
    const ushort unicode = _state.currentChar.unicode();
    return unicode == 0x000Au
            || unicode == 0x000Du
            || unicode == 0x2028u
            || unicode == 0x2029u;
}

unsigned Lexer::isLineTerminatorSequence() const
{
    switch (_state.currentChar.unicode()) {
    case 0x000Au:
    case 0x2028u:
    case 0x2029u:
        return 1;
    case 0x000Du:
        if (_codePtr->unicode() == 0x000Au)
            return 2;
        else
            return 1;
    default:
        return 0;
    }
}

bool Lexer::isIdentLetter(QChar ch)
{
    // ASCII-biased, since all reserved words are ASCII, aand hence the
    // bulk of content to be parsed.
    if ((ch >= u'a' && ch <= u'z')
            || (ch >= u'A' && ch <= u'Z')
            || ch == u'$' || ch == u'_')
        return true;
    if (ch.unicode() < 128)
        return false;
    return ch.isLetterOrNumber();
}

bool Lexer::isDecimalDigit(ushort c)
{
    return (c >= u'0' && c <= u'9');
}

bool Lexer::isHexDigit(QChar c)
{
    return ((c >= u'0' && c <= u'9')
            || (c >= u'a' && c <= u'f')
            || (c >= u'A' && c <= u'F'));
}

bool Lexer::isOctalDigit(ushort c)
{
    return (c >= u'0' && c <= u'7');
}

QString Lexer::tokenText() const
{
    if (_state.validTokenText)
        return _tokenText;

    if (_state.tokenKind == T_STRING_LITERAL)
        return QString(_tokenStartPtr + 1, _tokenLength - 2);

    return QString(_tokenStartPtr, _tokenLength);
}

Lexer::Error Lexer::errorCode() const
{
    return _state.errorCode;
}

QString Lexer::errorMessage() const
{
    return _errorMessage;
}

void Lexer::syncProhibitAutomaticSemicolon()
{
    if (_state.parenthesesState == BalancedParentheses) {
        // we have seen something like "if (foo)", which means we should
        // never insert an automatic semicolon at this point, since it would
        // then be expanded into an empty statement (ECMA-262 7.9.1)
        _state.prohibitAutomaticSemicolon = true;
        _state.parenthesesState = IgnoreParentheses;
    } else {
        _state.prohibitAutomaticSemicolon = false;
    }
}

bool Lexer::prevTerminator() const
{
    return _state.terminator;
}

bool Lexer::followsClosingBrace() const
{
    return _state.followsClosingBrace;
}

bool Lexer::canInsertAutomaticSemicolon(int token) const
{
    return token == T_RBRACE || token == EOF_SYMBOL || _state.terminator
            || _state.followsClosingBrace;
}

static const int uriTokens[] = {
    QQmlJSGrammar::T_IDENTIFIER,
    QQmlJSGrammar::T_PROPERTY,
    QQmlJSGrammar::T_SIGNAL,
    QQmlJSGrammar::T_READONLY,
    QQmlJSGrammar::T_ON,
    QQmlJSGrammar::T_BREAK,
    QQmlJSGrammar::T_CASE,
    QQmlJSGrammar::T_CATCH,
    QQmlJSGrammar::T_CONTINUE,
    QQmlJSGrammar::T_DEFAULT,
    QQmlJSGrammar::T_DELETE,
    QQmlJSGrammar::T_DO,
    QQmlJSGrammar::T_ELSE,
    QQmlJSGrammar::T_FALSE,
    QQmlJSGrammar::T_FINAL,
    QQmlJSGrammar::T_FINALLY,
    QQmlJSGrammar::T_FOR,
    QQmlJSGrammar::T_FUNCTION,
    QQmlJSGrammar::T_IF,
    QQmlJSGrammar::T_IN,
    QQmlJSGrammar::T_OF,
    QQmlJSGrammar::T_INSTANCEOF,
    QQmlJSGrammar::T_NEW,
    QQmlJSGrammar::T_NULL,
    QQmlJSGrammar::T_RETURN,
    QQmlJSGrammar::T_SWITCH,
    QQmlJSGrammar::T_THIS,
    QQmlJSGrammar::T_THROW,
    QQmlJSGrammar::T_TRUE,
    QQmlJSGrammar::T_TRY,
    QQmlJSGrammar::T_TYPEOF,
    QQmlJSGrammar::T_VAR,
    QQmlJSGrammar::T_VOID,
    QQmlJSGrammar::T_WHILE,
    QQmlJSGrammar::T_CONST,
    QQmlJSGrammar::T_DEBUGGER,
    QQmlJSGrammar::T_ENUM,
    QQmlJSGrammar::T_PACKAGE,
    QQmlJSGrammar::T_ABSTRACT,
    QQmlJSGrammar::T_INTERFACE,
    QQmlJSGrammar::T_IMPLEMENTS,
    QQmlJSGrammar::T_PUBLIC,
    QQmlJSGrammar::T_PROTECTED,
    QQmlJSGrammar::T_PRIVATE,
    QQmlJSGrammar::T_NATIVE,
    QQmlJSGrammar::T_VOLATILE,
    QQmlJSGrammar::T_TRANSIENT,
    QQmlJSGrammar::T_SYNCHRONIZED,
    QQmlJSGrammar::T_THROWS,
    QQmlJSGrammar::T_WITH,

    QQmlJSGrammar::EOF_SYMBOL
};
static inline bool isUriToken(int token)
{
    const int *current = uriTokens;
    while (*current != QQmlJSGrammar::EOF_SYMBOL) {
        if (*current == token)
            return true;
        ++current;
    }
    return false;
}

bool Lexer::scanDirectives(Directives *directives, DiagnosticMessage *error)
{
    auto setError = [error, this](QString message) {
        error->message = std::move(message);
        error->loc.startLine = tokenStartLine();
        error->loc.startColumn = tokenStartColumn();
    };

    QScopedValueRollback<bool> directivesGuard(_state.handlingDirectives, true);
    Q_ASSERT(!_qmlMode);

    lex(); // fetch the first token

    if (_state.tokenKind != T_DOT)
        return true;

    do {
        const int lineNumber = tokenStartLine();
        const int column = tokenStartColumn();

        lex(); // skip T_DOT

        if (!(_state.tokenKind == T_IDENTIFIER || _state.tokenKind == T_IMPORT))
            return true; // expected a valid QML/JS directive

        const QString directiveName = tokenText();

        if (! (directiveName == QLatin1String("pragma") ||
               directiveName == QLatin1String("import"))) {
            setError(QCoreApplication::translate("QQmlParser", "Syntax error"));
            return false; // not a valid directive name
        }

        // it must be a pragma or an import directive.
        if (directiveName == QLatin1String("pragma")) {
            // .pragma library
            if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library"))) {
                setError(QCoreApplication::translate("QQmlParser", "Syntax error"));
                return false; // expected `library
            }

            // we found a .pragma library directive
            directives->pragmaLibrary();

        } else {
            Q_ASSERT(directiveName == QLatin1String("import"));
            lex(); // skip .import

            QString pathOrUri;
            QString version;
            bool fileImport = false; // file or uri import

            if (_state.tokenKind == T_STRING_LITERAL) {
                // .import T_STRING_LITERAL as T_IDENTIFIER

                fileImport = true;
                pathOrUri = tokenText();

                if (!pathOrUri.endsWith(QLatin1String("js"))) {
                    setError(QCoreApplication::translate("QQmlParser","Imported file must be a script"));
                    return false;
                }
                lex();

            } else if (_state.tokenKind == T_IDENTIFIER) {
                // .import T_IDENTIFIER (. T_IDENTIFIER)* (T_VERSION_NUMBER (. T_VERSION_NUMBER)?)? as T_IDENTIFIER
                while (true) {
                    if (!isUriToken(_state.tokenKind)) {
                        setError(QCoreApplication::translate("QQmlParser","Invalid module URI"));
                        return false;
                    }

                    pathOrUri.append(tokenText());

                    lex();
                    if (tokenStartLine() != lineNumber) {
                        setError(QCoreApplication::translate("QQmlParser","Invalid module URI"));
                        return false;
                    }
                    if (_state.tokenKind != QQmlJSGrammar::T_DOT)
                        break;

                    pathOrUri.append(u'.');

                    lex();
                    if (tokenStartLine() != lineNumber) {
                        setError(QCoreApplication::translate("QQmlParser","Invalid module URI"));
                        return false;
                    }
                }

                if (_state.tokenKind == T_VERSION_NUMBER) {
                    version = tokenText();
                    lex();
                    if (_state.tokenKind == T_DOT) {
                        version += u'.';
                        lex();
                        if (_state.tokenKind != T_VERSION_NUMBER) {
                            setError(QCoreApplication::translate(
                                         "QQmlParser", "Incomplete version number (dot but no minor)"));
                            return false; // expected the module version number
                        }
                        version += tokenText();
                        lex();
                    }
                }
            }

            //
            // recognize the mandatory `as' followed by the module name
            //
            if (!(_state.tokenKind == T_AS && tokenStartLine() == lineNumber)) {
                if (fileImport)
                    setError(QCoreApplication::translate("QQmlParser", "File import requires a qualifier"));
                else
                    setError(QCoreApplication::translate("QQmlParser", "Module import requires a qualifier"));
                if (tokenStartLine() != lineNumber) {
                    error->loc.startLine = lineNumber;
                    error->loc.startColumn = column;
                }
                return false; // expected `as'
            }

            if (lex() != T_IDENTIFIER || tokenStartLine() != lineNumber) {
                if (fileImport)
                    setError(QCoreApplication::translate("QQmlParser", "File import requires a qualifier"));
                else
                    setError(QCoreApplication::translate("QQmlParser", "Module import requires a qualifier"));
                return false; // expected module name
            }

            const QString module = tokenText();
            if (!module.at(0).isUpper()) {
                setError(QCoreApplication::translate("QQmlParser","Invalid import qualifier"));
                return false;
            }

            if (fileImport)
                directives->importFile(pathOrUri, module, lineNumber, column);
            else
                directives->importModule(pathOrUri, version, module, lineNumber, column);
        }

        if (tokenStartLine() != lineNumber) {
            setError(QCoreApplication::translate("QQmlParser", "Syntax error"));
            return false; // the directives cannot span over multiple lines
        }

        // fetch the first token after the .pragma/.import directive
        lex();
    } while (_state.tokenKind == T_DOT);

    return true;
}

const Lexer::State &Lexer::state() const
{
    return _state;
}
void Lexer::setState(const Lexer::State &state)
{
    _state = state;
}

int Lexer::parseModeFlags() const {
    int flags = 0;
    if (qmlMode())
        flags |= QmlMode|StaticIsKeyword;
    if (yieldIsKeyWord())
        flags |= YieldIsKeyword;
    if (_staticIsKeyword)
        flags |= StaticIsKeyword;
    return flags;
}

namespace QQmlJS {
QDebug operator<<(QDebug dbg, const Lexer::State &s)
{
    dbg << "{\n"
        << "   errorCode:" << int(s.errorCode) << ",\n"
        << "   currentChar:" << s.currentChar << ",\n"
        << "   tokenValue:" << s.tokenValue << ",\n"
        << "   parenthesesState:" << s.parenthesesState << ",\n"
        << "   parenthesesCount:" << s.parenthesesCount << ",\n"
        << "   outerTemplateBraceCount:" << s.outerTemplateBraceCount << ",\n"
        << "   bracesCount:" << s.bracesCount << ",\n"
        << "   stackToken:" << s.stackToken << ",\n"
        << "   patternFlags:" << s.patternFlags << ",\n"
        << "   tokenKind:" << s.tokenKind << ",\n"
        << "   importState:" << int(s.importState) << ",\n"
        << "   validTokenText:" << s.validTokenText << ",\n"
        << "   prohibitAutomaticSemicolon:" << s.prohibitAutomaticSemicolon << ",\n"
        << "   restrictedKeyword:" << s.restrictedKeyword << ",\n"
        << "   terminator:" << s.terminator << ",\n"
        << "   followsClosingBrace:" << s.followsClosingBrace << ",\n"
        << "   delimited:" << s.delimited << ",\n"
        << "   handlingDirectives:" << s.handlingDirectives << ",\n"
        << "   generatorLevel:" << s.generatorLevel << "\n}";
    return dbg;
}
}

QT_END_NAMESPACE
