// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2019 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "moc.h"
#include "generator.h"
#include "qdatetime.h"
#include "utils.h"
#include "outputrevision.h"
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qjsondocument.h>

// -- QScxml
#include <QtCore/qjsonobject.h>
// -- QScxml

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

#if 0 // -- QtScxml
// only moc needs this function
static QByteArray normalizeType(const QByteArray &ba)
{
    return ba.size() ? normalizeTypeInternal(ba.constBegin(), ba.constEnd()) : ba;
}

bool Moc::parseClassHead(ClassDef *def)
{
    // figure out whether this is a class declaration, or only a
    // forward or variable declaration.
    int i = 0;
    Token token;
    do {
        token = lookup(i++);
        if (token == COLON || token == LBRACE)
            break;
        if (token == SEMIC || token == RANGLE)
            return false;
    } while (token);

    // support attributes like "class [[deprecated]]] name"
    skipCxxAttributes();

    if (!test(IDENTIFIER)) // typedef struct { ... }
        return false;
    QByteArray name = lexem();

    // support "class IDENT name" and "class IDENT(IDENT) name"
    // also support "class IDENT name (final|sealed|Q_DECL_FINAL)"
    if (test(LPAREN)) {
        until(RPAREN);
        if (!test(IDENTIFIER))
            return false;
        name = lexem();
    } else  if (test(IDENTIFIER)) {
        const QByteArray lex = lexem();
        if (lex != "final" && lex != "sealed" && lex != "Q_DECL_FINAL")
            name = lex;
    }

    def->qualified += name;
    while (test(SCOPE)) {
        def->qualified += lexem();
        if (test(IDENTIFIER)) {
            name = lexem();
            def->qualified += name;
        }
    }
    def->classname = name;

    if (test(IDENTIFIER)) {
        const QByteArray lex = lexem();
        if (lex != "final" && lex != "sealed" && lex != "Q_DECL_FINAL")
            return false;
    }

    if (test(COLON)) {
        do {
            test(VIRTUAL);
            FunctionDef::Access access = FunctionDef::Public;
            if (test(PRIVATE))
                access = FunctionDef::Private;
            else if (test(PROTECTED))
                access = FunctionDef::Protected;
            else
                test(PUBLIC);
            test(VIRTUAL);
            const QByteArray type = parseType().name;
            // ignore the 'class Foo : BAR(Baz)' case
            if (test(LPAREN)) {
                until(RPAREN);
            } else {
                def->superclassList += qMakePair(type, access);
            }
        } while (test(COMMA));

        if (!def->superclassList.isEmpty()
            && knownGadgets.contains(def->superclassList.constFirst().first)) {
            // Q_GADGET subclasses are treated as Q_GADGETs
            knownGadgets.insert(def->classname, def->qualified);
            knownGadgets.insert(def->qualified, def->qualified);
        }
    }
    if (!test(LBRACE))
        return false;
    def->begin = index - 1;
    bool foundRBrace = until(RBRACE);
    def->end = index;
    index = def->begin + 1;
    return foundRBrace;
}

Type Moc::parseType()
{
    Type type;
    bool hasSignedOrUnsigned = false;
    bool isVoid = false;
    type.firstToken = lookup();
    for (;;) {
        skipCxxAttributes();
        switch (next()) {
            case SIGNED:
            case UNSIGNED:
                hasSignedOrUnsigned = true;
                Q_FALLTHROUGH();
            case CONST:
            case VOLATILE:
                type.name += lexem();
                type.name += ' ';
                if (lookup(0) == VOLATILE)
                    type.isVolatile = true;
                continue;
            case Q_MOC_COMPAT_TOKEN:
            case Q_INVOKABLE_TOKEN:
            case Q_SCRIPTABLE_TOKEN:
            case Q_SIGNALS_TOKEN:
            case Q_SLOTS_TOKEN:
            case Q_SIGNAL_TOKEN:
            case Q_SLOT_TOKEN:
                type.name += lexem();
                return type;
            case NOTOKEN:
                return type;
            default:
                prev();
                break;
        }
        break;
    }

    skipCxxAttributes();
    test(ENUM) || test(CLASS) || test(STRUCT);
    for(;;) {
        skipCxxAttributes();
        switch (next()) {
        case IDENTIFIER:
            // void mySlot(unsigned myArg)
            if (hasSignedOrUnsigned) {
                prev();
                break;
            }
            Q_FALLTHROUGH();
        case CHAR:
        case SHORT:
        case INT:
        case LONG:
            type.name += lexem();
            // preserve '[unsigned] long long', 'short int', 'long int', 'long double'
            if (test(LONG) || test(INT) || test(DOUBLE)) {
                type.name += ' ';
                prev();
                continue;
            }
            break;
        case FLOAT:
        case DOUBLE:
        case VOID:
        case BOOL:
        case AUTO:
            type.name += lexem();
            isVoid |= (lookup(0) == VOID);
            break;
        case NOTOKEN:
            return type;
        default:
            prev();
            ;
        }
        if (test(LANGLE)) {
            if (type.name.isEmpty()) {
                // '<' cannot start a type
                return type;
            }
            type.name += lexemUntil(RANGLE);
        }
        if (test(SCOPE)) {
            type.name += lexem();
            type.isScoped = true;
        } else {
            break;
        }
    }
    while (test(CONST) || test(VOLATILE) || test(SIGNED) || test(UNSIGNED)
           || test(STAR) || test(AND) || test(ANDAND)) {
        type.name += ' ';
        type.name += lexem();
        if (lookup(0) == AND)
            type.referenceType = Type::Reference;
        else if (lookup(0) == ANDAND)
            type.referenceType = Type::RValueReference;
        else if (lookup(0) == STAR)
            type.referenceType = Type::Pointer;
    }
    type.rawName = type.name;
    // transform stupid things like 'const void' or 'void const' into 'void'
    if (isVoid && type.referenceType == Type::NoReference) {
        type.name = "void";
    }
    return type;
}

enum class IncludeState {
    IncludeBegin,
    IncludeEnd,
    NoInclude,
};

bool Moc::parseEnum(EnumDef *def)
{
    bool isTypdefEnum = false; // typedef enum { ... } Foo;

    if (test(CLASS) || test(STRUCT))
        def->isEnumClass = true;

    if (test(IDENTIFIER)) {
        def->name = lexem();
    } else {
        if (lookup(-1) != TYPEDEF)
            return false; // anonymous enum
        isTypdefEnum = true;
    }
    if (test(COLON)) { // C++11 strongly typed enum
        // enum Foo : unsigned long { ... };
        def->type = normalizeType(parseType().name);
    }
    if (!test(LBRACE))
        return false;
    auto handleInclude = [this]() -> IncludeState  {
        bool hadIncludeBegin = false;
        if (test(MOC_INCLUDE_BEGIN)) {
            currentFilenames.push(symbol().unquotedLexem());
            // we do not return early to handle empty headers in one go
            hadIncludeBegin = true;
        }
        if (test(NOTOKEN)) {
            next(MOC_INCLUDE_END);
            currentFilenames.pop();
            return IncludeState::IncludeEnd;
        }
        if (hadIncludeBegin)
            return IncludeState::IncludeBegin;
        else
            return IncludeState::NoInclude;
    };
    do {
        handleInclude();
        if (lookup() == RBRACE) // accept trailing comma
            break;
        next(IDENTIFIER);
        def->values += lexem();
        handleInclude();
        skipCxxAttributes();
    } while (test(EQ) ? until(COMMA) : test(COMMA));
    next(RBRACE);
    if (isTypdefEnum) {
        if (!test(IDENTIFIER))
            return false;
        def->name = lexem();
    }
    return true;
}

void Moc::parseFunctionArguments(FunctionDef *def)
{
    Q_UNUSED(def);
    while (hasNext()) {
        ArgumentDef  arg;
        arg.type = parseType();
        if (arg.type.name == "void")
            break;
        if (test(IDENTIFIER))
            arg.name = lexem();
        while (test(LBRACK)) {
            arg.rightType += lexemUntil(RBRACK);
        }
        if (test(CONST) || test(VOLATILE)) {
            arg.rightType += ' ';
            arg.rightType += lexem();
        }
        arg.normalizedType = normalizeType(QByteArray(arg.type.name + ' ' + arg.rightType));
        arg.typeNameForCast = QByteArray("std::add_pointer_t<"+arg.normalizedType+">");
        if (test(EQ))
            arg.isDefault = true;
        def->arguments += arg;
        if (!until(COMMA))
            break;
    }

    if (!def->arguments.isEmpty()
        && def->arguments.constLast().normalizedType == "QPrivateSignal") {
        def->arguments.removeLast();
        def->isPrivateSignal = true;
    }
    if (def->arguments.size() == 1
        && def->arguments.constLast().normalizedType == "QMethodRawArguments") {
        def->arguments.removeLast();
        def->isRawSlot = true;
    }
}

bool Moc::testFunctionAttribute(FunctionDef *def)
{
    if (index < symbols.size() && testFunctionAttribute(symbols.at(index).token, def)) {
        ++index;
        return true;
    }
    return false;
}

bool Moc::testFunctionAttribute(Token tok, FunctionDef *def)
{
    switch (tok) {
        case Q_MOC_COMPAT_TOKEN:
            def->isCompat = true;
            return true;
        case Q_INVOKABLE_TOKEN:
            def->isInvokable = true;
            return true;
        case Q_SIGNAL_TOKEN:
            def->isSignal = true;
            return true;
        case Q_SLOT_TOKEN:
            def->isSlot = true;
            return true;
        case Q_SCRIPTABLE_TOKEN:
            def->isInvokable = def->isScriptable = true;
            return true;
        default: break;
    }
    return false;
}

bool Moc::skipCxxAttributes()
{
    auto rewind = index;
    if (test(LBRACK) && test(LBRACK) && until(RBRACK) && test(RBRACK))
        return true;
    index = rewind;
    return false;
}

QTypeRevision Moc::parseRevision()
{
    next(LPAREN);
    QByteArray revisionString = lexemUntil(RPAREN);
    revisionString.remove(0, 1);
    revisionString.chop(1);
    const QList<QByteArray> majorMinor = revisionString.split(',');
    switch (majorMinor.size()) {
    case 1: {
        bool ok = false;
        const int revision = revisionString.toInt(&ok);
        if (!ok || !QTypeRevision::isValidSegment(revision))
            error("Invalid revision");
        return QTypeRevision::fromMinorVersion(revision);
    }
    case 2: { // major.minor
        bool ok = false;
        const int major = majorMinor[0].toInt(&ok);
        if (!ok || !QTypeRevision::isValidSegment(major))
            error("Invalid major version");
        const int minor = majorMinor[1].toInt(&ok);
        if (!ok || !QTypeRevision::isValidSegment(minor))
            error("Invalid minor version");
        return QTypeRevision::fromVersion(major, minor);
    }
    default:
        error("Invalid revision");
        return QTypeRevision();
    }
}

bool Moc::testFunctionRevision(FunctionDef *def)
{

    if (test(Q_REVISION_TOKEN)) {
        def->revision = parseRevision().toEncodedVersion<int>();
        return true;
    }

    return false;
}

// returns false if the function should be ignored
bool Moc::parseFunction(FunctionDef *def, bool inMacro)
{
    def->isVirtual = false;
    def->isStatic = false;
    //skip modifiers and attributes
    while (testForFunctionModifiers(def)
        || skipCxxAttributes() || testFunctionAttribute(def) || testFunctionRevision(def)) {}
    bool templateFunction = (lookup() == TEMPLATE);
    def->type = parseType();
    if (def->type.name.isEmpty()) {
        if (templateFunction)
            error("Template function as signal or slot");
        else
            error();
    }
    bool scopedFunctionName = false;
    if (test(LPAREN)) {
        def->name = def->type.name;
        scopedFunctionName = def->type.isScoped;
        def->type = Type("int");
    } else {
        // we might have modifiers and attributes after a tag
        // note that testFunctionAttribute is handled further below,
        // and revisions and attributes must come first
        while (testForFunctionModifiers(def)) {}
        Type tempType = parseType();;
        while (!tempType.name.isEmpty() && lookup() != LPAREN) {
            if (testFunctionAttribute(def->type.firstToken, def))
                ; // fine
            else if (def->type.firstToken == Q_SIGNALS_TOKEN)
                error();
            else if (def->type.firstToken == Q_SLOTS_TOKEN)
                error();
            else {
                if (!def->tag.isEmpty())
                    def->tag += ' ';
                def->tag += def->type.name;
            }
            def->type = tempType;
            tempType = parseType();
        }
        next(LPAREN, "Not a signal or slot declaration");
        def->name = tempType.name;
        scopedFunctionName = tempType.isScoped;
    }

    if (!test(RPAREN)) {
        parseFunctionArguments(def);
        next(RPAREN);
    }

    // support optional macros with compiler specific options
    while (test(IDENTIFIER))
        ;

    def->isConst = test(CONST);

    while (test(IDENTIFIER))
        ;

    if (inMacro) {
        next(RPAREN);
        prev();
    } else {
        if (test(THROW)) {
            next(LPAREN);
            until(RPAREN);
        }

        if (def->type.name == "auto" && test(ARROW))
            def->type = parseType(); // Parse trailing return-type

        if (test(SEMIC))
            ;
        else if ((def->inlineCode = test(LBRACE)))
            until(RBRACE);
        else if ((def->isAbstract = test(EQ)))
            until(SEMIC);
        else if (skipCxxAttributes())
            until(SEMIC);
        else
            error();
    }
    if (scopedFunctionName) {
        const QByteArray msg = "Function declaration " + def->name
                + " contains extra qualification. Ignoring as signal or slot.";
        warning(msg.constData());
        return false;
    }

    QList<QByteArray> typeNameParts = normalizeType(def->type.name).split(' ');
    if (typeNameParts.contains("auto")) {
        // We expected a trailing return type but we haven't seen one
        error("Function declared with auto as return type but missing trailing return type. "
              "Return type deduction is not supported.");
    }

    // we don't support references as return types, it's too dangerous
    if (def->type.referenceType == Type::Reference) {
        QByteArray rawName = def->type.rawName;
        def->type = Type("void");
        def->type.rawName = rawName;
    }

    def->normalizedType = normalizeType(def->type.name);
    return true;
}

bool Moc::testForFunctionModifiers(FunctionDef *def)
{
    return test(EXPLICIT) || test(INLINE) ||
            (test(STATIC) && (def->isStatic = true)) ||
            (test(VIRTUAL) && (def->isVirtual = true));
}

// like parseFunction, but never aborts with an error
bool Moc::parseMaybeFunction(const ClassDef *cdef, FunctionDef *def)
{
    def->isVirtual = false;
    def->isStatic = false;
    //skip modifiers and attributes
    while (testForFunctionModifiers(def)
        || skipCxxAttributes() || testFunctionAttribute(def) || testFunctionRevision(def)) {}
    bool tilde = test(TILDE);
    def->type = parseType();
    if (def->type.name.isEmpty())
        return false;
    bool scopedFunctionName = false;
    if (test(LPAREN)) {
        def->name = def->type.name;
        scopedFunctionName = def->type.isScoped;
        if (def->name == cdef->classname) {
            def->isDestructor = tilde;
            def->isConstructor = !tilde;
            def->type = Type();
        } else {
            def->type = Type("int");
        }
    } else {
        // ### TODO: The condition before testForFunctionModifiers shoulnd't be necessary,
        // but otherwise we end up with misparses
        if (def->isSlot || def->isSignal || def->isInvokable)
            while (testForFunctionModifiers(def)) {}
        Type tempType = parseType();;
        while (!tempType.name.isEmpty() && lookup() != LPAREN) {
            if (testFunctionAttribute(def->type.firstToken, def))
                ; // fine
            else if (def->type.name == "Q_SIGNAL")
                def->isSignal = true;
            else if (def->type.name == "Q_SLOT")
                def->isSlot = true;
            else {
                if (!def->tag.isEmpty())
                    def->tag += ' ';
                def->tag += def->type.name;
            }
            def->type = tempType;
            tempType = parseType();
        }
        if (!test(LPAREN))
            return false;
        def->name = tempType.name;
        scopedFunctionName = tempType.isScoped;
    }

    // we don't support references as return types, it's too dangerous
    if (def->type.referenceType == Type::Reference) {
        QByteArray rawName = def->type.rawName;
        def->type = Type("void");
        def->type.rawName = rawName;
    }

    def->normalizedType = normalizeType(def->type.name);

    if (!test(RPAREN)) {
        parseFunctionArguments(def);
        if (!test(RPAREN))
            return false;
    }
    def->isConst = test(CONST);
    if (scopedFunctionName
        && (def->isSignal || def->isSlot || def->isInvokable)) {
        const QByteArray msg = "parsemaybe: Function declaration " + def->name
                + " contains extra qualification. Ignoring as signal or slot.";
        warning(msg.constData());
        return false;
    }
    return true;
}

inline void handleDefaultArguments(QList<FunctionDef> *functionList, FunctionDef &function)
{
    // support a function with a default argument by pretending there is an
    // overload without the argument (the original function is the overload with
    // all arguments present)
    while (function.arguments.size() > 0 && function.arguments.constLast().isDefault) {
        function.wasCloned = true;
        function.arguments.removeLast();
        *functionList += function;
    }
}

void Moc::prependNamespaces(BaseDef &def, const QList<NamespaceDef> &namespaceList) const
{
    auto it = namespaceList.crbegin();
    const auto rend = namespaceList.crend();
    for (; it != rend; ++it) {
        if (inNamespace(&*it))
            def.qualified.prepend(it->classname + "::");
    }
}

void Moc::parse()
{
    QList<NamespaceDef> namespaceList;
    bool templateClass = false;
    while (hasNext()) {
        Token t = next();
        switch (t) {
            case NAMESPACE: {
                qsizetype rewind = index;
                if (test(IDENTIFIER)) {
                    QByteArray nsName = lexem();
                    QByteArrayList nested;
                    while (test(SCOPE)) {
                        /* treat (C++20's) namespace A::inline B {} as A::B
                           this is mostly to not break compilation when encountering such
                           a construct in a header; the interaction of Qt's meta-macros with
                           inline namespaces is still rather poor.
                        */
                        test(INLINE);
                        next(IDENTIFIER);
                        nested.append(nsName);
                        nsName = lexem();
                    }
                    if (test(EQ)) {
                        // namespace Foo = Bar::Baz;
                        until(SEMIC);
                    } else if (test(LPAREN)) {
                        // Ignore invalid code such as: 'namespace __identifier("x")' (QTBUG-56634)
                        until(RPAREN);
                    } else if (!test(SEMIC)) {
                        NamespaceDef def;
                        def.classname = nsName;
                        def.doGenerate = currentFilenames.size() <= 1;

                        next(LBRACE);
                        def.begin = index - 1;
                        until(RBRACE);
                        def.end = index;
                        index = def.begin + 1;

                        prependNamespaces(def, namespaceList);

                        for (const QByteArray &ns : nested) {
                            NamespaceDef parentNs;
                            parentNs.classname = ns;
                            parentNs.qualified = def.qualified;
                            def.qualified += ns + "::";
                            parentNs.begin = def.begin;
                            parentNs.end = def.end;
                            namespaceList += parentNs;
                        }

                        while (inNamespace(&def) && hasNext()) {
                            switch (next()) {
                            case NAMESPACE:
                                if (test(IDENTIFIER)) {
                                    while (test(SCOPE))
                                        next(IDENTIFIER);
                                    if (test(EQ)) {
                                        // namespace Foo = Bar::Baz;
                                        until(SEMIC);
                                    } else if (!test(SEMIC)) {
                                        until(RBRACE);
                                    }
                                }
                                break;
                            case Q_NAMESPACE_TOKEN:
                                def.hasQNamespace = true;
                                break;
                            case Q_NAMESPACE_EXPORT_TOKEN:
                                next(LPAREN);
                                while (test(IDENTIFIER))
                                    {}
                                next(RPAREN);
                                def.hasQNamespace = true;
                                break;
                            case Q_ENUMS_TOKEN:
                            case Q_ENUM_NS_TOKEN:
                                parseEnumOrFlag(&def, false);
                                break;
                            case Q_ENUM_TOKEN:
                                error("Q_ENUM can't be used in a Q_NAMESPACE, use Q_ENUM_NS instead");
                                break;
                            case Q_FLAGS_TOKEN:
                            case Q_FLAG_NS_TOKEN:
                                parseEnumOrFlag(&def, true);
                                break;
                            case Q_FLAG_TOKEN:
                                error("Q_FLAG can't be used in a Q_NAMESPACE, use Q_FLAG_NS instead");
                                break;
                            case Q_DECLARE_FLAGS_TOKEN:
                                parseFlag(&def);
                                break;
                            case Q_CLASSINFO_TOKEN:
                                parseClassInfo(&def);
                                break;
                            case Q_MOC_INCLUDE_TOKEN:
                                // skip it, the namespace is parsed twice
                                next(LPAREN);
                                lexemUntil(RPAREN);
                                break;
                            case ENUM: {
                                EnumDef enumDef;
                                if (parseEnum(&enumDef))
                                    def.enumList += enumDef;
                            } break;
                            case CLASS:
                            case STRUCT: {
                                ClassDef classdef;
                                if (!parseClassHead(&classdef))
                                    continue;
                                while (inClass(&classdef) && hasNext())
                                    next(); // consume all Q_XXXX macros from this class
                            } break;
                            default: break;
                            }
                        }
                        namespaceList += def;
                        index = rewind;
                        if (!def.hasQNamespace && (!def.classInfoList.isEmpty() || !def.enumDeclarations.isEmpty()))
                            error("Namespace declaration lacks Q_NAMESPACE macro.");
                    }
                }
                break;
            }
            case SEMIC:
            case RBRACE:
                templateClass = false;
                break;
            case TEMPLATE:
                templateClass = true;
                break;
            case MOC_INCLUDE_BEGIN:
                currentFilenames.push(symbol().unquotedLexem());
                break;
            case MOC_INCLUDE_END:
                currentFilenames.pop();
                break;
            case Q_DECLARE_INTERFACE_TOKEN:
                parseDeclareInterface();
                break;
            case Q_DECLARE_METATYPE_TOKEN:
                parseDeclareMetatype();
                break;
            case Q_MOC_INCLUDE_TOKEN:
                parseMocInclude();
                break;
            case USING:
                if (test(NAMESPACE)) {
                    while (test(SCOPE) || test(IDENTIFIER))
                        ;
                    // Ignore invalid code such as: 'using namespace __identifier("x")' (QTBUG-63772)
                    if (test(LPAREN))
                        until(RPAREN);
                    next(SEMIC);
                }
                break;
            case CLASS:
            case STRUCT: {
                if (currentFilenames.size() <= 1)
                    break;

                ClassDef def;
                if (!parseClassHead(&def))
                    continue;

                while (inClass(&def) && hasNext()) {
                    switch (next()) {
                    case Q_OBJECT_TOKEN:
                        def.hasQObject = true;
                        break;
                    case Q_GADGET_EXPORT_TOKEN:
                        next(LPAREN);
                        while (test(IDENTIFIER))
                            {}
                        next(RPAREN);
                        Q_FALLTHROUGH();
                    case Q_GADGET_TOKEN:
                        def.hasQGadget = true;
                        break;
                    default: break;
                    }
                }

                if (!def.hasQObject && !def.hasQGadget)
                    continue;

                prependNamespaces(def, namespaceList);

                QHash<QByteArray, QByteArray> &classHash = def.hasQObject ? knownQObjectClasses : knownGadgets;
                classHash.insert(def.classname, def.qualified);
                classHash.insert(def.qualified, def.qualified);

                continue; }
            default: break;
        }
        if ((t != CLASS && t != STRUCT)|| currentFilenames.size() > 1)
            continue;
        ClassDef def;
        if (parseClassHead(&def)) {
            prependNamespaces(def, namespaceList);

            FunctionDef::Access access = FunctionDef::Private;
            while (inClass(&def) && hasNext()) {
                switch ((t = next())) {
                case PRIVATE:
                    access = FunctionDef::Private;
                    if (test(Q_SIGNALS_TOKEN))
                        error("Signals cannot have access specifier");
                    break;
                case PROTECTED:
                    access = FunctionDef::Protected;
                    if (test(Q_SIGNALS_TOKEN))
                        error("Signals cannot have access specifier");
                    break;
                case PUBLIC:
                    access = FunctionDef::Public;
                    if (test(Q_SIGNALS_TOKEN))
                        error("Signals cannot have access specifier");
                    break;
                case CLASS: {
                    ClassDef nestedDef;
                    if (parseClassHead(&nestedDef)) {
                        while (inClass(&nestedDef) && inClass(&def)) {
                            t = next();
                            if (t >= Q_META_TOKEN_BEGIN && t < Q_META_TOKEN_END)
                                error("Meta object features not supported for nested classes");
                        }
                    }
                } break;
                case Q_SIGNALS_TOKEN:
                    parseSignals(&def);
                    break;
                case Q_SLOTS_TOKEN:
                    switch (lookup(-1)) {
                    case PUBLIC:
                    case PROTECTED:
                    case PRIVATE:
                        parseSlots(&def, access);
                        break;
                    default:
                        error("Missing access specifier for slots");
                    }
                    break;
                case Q_OBJECT_TOKEN:
                    def.hasQObject = true;
                    if (templateClass)
                        error("Template classes not supported by Q_OBJECT");
                    if (def.classname != "Qt" && def.classname != "QObject" && def.superclassList.isEmpty())
                        error("Class contains Q_OBJECT macro but does not inherit from QObject");
                    break;
                case Q_GADGET_EXPORT_TOKEN:
                    next(LPAREN);
                    while (test(IDENTIFIER))
                        {}
                    next(RPAREN);
                    Q_FALLTHROUGH();
                case Q_GADGET_TOKEN:
                    def.hasQGadget = true;
                    if (templateClass)
                        error("Template classes not supported by Q_GADGET");
                    break;
                case Q_PROPERTY_TOKEN:
                    parseProperty(&def, Named);
                    break;
                case QT_ANONYMOUS_PROPERTY_TOKEN:
                    parseProperty(&def, Anonymous);
                    break;
                case Q_PLUGIN_METADATA_TOKEN:
                    parsePluginData(&def);
                    break;
                case Q_ENUMS_TOKEN:
                case Q_ENUM_TOKEN:
                    parseEnumOrFlag(&def, false);
                    break;
                case Q_ENUM_NS_TOKEN:
                    error("Q_ENUM_NS can't be used in a Q_OBJECT/Q_GADGET, use Q_ENUM instead");
                    break;
                case Q_FLAGS_TOKEN:
                case Q_FLAG_TOKEN:
                    parseEnumOrFlag(&def, true);
                    break;
                case Q_FLAG_NS_TOKEN:
                    error("Q_FLAG_NS can't be used in a Q_OBJECT/Q_GADGET, use Q_FLAG instead");
                    break;
                case Q_DECLARE_FLAGS_TOKEN:
                    parseFlag(&def);
                    break;
                case Q_CLASSINFO_TOKEN:
                    parseClassInfo(&def);
                    break;
                case Q_MOC_INCLUDE_TOKEN:
                    parseMocInclude();
                    break;
                case Q_INTERFACES_TOKEN:
                    parseInterfaces(&def);
                    break;
                case Q_PRIVATE_SLOT_TOKEN:
                    parseSlotInPrivate(&def, access);
                    break;
                case Q_PRIVATE_PROPERTY_TOKEN:
                    parsePrivateProperty(&def, Named);
                    break;
                case QT_ANONYMOUS_PRIVATE_PROPERTY_TOKEN:
                    parsePrivateProperty(&def, Anonymous);
                    break;
                case ENUM: {
                    EnumDef enumDef;
                    if (parseEnum(&enumDef))
                        def.enumList += enumDef;
                } break;
                case SEMIC:
                case COLON:
                    break;
                default:
                    FunctionDef funcDef;
                    funcDef.access = access;
                    qsizetype rewind = index--;
                    if (parseMaybeFunction(&def, &funcDef)) {
                        if (funcDef.isConstructor) {
                            if ((access == FunctionDef::Public) && funcDef.isInvokable) {
                                def.constructorList += funcDef;
                                handleDefaultArguments(&def.constructorList, funcDef);
                            }
                        } else if (funcDef.isDestructor) {
                            // don't care about destructors
                        } else {
                            if (access == FunctionDef::Public)
                                def.publicList += funcDef;
                            if (funcDef.isSlot) {
                                def.slotList += funcDef;
                                handleDefaultArguments(&def.slotList, funcDef);
                                if (funcDef.revision > 0)
                                    ++def.revisionedMethods;
                            } else if (funcDef.isSignal) {
                                def.signalList += funcDef;
                                handleDefaultArguments(&def.signalList, funcDef);
                                if (funcDef.revision > 0)
                                    ++def.revisionedMethods;
                            } else if (funcDef.isInvokable) {
                                def.methodList += funcDef;
                                handleDefaultArguments(&def.methodList, funcDef);
                                if (funcDef.revision > 0)
                                    ++def.revisionedMethods;
                            }
                        }
                    } else {
                        index = rewind;
                    }
                }
            }

            next(RBRACE);

            if (!def.hasQObject && !def.hasQGadget && def.signalList.isEmpty() && def.slotList.isEmpty()
                && def.propertyList.isEmpty() && def.enumDeclarations.isEmpty())
                continue; // no meta object code required


            if (!def.hasQObject && !def.hasQGadget)
                error("Class declaration lacks Q_OBJECT macro.");

            // Add meta tags to the plugin meta data:
            if (!def.pluginData.iid.isEmpty())
                def.pluginData.metaArgs = metaArgs;

            if (def.hasQObject && !def.superclassList.isEmpty())
                checkSuperClasses(&def);

            checkProperties(&def);

            classList += def;
            QHash<QByteArray, QByteArray> &classHash = def.hasQObject ? knownQObjectClasses : knownGadgets;
            classHash.insert(def.classname, def.qualified);
            classHash.insert(def.qualified, def.qualified);
        }
    }
    for (const auto &n : std::as_const(namespaceList)) {
        if (!n.hasQNamespace)
            continue;
        ClassDef def;
        static_cast<BaseDef &>(def) = static_cast<BaseDef>(n);
        def.qualified += def.classname;
        def.hasQNamespace = true;
        auto it = std::find_if(classList.begin(), classList.end(), [&def](const ClassDef &val) {
            return def.classname == val.classname && def.qualified == val.qualified;
        });

        if (it != classList.end()) {
            it->classInfoList += def.classInfoList;
            it->enumDeclarations.insert(def.enumDeclarations);
            it->enumList += def.enumList;
            it->flagAliases.insert(def.flagAliases);
        } else {
            knownGadgets.insert(def.classname, def.qualified);
            knownGadgets.insert(def.qualified, def.qualified);
            if (n.doGenerate)
                classList += def;
        }
    }
}

static bool any_type_contains(const QList<PropertyDef> &properties, const QByteArray &pattern)
{
    for (const auto &p : properties) {
        if (p.type.contains(pattern))
            return true;
    }
    return false;
}

static bool any_arg_contains(const QList<FunctionDef> &functions, const QByteArray &pattern)
{
    for (const auto &f : functions) {
        for (const auto &arg : f.arguments) {
            if (arg.normalizedType.contains(pattern))
                return true;
        }
    }
    return false;
}

static QByteArrayList make_candidates()
{
    QByteArrayList result;
    result
#define STREAM_SMART_POINTER(SMART_POINTER) << #SMART_POINTER
        QT_FOR_EACH_AUTOMATIC_TEMPLATE_SMART_POINTER(STREAM_SMART_POINTER)
#undef STREAM_SMART_POINTER
#define STREAM_1ARG_TEMPLATE(TEMPLATENAME) << #TEMPLATENAME
        QT_FOR_EACH_AUTOMATIC_TEMPLATE_1ARG(STREAM_1ARG_TEMPLATE)
#undef STREAM_1ARG_TEMPLATE
        ;
    return result;
}

static QByteArrayList requiredQtContainers(const QList<ClassDef> &classes)
{
    static const QByteArrayList candidates = make_candidates();

    QByteArrayList required;
    required.reserve(candidates.size());

    bool needsQProperty = false;

    for (const auto &candidate : candidates) {
        const QByteArray pattern = candidate + '<';

        for (const auto &c : classes) {
            for (const auto &p : c.propertyList)
                needsQProperty |= !p.bind.isEmpty();
            if (any_type_contains(c.propertyList, pattern) ||
                    any_arg_contains(c.slotList, pattern) ||
                    any_arg_contains(c.signalList, pattern) ||
                    any_arg_contains(c.methodList, pattern)) {
                required.push_back(candidate);
                break;
            }
        }
    }

    if (needsQProperty)
        required.push_back("QProperty");

    return required;
}

void Moc::generate(FILE *out, FILE *jsonOutput)
{
    QByteArrayView fn = QByteArrayView(filename);

    auto isSlash = [](char ch) { return ch == '/' || ch == '\\'; };
    auto rit = std::find_if(fn.crbegin(), fn.crend(), isSlash);
    if (rit != fn.crend())
        fn = fn.last(rit - fn.crbegin());

    fprintf(out, "/****************************************************************************\n"
            "** Meta object code from reading C++ file '%s'\n**\n" , fn.constData());
    fprintf(out, "** Created by: The Qt Meta Object Compiler version %d (Qt %s)\n**\n" , mocOutputRevision, QT_VERSION_STR);
    fprintf(out, "** WARNING! All changes made in this file will be lost!\n"
            "*****************************************************************************/\n\n");

    // include header(s) of user class definitions at _first_ to allow
    // for preprocessor definitions possibly affecting standard headers.
    // see https://codereview.qt-project.org/c/qt/qtbase/+/445937
    if (!noInclude) {
        if (includePath.size() && !includePath.endsWith('/'))
            includePath += '/';
        for (QByteArray inc : std::as_const(includeFiles)) {
            if (!inc.isEmpty() && inc.at(0) != '<' && inc.at(0) != '"') {
                if (includePath.size() && includePath != "./")
                    inc.prepend(includePath);
                inc = '\"' + inc + '\"';
            }
            fprintf(out, "#include %s\n", inc.constData());
        }
    }
    if (classList.size() && classList.constFirst().classname == "Qt")
        fprintf(out, "#include <QtCore/qobject.h>\n");

    fprintf(out, "#include <QtCore/qmetatype.h>\n");  // For QMetaType::Type
    if (mustIncludeQPluginH)
        fprintf(out, "#include <QtCore/qplugin.h>\n");

    const auto qtContainers = requiredQtContainers(classList);
    for (const QByteArray &qtContainer : qtContainers)
        fprintf(out, "#include <QtCore/%s>\n", qtContainer.constData());

    fprintf(out, "\n%s#include <QtCore/qtmochelpers.h>\n%s\n",
#if QT_VERSION <= QT_VERSION_CHECK(6, 9, 0)
            "#if __has_include(<QtCore/qtmochelpers.h>)\n",
            "#else\n"
            "QT_BEGIN_MOC_NAMESPACE\n"
            "#endif\n"
#else
            "", ""
#endif
    );

    fprintf(out, "\n#include <memory>\n\n");  // For std::addressof

    fprintf(out, "#if !defined(Q_MOC_OUTPUT_REVISION)\n"
            "#error \"The header file '%s' doesn't include <QObject>.\"\n", fn.constData());
    fprintf(out, "#elif Q_MOC_OUTPUT_REVISION != %d\n", mocOutputRevision);
    fprintf(out, "#error \"This file was generated using the moc from %s."
            " It\"\n#error \"cannot be used with the include files from"
            " this version of Qt.\"\n#error \"(The moc has changed too"
            " much.)\"\n", QT_VERSION_STR);
    fprintf(out, "#endif\n\n");

#if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0)
    fprintf(out, "#ifndef Q_CONSTINIT\n"
            "#define Q_CONSTINIT\n"
            "#endif\n\n");
#endif

    fprintf(out, "QT_WARNING_PUSH\n");
    fprintf(out, "QT_WARNING_DISABLE_DEPRECATED\n");
    fprintf(out, "QT_WARNING_DISABLE_GCC(\"-Wuseless-cast\")\n");

    fputs("", out);
    for (ClassDef &def : classList) {
        Generator generator(&def, metaTypes, knownQObjectClasses, knownGadgets, out,
                            requireCompleteTypes);
        generator.generateCode();
    }
    fputs("", out);

    fprintf(out, "QT_WARNING_POP\n");

    if (jsonOutput) {
        QJsonObject mocData;
        mocData["outputRevision"_L1] = mocOutputRevision;
        mocData["inputFile"_L1] = QLatin1StringView(fn.constData());

        QJsonArray classesJsonFormatted;

        for (const ClassDef &cdef: std::as_const(classList))
            classesJsonFormatted.append(cdef.toJson());

        if (!classesJsonFormatted.isEmpty())
            mocData["classes"_L1] = classesJsonFormatted;

        QJsonDocument jsonDoc(mocData);
        fputs(jsonDoc.toJson().constData(), jsonOutput);
    }
}

void Moc::parseSlots(ClassDef *def, FunctionDef::Access access)
{
    QTypeRevision defaultRevision;
    if (test(Q_REVISION_TOKEN))
        defaultRevision = parseRevision();

    next(COLON);
    while (inClass(def) && hasNext()) {
        switch (next()) {
        case PUBLIC:
        case PROTECTED:
        case PRIVATE:
        case Q_SIGNALS_TOKEN:
        case Q_SLOTS_TOKEN:
            prev();
            return;
        case SEMIC:
            continue;
        case FRIEND:
            until(SEMIC);
            continue;
        case USING:
            error("'using' directive not supported in 'slots' section");
        default:
            prev();
        }

        FunctionDef funcDef;
        funcDef.access = access;
        if (!parseFunction(&funcDef))
            continue;
        if (funcDef.revision > 0) {
            ++def->revisionedMethods;
        } else if (defaultRevision.isValid()) {
            funcDef.revision = defaultRevision.toEncodedVersion<int>();
            ++def->revisionedMethods;
        }
        def->slotList += funcDef;
        handleDefaultArguments(&def->slotList, funcDef);
    }
}

void Moc::parseSignals(ClassDef *def)
{
    QTypeRevision defaultRevision;
    if (test(Q_REVISION_TOKEN))
        defaultRevision = parseRevision();

    next(COLON);
    while (inClass(def) && hasNext()) {
        switch (next()) {
        case PUBLIC:
        case PROTECTED:
        case PRIVATE:
        case Q_SIGNALS_TOKEN:
        case Q_SLOTS_TOKEN:
            prev();
            return;
        case SEMIC:
            continue;
        case FRIEND:
            until(SEMIC);
            continue;
        case USING:
            error("'using' directive not supported in 'signals' section");
        default:
            prev();
        }
        FunctionDef funcDef;
        funcDef.access = FunctionDef::Public;
        parseFunction(&funcDef);
        if (funcDef.isVirtual)
            warning("Signals cannot be declared virtual");
        if (funcDef.inlineCode)
            error("Not a signal declaration");
        if (funcDef.revision > 0) {
            ++def->revisionedMethods;
        } else if (defaultRevision.isValid()) {
            funcDef.revision = defaultRevision.toEncodedVersion<int>();
            ++def->revisionedMethods;
        }
        def->signalList += funcDef;
        handleDefaultArguments(&def->signalList, funcDef);
    }
}

void Moc::createPropertyDef(PropertyDef &propDef, int propertyIndex, Moc::PropertyMode mode)
{
    propDef.location = index;
    propDef.relativeIndex = propertyIndex;

    QByteArray type = parseType().name;
    if (type.isEmpty())
        error();
    propDef.designable = propDef.scriptable = propDef.stored = "true";
    propDef.user = "false";
    /*
      The Q_PROPERTY construct cannot contain any commas, since
      commas separate macro arguments. We therefore expect users
      to type "QMap" instead of "QMap<QString, QVariant>". For
      coherence, we also expect the same for
      QValueList<QVariant>, the other template class supported by
      QVariant.
    */
    type = normalizeType(type);
    if (type == "QMap")
        type = "QMap<QString,QVariant>";
    else if (type == "QValueList")
        type = "QValueList<QVariant>";
    else if (type == "LongLong")
        type = "qlonglong";
    else if (type == "ULongLong")
        type = "qulonglong";

    propDef.type = type;

    if (mode == Moc::Named) {
        next();
        propDef.name = lexem();
    }

    parsePropertyAttributes(propDef);
}

void Moc::parsePropertyAttributes(PropertyDef &propDef)
{
    auto checkIsFunction = [&](const QByteArray &def, const char *name) {
        if (def.endsWith(')')) {
            QByteArray msg = "Providing a function for ";
            msg += name;
            msg += " in a property declaration is not be supported in Qt 6.";
            error(msg.constData());
        }
    };

    while (test(IDENTIFIER)) {
        const Symbol &lsym = symbol();
        const QByteArray l = lsym.lexem();
        if (l[0] == 'C' && l == "CONSTANT") {
            propDef.constant = true;
            continue;
        } else if (l[0] == 'F' && l == "FINAL") {
            propDef.final = true;
            continue;
        } else if (l[0] == 'N' && l == "NAME") {
            next(IDENTIFIER);
            propDef.name = lexem();
            continue;
        } else if (l[0] == 'R' && l == "REQUIRED") {
            propDef.required = true;
            continue;
        } else if (l[0] == 'R' && l == "REVISION" && test(LPAREN)) {
            prev();
            propDef.revision = parseRevision().toEncodedVersion<int>();
            continue;
        }

        QByteArray v, v2;
        if (test(LPAREN)) {
            v = lexemUntil(RPAREN);
            v = v.mid(1, v.size() - 2); // removes the '(' and ')'
        } else if (test(INTEGER_LITERAL)) {
            v = lexem();
            if (l != "REVISION")
                error(lsym);
        } else if (test(DEFAULT)) {
            v = lexem();
            if (l != "READ" && l != "WRITE")
                error(lsym);
        } else {
            next(IDENTIFIER);
            v = lexem();
            if (test(LPAREN))
                v2 = lexemUntil(RPAREN);
            else if (v != "true" && v != "false")
                v2 = "()";
        }
        switch (l[0]) {
        case 'M':
            if (l == "MEMBER")
                propDef.member = v;
            else
                error(lsym);
            break;
        case 'R':
            if (l == "READ")
                propDef.read = v;
            else if (l == "RESET")
                propDef.reset = v;
            else if (l == "REVISION") {
                bool ok = false;
                const int minor = v.toInt(&ok);
                if (!ok || !QTypeRevision::isValidSegment(minor))
                    error(lsym);
                propDef.revision = QTypeRevision::fromMinorVersion(minor).toEncodedVersion<int>();
            } else
                error(lsym);
            break;
        case 'S':
            if (l == "SCRIPTABLE") {
                propDef.scriptable = v + v2;
                checkIsFunction(propDef.scriptable, "SCRIPTABLE");
            } else if (l == "STORED") {
                propDef.stored = v + v2;
                checkIsFunction(propDef.stored, "STORED");
            } else
                error(lsym);
            break;
        case 'W': if (l != "WRITE") error(lsym);
            propDef.write = v;
            break;
        case 'B': if (l != "BINDABLE") error(lsym);
            propDef.bind = v;
            break;
        case 'D': if (l != "DESIGNABLE") error(lsym);
            propDef.designable = v + v2;
            checkIsFunction(propDef.designable, "DESIGNABLE");
            break;
        case 'N': if (l != "NOTIFY") error(lsym);
            propDef.notify = v;
            break;
        case 'U': if (l != "USER") error(lsym);
            propDef.user = v + v2;
            checkIsFunction(propDef.user, "USER");
            break;
        default:
            error(lsym);
        }
    }
    if (propDef.constant && !propDef.write.isNull()) {
        const QByteArray msg = "Property declaration " + propDef.name
                + " is both WRITEable and CONSTANT. CONSTANT will be ignored.";
        propDef.constant = false;
        warning(msg.constData());
    }
    if (propDef.constant && !propDef.notify.isNull()) {
        const QByteArray msg = "Property declaration " + propDef.name
                + " is both NOTIFYable and CONSTANT. CONSTANT will be ignored.";
        propDef.constant = false;
        warning(msg.constData());
    }
    if (propDef.constant && !propDef.bind.isNull()) {
        const QByteArray msg = "Property declaration " + propDef.name
                + " is both BINDable and CONSTANT. CONSTANT will be ignored.";
        propDef.constant = false;
        warning(msg.constData());
    }
    if (propDef.read == "default" && propDef.bind.isNull()) {
        const QByteArray msg = "Property declaration " + propDef.name
                + " is not BINDable but default-READable. READ will be ignored.";
        propDef.read = "";
        warning(msg.constData());
    }
    if (propDef.write == "default" && propDef.bind.isNull()) {
        const QByteArray msg = "Property declaration " + propDef.name
                + " is not BINDable but default-WRITEable. WRITE will be ignored.";
        propDef.write = "";
        warning(msg.constData());
    }
}

void Moc::parseProperty(ClassDef *def, Moc::PropertyMode mode)
{
    next(LPAREN);
    PropertyDef propDef;
    createPropertyDef(propDef, int(def->propertyList.size()), mode);
    next(RPAREN);

    def->propertyList += propDef;
}

void Moc::parsePluginData(ClassDef *def)
{
    next(LPAREN);
    QByteArray metaData;
    while (test(IDENTIFIER)) {
        QByteArray l = lexem();
        if (l == "IID") {
            next(STRING_LITERAL);
            def->pluginData.iid = unquotedLexem();
        } else if (l == "URI") {
            next(STRING_LITERAL);
            def->pluginData.uri = unquotedLexem();
        } else if (l == "FILE") {
            next(STRING_LITERAL);
            QByteArray metaDataFile = unquotedLexem();
            QFileInfo fi(QFileInfo(QString::fromLocal8Bit(currentFilenames.top())).dir(),
                         QString::fromLocal8Bit(metaDataFile));
            for (const IncludePath &p : std::as_const(includes)) {
                if (fi.exists())
                    break;
                if (p.isFrameworkPath)
                    continue;

                fi.setFile(QString::fromLocal8Bit(p.path.constData()), QString::fromLocal8Bit(metaDataFile.constData()));
                // try again, maybe there's a file later in the include paths with the same name
                if (fi.isDir()) {
                    fi = QFileInfo();
                    continue;
                }
            }
            if (!fi.exists()) {
                const QByteArray msg = "Plugin Metadata file " + lexem()
                        + " does not exist. Declaration will be ignored";
                error(msg.constData());
                return;
            }
            QFile file(fi.canonicalFilePath());
            if (!file.open(QFile::ReadOnly)) {
                QByteArray msg = "Plugin Metadata file " + lexem() + " could not be opened: "
                    + file.errorString().toUtf8();
                error(msg.constData());
                return;
            }
            parsedPluginMetadataFiles.append(fi.canonicalFilePath());
            metaData = file.readAll();
        }
    }

    if (!metaData.isEmpty()) {
        def->pluginData.metaData = QJsonDocument::fromJson(metaData);
        if (!def->pluginData.metaData.isObject()) {
            const QByteArray msg = "Plugin Metadata file " + lexem()
                    + " does not contain a valid JSON object. Declaration will be ignored";
            warning(msg.constData());
            def->pluginData.iid = QByteArray();
            def->pluginData.uri = QByteArray();
            return;
        }
    }

    mustIncludeQPluginH = true;
    next(RPAREN);
}

QByteArray Moc::parsePropertyAccessor()
{
    int nesting = 0;
    QByteArray accessor;
    while (1) {
        Token t = peek();
        if (!nesting && (t == RPAREN || t == COMMA))
            break;
        t = next();
        if (t == LPAREN)
            ++nesting;
        if (t == RPAREN)
            --nesting;
        accessor += lexem();
    }
    return accessor;
}

void Moc::parsePrivateProperty(ClassDef *def, Moc::PropertyMode mode)
{
    next(LPAREN);
    PropertyDef propDef;
    propDef.inPrivateClass = parsePropertyAccessor();

    next(COMMA);

    createPropertyDef(propDef, int(def->propertyList.size()), mode);

    def->propertyList += propDef;
}

void Moc::parseEnumOrFlag(BaseDef *def, bool isFlag)
{
    next(LPAREN);
    QByteArray identifier;
    while (test(IDENTIFIER)) {
        identifier = lexem();
        while (test(SCOPE) && test(IDENTIFIER)) {
            identifier += "::";
            identifier += lexem();
        }
        def->enumDeclarations[identifier] = isFlag;
    }
    next(RPAREN);
}

void Moc::parseFlag(BaseDef *def)
{
    next(LPAREN);
    QByteArray flagName, enumName;
    while (test(IDENTIFIER)) {
        flagName = lexem();
        while (test(SCOPE) && test(IDENTIFIER)) {
            flagName += "::";
            flagName += lexem();
        }
    }
    next(COMMA);
    while (test(IDENTIFIER)) {
        enumName = lexem();
        while (test(SCOPE) && test(IDENTIFIER)) {
            enumName += "::";
            enumName += lexem();
        }
    }

    def->flagAliases.insert(enumName, flagName);
    next(RPAREN);
}

Moc::EncounteredQmlMacro Moc::parseClassInfo(BaseDef *def)
{
    bool encounteredQmlMacro = false;
    next(LPAREN);
    ClassInfoDef infoDef;
    next(STRING_LITERAL);
    infoDef.name = symbol().unquotedLexem();
    if (infoDef.name.startsWith("QML."))
        encounteredQmlMacro = true;
    next(COMMA);
    if (test(STRING_LITERAL)) {
        infoDef.value = symbol().unquotedLexem();
    } else if (test(Q_REVISION_TOKEN)) {
        infoDef.value = QByteArray::number(parseRevision().toEncodedVersion<quint16>());
    } else {
        // support Q_CLASSINFO("help", QT_TR_NOOP("blah"))
        next(IDENTIFIER);
        next(LPAREN);
        next(STRING_LITERAL);
        infoDef.value = symbol().unquotedLexem();
        next(RPAREN);
    }
    next(RPAREN);
    def->classInfoList += infoDef;
    return encounteredQmlMacro ? EncounteredQmlMacro::Yes : EncounteredQmlMacro::No;
}

void Moc::parseClassInfo(ClassDef *def)
{
    if (parseClassInfo(static_cast<BaseDef *>(def)) == EncounteredQmlMacro::Yes)
        def->requireCompleteMethodTypes = true;
}

void Moc::parseInterfaces(ClassDef *def)
{
    next(LPAREN);
    while (test(IDENTIFIER)) {
        QList<ClassDef::Interface> iface;
        iface += ClassDef::Interface(lexem());
        while (test(SCOPE)) {
            iface.last().className += lexem();
            next(IDENTIFIER);
            iface.last().className += lexem();
        }
        while (test(COLON)) {
            next(IDENTIFIER);
            iface += ClassDef::Interface(lexem());
            while (test(SCOPE)) {
                iface.last().className += lexem();
                next(IDENTIFIER);
                iface.last().className += lexem();
            }
        }
        // resolve from classnames to interface ids
        for (qsizetype i = 0; i < iface.size(); ++i) {
            const QByteArray iid = interface2IdMap.value(iface.at(i).className);
            if (iid.isEmpty())
                error("Undefined interface");

            iface[i].interfaceId = iid;
        }
        def->interfaceList += iface;
    }
    next(RPAREN);
}

void Moc::parseDeclareInterface()
{
    next(LPAREN);
    QByteArray interface;
    next(IDENTIFIER);
    interface += lexem();
    while (test(SCOPE)) {
        interface += lexem();
        next(IDENTIFIER);
        interface += lexem();
    }
    next(COMMA);
    QByteArray iid;
    if (test(STRING_LITERAL)) {
        iid = lexem();
    } else {
        next(IDENTIFIER);
        iid = lexem();
    }
    interface2IdMap.insert(interface, iid);
    next(RPAREN);
}

void Moc::parseDeclareMetatype()
{
    next(LPAREN);
    QByteArray typeName = lexemUntil(RPAREN);
    typeName.remove(0, 1);
    typeName.chop(1);
    metaTypes.append(typeName);
}

void Moc::parseMocInclude()
{
    next(LPAREN);
    QByteArray include = lexemUntil(RPAREN);
    // remove parentheses
    include.remove(0, 1);
    include.chop(1);
    includeFiles.append(include);
}

void Moc::parseSlotInPrivate(ClassDef *def, FunctionDef::Access access)
{
    next(LPAREN);
    FunctionDef funcDef;
    next(IDENTIFIER);
    funcDef.inPrivateClass = lexem();
    // also allow void functions
    if (test(LPAREN)) {
        next(RPAREN);
        funcDef.inPrivateClass += "()";
    }
    next(COMMA);
    funcDef.access = access;
    parseFunction(&funcDef, true);
    def->slotList += funcDef;
    handleDefaultArguments(&def->slotList, funcDef);
    if (funcDef.revision > 0)
        ++def->revisionedMethods;

}

QByteArray Moc::lexemUntil(Token target)
{
    qsizetype from = index;
    until(target);
    QByteArray s;
    while (from <= index) {
        QByteArray n = symbols.at(from++-1).lexem();
        if (s.size() && n.size()) {
            char prev = s.at(s.size()-1);
            char next = n.at(0);
            if ((is_ident_char(prev) && is_ident_char(next))
                || (prev == '<' && next == ':')
                || (prev == '>' && next == '>'))
                s += ' ';
        }
        s += n;
    }
    return s;
}

bool Moc::until(Token target) {
    int braceCount = 0;
    int brackCount = 0;
    int parenCount = 0;
    int angleCount = 0;
    if (index) {
        switch(symbols.at(index-1).token) {
        case LBRACE: ++braceCount; break;
        case LBRACK: ++brackCount; break;
        case LPAREN: ++parenCount; break;
        case LANGLE: ++angleCount; break;
        default: break;
        }
    }

    //when searching commas within the default argument, we should take care of template depth (anglecount)
    // unfortunately, we do not have enough semantic information to know if '<' is the operator< or
    // the beginning of a template type. so we just use heuristics.
    qsizetype possible = -1;

    while (index < symbols.size()) {
        Token t = symbols.at(index++).token;
        switch (t) {
        case LBRACE: ++braceCount; break;
        case RBRACE: --braceCount; break;
        case LBRACK: ++brackCount; break;
        case RBRACK: --brackCount; break;
        case LPAREN: ++parenCount; break;
        case RPAREN: --parenCount; break;
        case LANGLE:
            if (parenCount == 0 && braceCount == 0)
                ++angleCount;
          break;
        case RANGLE:
            if (parenCount == 0 && braceCount == 0)
                --angleCount;
          break;
        case GTGT:
            if (parenCount == 0 && braceCount == 0) {
                angleCount -= 2;
                t = RANGLE;
            }
            break;
        default: break;
        }
        if (t == target
            && braceCount <= 0
            && brackCount <= 0
            && parenCount <= 0
            && (target != RANGLE || angleCount <= 0)) {
            if (target != COMMA || angleCount <= 0)
                return true;
            possible = index;
        }

        if (target == COMMA && t == EQ && possible != -1) {
            index = possible;
            return true;
        }

        if (braceCount < 0 || brackCount < 0 || parenCount < 0
            || (target == RANGLE && angleCount < 0)) {
            --index;
            break;
        }

        if (braceCount <= 0 && t == SEMIC) {
            // Abort on semicolon. Allow recovering bad template parsing (QTBUG-31218)
            break;
        }
    }

    if (target == COMMA && angleCount != 0 && possible != -1) {
        index = possible;
        return true;
    }

    return false;
}

void Moc::checkSuperClasses(ClassDef *def)
{
    Q_ASSERT(!def->superclassList.isEmpty());
    const QByteArray &firstSuperclass = def->superclassList.at(0).first;

    if (!knownQObjectClasses.contains(firstSuperclass)) {
        // enable once we /require/ include paths
#if 0
        const QByteArray msg
                = "Class "
                + def->className
                + " contains the Q_OBJECT macro and inherits from "
                + def->superclassList.value(0)
                + " but that is not a known QObject subclass. You may get compilation errors.";
        warning(msg.constData());
#endif
        return;
    }

    auto isRegisteredInterface = [&def](QByteArrayView super) {
        auto matchesSuperClass = [&super](const auto &ifaces) {
            return !ifaces.isEmpty() && ifaces.first().className == super;
        };
        return std::any_of(def->interfaceList.cbegin(), def->interfaceList.cend(), matchesSuperClass);
    };

    const auto end = def->superclassList.cend();
    auto it = def->superclassList.cbegin() + 1;
    for (; it != end; ++it) {
        const QByteArray &superClass = it->first;
        if (knownQObjectClasses.contains(superClass)) {
            const QByteArray msg
                    = "Class "
                    + def->classname
                    + " inherits from two QObject subclasses "
                    + firstSuperclass
                    + " and "
                    + superClass
                    + ". This is not supported!";
            warning(msg.constData());
        }

        if (interface2IdMap.contains(superClass)) {
            if (!isRegisteredInterface(superClass)) {
                const QByteArray msg
                        = "Class "
                        + def->classname
                        + " implements the interface "
                        + superClass
                        + " but does not list it in Q_INTERFACES. qobject_cast to "
                        + superClass
                        + " will not work!";
                warning(msg.constData());
            }
        }
    }
}

void Moc::checkProperties(ClassDef *cdef)
{
    //
    // specify get function, for compatibility we accept functions
    // returning pointers, or const char * for QByteArray.
    //
    QDuplicateTracker<QByteArray> definedProperties(cdef->propertyList.size());
    for (int i = 0; i < cdef->propertyList.size(); ++i) {
        PropertyDef &p = cdef->propertyList[i];
        if (definedProperties.hasSeen(p.name)) {
            QByteArray msg = "The property '" + p.name + "' is defined multiple times in class " + cdef->classname + ".";
            warning(msg.constData());
        }

        if (p.read.isEmpty() && p.member.isEmpty() && p.bind.isEmpty()) {
            const qsizetype rewind = index;
            if (p.location >= 0)
                index = p.location;
            QByteArray msg = "Property declaration " + p.name + " has neither an associated QProperty<> member"
                             ", nor a READ accessor function nor an associated MEMBER variable. The property will be invalid.";
            warning(msg.constData());
            index = rewind;
            if (p.write.isEmpty()) {
                cdef->propertyList.removeAt(i);
                --i;
            }
            continue;
        }

        for (const FunctionDef &f : std::as_const(cdef->publicList)) {
            if (f.name != p.read)
                continue;
            if (!f.isConst) // get  functions must be const
                continue;
            if (f.arguments.size()) // and must not take any arguments
                continue;
            PropertyDef::Specification spec = PropertyDef::ValueSpec;
            QByteArray tmp = f.normalizedType;
            if (p.type == "QByteArray" && tmp == "const char *")
                tmp = "QByteArray";
            if (tmp.left(6) == "const ")
                tmp = tmp.mid(6);
            if (p.type != tmp && tmp.endsWith('*')) {
                tmp.chop(1);
                spec = PropertyDef::PointerSpec;
            } else if (f.type.name.endsWith('&')) { // raw type, not normalized type
                spec = PropertyDef::ReferenceSpec;
            }
            if (p.type != tmp)
                continue;
            p.gspec = spec;
            break;
        }
        if (!p.notify.isEmpty()) {
            int notifyId = -1;
            for (int j = 0; j < cdef->signalList.size(); ++j) {
                const FunctionDef &f = cdef->signalList.at(j);
                if (f.name != p.notify) {
                    continue;
                } else {
                    notifyId = j /* Signal indexes start from 0 */;
                    break;
                }
            }
            p.notifyId = notifyId;
            if (notifyId == -1) {
                int index = cdef->nonClassSignalList.indexOf(p.notify);
                if (index == -1) {
                    cdef->nonClassSignalList << p.notify;
                    p.notifyId = -1 - cdef->nonClassSignalList.size();
                } else {
                    p.notifyId = int(-2 - index);
                }
            }
        }
    }
}
#endif // -- QScxml

QJsonObject ClassDef::toJson() const
{
    QJsonObject cls;
    cls["className"_L1] = QString::fromUtf8(classname.constData());
    cls["qualifiedClassName"_L1] = QString::fromUtf8(qualified.constData());

    QJsonArray classInfos;
    for (const auto &info: std::as_const(classInfoList)) {
        QJsonObject infoJson;
        infoJson["name"_L1] = QString::fromUtf8(info.name);
        infoJson["value"_L1] = QString::fromUtf8(info.value);
        classInfos.append(infoJson);
    }

    if (classInfos.size())
        cls["classInfos"_L1] = classInfos;

    const auto appendFunctions = [&cls](const QString &type, const QList<FunctionDef> &funcs) {
        QJsonArray jsonFuncs;

        for (const FunctionDef &fdef: funcs)
            jsonFuncs.append(fdef.toJson());

        if (!jsonFuncs.isEmpty())
            cls[type] = jsonFuncs;
    };

    appendFunctions("signals"_L1, signalList);
    appendFunctions("slots"_L1, slotList);
    appendFunctions("constructors"_L1, constructorList);
    appendFunctions("methods"_L1, methodList);

    QJsonArray props;

    for (const PropertyDef &propDef: std::as_const(propertyList))
        props.append(propDef.toJson());

    if (!props.isEmpty())
        cls["properties"_L1] = props;

    if (hasQObject)
        cls["object"_L1] = true;
    if (hasQGadget)
        cls["gadget"_L1] = true;
    if (hasQNamespace)
        cls["namespace"_L1] = true;

    QJsonArray superClasses;

    for (const auto &super: std::as_const(superclassList)) {
        const auto name = super.first;
        const auto access = super.second;
        QJsonObject superCls;
        superCls["name"_L1] = QString::fromUtf8(name);
        FunctionDef::accessToJson(&superCls, access);
        superClasses.append(superCls);
    }

    if (!superClasses.isEmpty())
        cls["superClasses"_L1] = superClasses;

    QJsonArray enums;
    for (const EnumDef &enumDef: std::as_const(enumList))
        enums.append(enumDef.toJson(*this));
    if (!enums.isEmpty())
        cls["enums"_L1] = enums;

    QJsonArray ifaces;
    for (const QList<Interface> &ifaceList : interfaceList) {
        QJsonArray jsonList;
        for (const Interface &iface: ifaceList) {
            QJsonObject ifaceJson;
            ifaceJson["id"_L1] = QString::fromUtf8(iface.interfaceId);
            ifaceJson["className"_L1] = QString::fromUtf8(iface.className);
            jsonList.append(ifaceJson);
        }
        ifaces.append(jsonList);
    }
    if (!ifaces.isEmpty())
        cls["interfaces"_L1] = ifaces;

    return cls;
}

QJsonObject FunctionDef::toJson() const
{
    QJsonObject fdef;
    fdef["name"_L1] = QString::fromUtf8(name);
    if (!tag.isEmpty())
        fdef["tag"_L1] = QString::fromUtf8(tag);
    fdef["returnType"_L1] = QString::fromUtf8(normalizedType);

    QJsonArray args;
    for (const ArgumentDef &arg: arguments)
        args.append(arg.toJson());

    if (!args.isEmpty())
        fdef["arguments"_L1] = args;

    accessToJson(&fdef, access);

    if (revision > 0)
        fdef["revision"_L1] = revision;

    if (wasCloned)
        fdef["isCloned"_L1] = true;

    return fdef;
}

void FunctionDef::accessToJson(QJsonObject *obj, FunctionDef::Access acs)
{
    switch (acs) {
    case Private: (*obj)["access"_L1] = "private"_L1; break;
    case Public: (*obj)["access"_L1] = "public"_L1; break;
    case Protected: (*obj)["access"_L1] = "protected"_L1; break;
    }
}

QJsonObject ArgumentDef::toJson() const
{
    QJsonObject arg;
    arg["type"_L1] = QString::fromUtf8(normalizedType);
    if (!name.isEmpty())
        arg["name"_L1] = QString::fromUtf8(name);
    return arg;
}

QJsonObject PropertyDef::toJson() const
{
    QJsonObject prop;
    prop["name"_L1] = QString::fromUtf8(name);
    prop["type"_L1] = QString::fromUtf8(type);

    const auto jsonify = [&prop](const char *str, const QByteArray &member) {
        if (!member.isEmpty())
            prop[QLatin1StringView(str)] = QString::fromUtf8(member);
    };

    jsonify("member", member);
    jsonify("read", read);
    jsonify("write", write);
    jsonify("bindable", bind);
    jsonify("reset", reset);
    jsonify("notify", notify);
    jsonify("privateClass", inPrivateClass);

    const auto jsonifyBoolOrString = [&prop](const char *str, const QByteArray &boolOrString) {
        QJsonValue value;
        if (boolOrString == "true")
            value = true;
        else if (boolOrString == "false")
            value = false;
        else
            value = QString::fromUtf8(boolOrString); // function name to query at run-time
        prop[QLatin1StringView(str)] = value;
    };

    jsonifyBoolOrString("designable", designable);
    jsonifyBoolOrString("scriptable", scriptable);
    jsonifyBoolOrString("stored", stored);
    jsonifyBoolOrString("user", user);

    prop["constant"_L1] = constant;
    prop["final"_L1] = final;
    prop["required"_L1] = required;
    prop["index"_L1] = relativeIndex;
    if (revision > 0)
        prop["revision"_L1] = revision;

    return prop;
}

QJsonObject EnumDef::toJson(const ClassDef &cdef) const
{
    QJsonObject def;
    def["name"_L1] = QString::fromUtf8(name);
    if (!enumName.isEmpty())
        def["alias"_L1] = QString::fromUtf8(enumName);
    if (!type.isEmpty())
        def["type"_L1] = QString::fromUtf8(type);
    def["isFlag"_L1] = cdef.enumDeclarations.value(name);
    def["isClass"_L1] = isEnumClass;

    QJsonArray valueArr;
    for (const QByteArray &value: values)
        valueArr.append(QString::fromUtf8(value));
    if (!valueArr.isEmpty())
        def["values"_L1] = valueArr;

    return def;
}

QByteArray EnumDef::qualifiedType(const ClassDef *cdef) const
{
    if (name == cdef->classname) {
        // The name of the enclosing namespace is the same as the enum class name
        if (cdef->qualified.contains("::")) {
            // QTBUG-112996, fully qualify by using cdef->qualified to disambiguate enum
            // class name and enclosing namespace, e.g.:
            // namespace A { namespace B { Q_NAMESPACE; enum class B { }; Q_ENUM_NS(B) } }
            return cdef->qualified % "::" % name;
        } else {
            // Just "B"; otherwise the compiler complains about the type "B::B" inside
            // "B::staticMetaObject" in the generated code; e.g.:
            // namespace B { Q_NAMESPACE; enum class B { }; Q_ENUM_NS(B) }
            return name;
        }
    }
    return cdef->classname % "::" % name;
}

QT_END_NAMESPACE
