qmltc: learn to generate minimum amount of code
- Finish output ir and the writer for it - Add simple compiler logic to generate dull class declarations Task-number: QTBUG-84368 Change-Id: I75be0f44b84ad3cfb1d862072d58b3bf87063d31 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
parent
8dab4a81a0
commit
ee469d528a
|
@ -1009,6 +1009,8 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
|
|||
|
||||
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
|
||||
addDefaultProperties();
|
||||
if (m_currentScope->scopeType() == QQmlJSScope::QMLScope)
|
||||
m_qmlTypes.append(m_currentScope);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ public:
|
|||
return m_signalHandlers;
|
||||
}
|
||||
QSet<QQmlJSScope::ConstPtr> literalScopesToCheck() const { return m_literalScopesToCheck; }
|
||||
QList<QQmlJSScope::ConstPtr> qmlScopes() const { return m_qmlTypes; }
|
||||
|
||||
static QString implicitImportDirectory(
|
||||
const QString &localFile, QQmlJSResourceFileMapper *mapper);
|
||||
|
@ -152,6 +153,7 @@ protected:
|
|||
QQmlJSScope::ConstPtr m_globalScope;
|
||||
QHash<QString, QQmlJSScope::ConstPtr> m_scopesById;
|
||||
QHash<QString, QQmlJSScope::ConstPtr> m_rootScopeImports;
|
||||
QList<QQmlJSScope::ConstPtr> m_qmlTypes;
|
||||
|
||||
// Maps all qmlNames to the source location of their import
|
||||
QMultiHash<QString, QQmlJS::SourceLocation> m_importTypeLocationMap;
|
||||
|
|
|
@ -70,10 +70,16 @@ void tst_qmltc::initTestCase()
|
|||
void tst_qmltc::qmlNameConflictResolution()
|
||||
{
|
||||
// we can include user-renamed files
|
||||
QQmlEngine e;
|
||||
// Note: the C++ class name is derived from the source qml file path, not
|
||||
// the output .h/.cpp, so: NameConflict class name for NameConflict.qml
|
||||
q_qmltc::NameConflict created(&e); // note: declared in ResolvedNameConflict.h
|
||||
}
|
||||
|
||||
void tst_qmltc::helloWorld()
|
||||
{
|
||||
QQmlEngine e;
|
||||
q_qmltc::HelloWorld created(&e);
|
||||
QSKIP("Nothing is supported yet.");
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ qt_internal_add_tool(${target_name}
|
|||
qmltccodewriter.h qmltccodewriter.cpp
|
||||
qmltcoutputir.h
|
||||
qmltctyperesolver.h
|
||||
qmltcvisitor.h
|
||||
qmltcvisitor.h qmltcvisitor.cpp
|
||||
qmltccompiler.h qmltccompiler.cpp
|
||||
DEFINES
|
||||
QT_NO_CAST_FROM_ASCII
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "qmltccommandlineutils.h"
|
||||
#include "qmltccompiler.h"
|
||||
#include "qmltcvisitor.h"
|
||||
|
||||
#include <QtQml/private/qqmlirbuilder_p.h>
|
||||
#include <private/qqmljscompiler_p.h>
|
||||
|
@ -43,6 +44,11 @@
|
|||
|
||||
#include <cstdio>
|
||||
|
||||
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
|
||||
{
|
||||
logger.setCategoryError(Log_Compiler, true);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Produce reliably the same output for the same input by disabling QHash's
|
||||
|
@ -158,6 +164,7 @@ int main(int argc, char **argv)
|
|||
|
||||
QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr };
|
||||
QQmlJSLogger logger(url, sourceCode, /* silent */ false);
|
||||
setupLogger(logger);
|
||||
QmltcVisitor visitor(&importer, &logger, implicitImportDirectory, qmltypesFiles);
|
||||
// Type resolving is only static here due the inability to resolve parent
|
||||
// properties dynamically (QTBUG-95530). Indirect type storage is used as
|
||||
|
@ -167,7 +174,7 @@ int main(int argc, char **argv)
|
|||
QQmlJSTypeResolver::Static, &logger };
|
||||
typeResolver.init(visitor);
|
||||
|
||||
QmltcCompiler compiler(url, &typeResolver, &logger);
|
||||
QmltcCompiler compiler(url, &typeResolver, &visitor, &logger);
|
||||
compiler.compile(info);
|
||||
|
||||
if (logger.hasWarnings() || logger.hasErrors()) {
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
#include <QtCore/qfileinfo.h>
|
||||
#include <QtCore/qstringbuilder.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static QString urlToMacro(const QString &url)
|
||||
|
@ -39,6 +41,59 @@ static QString urlToMacro(const QString &url)
|
|||
return u"Q_QMLTC_" + fi.baseName().toUpper();
|
||||
}
|
||||
|
||||
static QString getFunctionCategory(const QmltcMethodBase &method)
|
||||
{
|
||||
QString category;
|
||||
switch (method.access) {
|
||||
case QQmlJSMetaMethod::Private:
|
||||
category = u"private"_qs;
|
||||
break;
|
||||
case QQmlJSMetaMethod::Protected:
|
||||
category = u"protected"_qs;
|
||||
break;
|
||||
case QQmlJSMetaMethod::Public:
|
||||
category = u"public"_qs;
|
||||
break;
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
static QString getFunctionCategory(const QmltcMethod &method)
|
||||
{
|
||||
QString category = getFunctionCategory(static_cast<const QmltcMethodBase &>(method));
|
||||
switch (method.type) {
|
||||
case QQmlJSMetaMethod::Signal:
|
||||
category = u"Q_SIGNALS"_qs;
|
||||
break;
|
||||
case QQmlJSMetaMethod::Slot:
|
||||
category += u" Q_SLOTS"_qs;
|
||||
break;
|
||||
case QQmlJSMetaMethod::Method:
|
||||
break;
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
static std::pair<QString, QString> functionSignatures(const QmltcMethodBase &method)
|
||||
{
|
||||
const QString name = method.name;
|
||||
const QList<QmltcVariable> ¶meterList = method.parameterList;
|
||||
|
||||
QStringList headerParamList;
|
||||
QStringList cppParamList;
|
||||
for (const QmltcVariable &variable : parameterList) {
|
||||
const QString commonPart = variable.cppType + u" " + variable.name;
|
||||
cppParamList << commonPart;
|
||||
headerParamList << commonPart;
|
||||
if (!variable.defaultValue.isEmpty())
|
||||
headerParamList.back() += u" = " + variable.defaultValue;
|
||||
}
|
||||
|
||||
const QString headerSignature = name + u"(" + headerParamList.join(u", "_qs) + u")";
|
||||
const QString cppSignature = name + u"(" + cppParamList.join(u", "_qs) + u")";
|
||||
return { headerSignature, cppSignature };
|
||||
}
|
||||
|
||||
void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath,
|
||||
const QString &hPath, const QString &cppPath,
|
||||
const QSet<QString> &requiredCppIncludes)
|
||||
|
@ -120,12 +175,177 @@ static void writeToFile(const QString &path, const QByteArray &data)
|
|||
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &program)
|
||||
{
|
||||
writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.includes);
|
||||
// TODO: keep the "NOT IMPLEMENTED" as long as we don't actually compile
|
||||
// useful code
|
||||
code.rawAppendToHeader(u"/* QMLTC: NOT IMPLEMENTED */");
|
||||
code.rawAppendToCpp(u"/* QMLTC: NOT IMPLEMENTED */");
|
||||
|
||||
// forward declare all the types first
|
||||
for (const QmltcType &type : qAsConst(program.compiledTypes))
|
||||
code.rawAppendToHeader(u"class " + type.cppType + u";");
|
||||
// write all the types and their content
|
||||
for (const QmltcType &type : qAsConst(program.compiledTypes))
|
||||
write(code, type);
|
||||
writeGlobalFooter(code, program.url);
|
||||
|
||||
writeToFile(program.hPath, code.code().header.toUtf8());
|
||||
writeToFile(program.cppPath, code.code().cpp.toUtf8());
|
||||
}
|
||||
|
||||
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type)
|
||||
{
|
||||
const auto constructClassString = [&]() {
|
||||
QString str = u"class " + type.cppType;
|
||||
if (!type.baseClasses.isEmpty())
|
||||
str += u" : public " + type.baseClasses.join(u", public "_qs);
|
||||
return str;
|
||||
};
|
||||
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
code.rawAppendToCpp(u""); // blank line
|
||||
|
||||
code.rawAppendToHeader(constructClassString());
|
||||
code.rawAppendToHeader(u"{");
|
||||
for (const QString &mocLine : qAsConst(type.mocCode))
|
||||
code.rawAppendToHeader(mocLine, 1);
|
||||
|
||||
QmltcOutputWrapper::MemberNameScope typeScope(&code, type.cppType);
|
||||
Q_UNUSED(typeScope);
|
||||
{
|
||||
QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
|
||||
Q_UNUSED(headerIndent);
|
||||
|
||||
// special member functions
|
||||
code.rawAppendToHeader(u"protected:", -1);
|
||||
write(code, type.basicCtor);
|
||||
write(code, type.init);
|
||||
write(code, type.finalize);
|
||||
// NB: externalCtor might not be public when the type is QML singleton
|
||||
code.rawAppendToHeader(getFunctionCategory(type.fullCtor) + u":", -1);
|
||||
write(code, type.fullCtor);
|
||||
|
||||
// enums
|
||||
if (!type.enums.isEmpty()) {
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
code.rawAppendToHeader(u"public:", -1);
|
||||
}
|
||||
for (const auto &enumeration : qAsConst(type.enums))
|
||||
write(code, enumeration);
|
||||
|
||||
// child types
|
||||
if (!type.children.isEmpty())
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
for (const auto &child : qAsConst(type.children))
|
||||
write(code, child);
|
||||
|
||||
// variables (mostly properties)
|
||||
if (!type.variables.isEmpty()) {
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
code.rawAppendToHeader(u"protected:", -1); // TODO: or private?
|
||||
}
|
||||
for (const auto &variable : qAsConst(type.variables))
|
||||
write(code, variable);
|
||||
|
||||
// functions (special case due to functions/signals/slots, etc.)
|
||||
QHash<QString, QList<const QmltcMethod *>> functionsByCategory;
|
||||
for (const auto &function : qAsConst(type.functions))
|
||||
functionsByCategory[getFunctionCategory(function)].append(&function);
|
||||
|
||||
if (!functionsByCategory.isEmpty())
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
for (auto it = functionsByCategory.cbegin(); it != functionsByCategory.cend(); ++it) {
|
||||
code.rawAppendToHeader(it.key() + u":", -1);
|
||||
for (const QmltcMethod *function : qAsConst(it.value()))
|
||||
write(code, *function);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.documentRootType) {
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
code.rawAppendToHeader(u"private:");
|
||||
code.rawAppendToHeader(u"friend class " + *type.documentRootType + u";", 1);
|
||||
}
|
||||
|
||||
if (type.typeCount) {
|
||||
code.rawAppendToHeader(u""); // blank line
|
||||
code.rawAppendToHeader(u"public:");
|
||||
code.rawAppendToHeader(u"constexpr static uint qmltc_typeCount = "
|
||||
+ QString::number(*type.typeCount) + u";",
|
||||
1);
|
||||
}
|
||||
|
||||
code.rawAppendToHeader(u"};");
|
||||
}
|
||||
|
||||
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcEnum &enumeration)
|
||||
{
|
||||
code.rawAppendToHeader(u"enum " + enumeration.cppType + u" {");
|
||||
for (qsizetype i = 0; i < enumeration.keys.size(); ++i) {
|
||||
QString str;
|
||||
if (enumeration.values.isEmpty()) {
|
||||
str += enumeration.keys.at(i) + u",";
|
||||
} else {
|
||||
str += enumeration.keys.at(i) + u" = " + enumeration.values.at(i) + u",";
|
||||
}
|
||||
code.rawAppendToHeader(str, 1);
|
||||
}
|
||||
code.rawAppendToHeader(u"};");
|
||||
}
|
||||
|
||||
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method)
|
||||
{
|
||||
const auto [hSignature, cppSignature] = functionSignatures(method);
|
||||
// Note: augment return type with preambles in declaration
|
||||
code.rawAppendToHeader(method.returnType + u" " + hSignature + u";");
|
||||
|
||||
// do not generate method implementation if it is a signal
|
||||
const auto methodType = method.type;
|
||||
if (methodType != QQmlJSMetaMethod::Signal) {
|
||||
code.rawAppendToCpp(u""); // blank line
|
||||
code.rawAppendToCpp(method.returnType);
|
||||
code.rawAppendSignatureToCpp(cppSignature);
|
||||
code.rawAppendToCpp(u"{");
|
||||
{
|
||||
QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
|
||||
Q_UNUSED(cppIndent);
|
||||
for (const QString &line : qAsConst(method.body))
|
||||
code.rawAppendToCpp(line);
|
||||
}
|
||||
code.rawAppendToCpp(u"}");
|
||||
}
|
||||
}
|
||||
|
||||
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor)
|
||||
{
|
||||
const auto [hSignature, cppSignature] = functionSignatures(ctor);
|
||||
const QString returnTypeWithSpace = ctor.returnType.isEmpty() ? u""_qs : ctor.returnType + u" ";
|
||||
|
||||
code.rawAppendToHeader(returnTypeWithSpace + hSignature + u";");
|
||||
|
||||
code.rawAppendToCpp(u""); // blank line
|
||||
if (!returnTypeWithSpace.isEmpty())
|
||||
code.rawAppendToCpp(returnTypeWithSpace);
|
||||
code.rawAppendSignatureToCpp(cppSignature);
|
||||
if (!ctor.initializerList.isEmpty()) {
|
||||
code.rawAppendToCpp(u":", 1);
|
||||
// double \n to make separate initializer list lines stand out more
|
||||
code.rawAppendToCpp(
|
||||
ctor.initializerList.join(u",\n\n" + u" "_qs.repeated(code.cppIndent + 1)), 1);
|
||||
}
|
||||
code.rawAppendToCpp(u"{");
|
||||
{
|
||||
QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
|
||||
Q_UNUSED(cppIndent);
|
||||
for (const QString &line : qAsConst(ctor.body))
|
||||
code.rawAppendToCpp(line);
|
||||
}
|
||||
code.rawAppendToCpp(u"}");
|
||||
}
|
||||
|
||||
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var)
|
||||
{
|
||||
const QString optionalPart = var.defaultValue.isEmpty() ? u""_qs : u" = " + var.defaultValue;
|
||||
code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";");
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -43,6 +43,11 @@ struct QmltcCodeWriter
|
|||
const QSet<QString> &requiredCppIncludes);
|
||||
static void writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath);
|
||||
static void write(QmltcOutputWrapper &code, const QmltcProgram &program);
|
||||
static void write(QmltcOutputWrapper &code, const QmltcType &type);
|
||||
static void write(QmltcOutputWrapper &code, const QmltcEnum &enumeration);
|
||||
static void write(QmltcOutputWrapper &code, const QmltcMethod &method);
|
||||
static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor);
|
||||
static void write(QmltcOutputWrapper &code, const QmltcVariable &var);
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -32,26 +32,113 @@
|
|||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QQmlJSLogger *logger)
|
||||
: m_url(url), m_typeResolver(resolver), m_logger(logger)
|
||||
Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
|
||||
|
||||
QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
|
||||
QQmlJSLogger *logger)
|
||||
: m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
|
||||
{
|
||||
Q_UNUSED(m_url);
|
||||
Q_UNUSED(m_typeResolver);
|
||||
Q_UNUSED(m_logger);
|
||||
Q_ASSERT(!hasErrors());
|
||||
}
|
||||
|
||||
void QmltcCompiler::compile(const QmltcCompilerInfo &info)
|
||||
{
|
||||
m_info = info;
|
||||
Q_ASSERT(!m_info.outputCppFile.isEmpty());
|
||||
Q_ASSERT(!m_info.outputHFile.isEmpty());
|
||||
Q_ASSERT(!m_info.resourcePath.isEmpty());
|
||||
|
||||
const QList<QQmlJSScope::ConstPtr> types = m_visitor->qmlScopes();
|
||||
QList<QmltcType> compiledTypes;
|
||||
compiledTypes.reserve(types.size());
|
||||
|
||||
for (const QQmlJSScope::ConstPtr &type : types) {
|
||||
compiledTypes.emplaceBack(); // creates empty type
|
||||
compileType(compiledTypes.back(), type);
|
||||
if (hasErrors())
|
||||
return;
|
||||
}
|
||||
|
||||
QmltcProgram program;
|
||||
program.url = m_url;
|
||||
program.cppPath = m_info.outputCppFile;
|
||||
program.hPath = m_info.outputHFile;
|
||||
program.compiledTypes = compiledTypes;
|
||||
|
||||
QmltcOutput out;
|
||||
QmltcOutputWrapper code(out);
|
||||
QmltcCodeWriter::write(code, program);
|
||||
}
|
||||
|
||||
void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type)
|
||||
{
|
||||
if (type->isSingleton()) {
|
||||
recordError(type->sourceLocation(), u"Singleton types are not supported"_qs);
|
||||
return;
|
||||
}
|
||||
|
||||
current.cppType = type->internalName();
|
||||
const QString baseClass = type->baseType()->internalName();
|
||||
|
||||
const bool documentRoot = (type == m_visitor->result());
|
||||
const bool baseTypeIsCompiledQml = false; // TODO: support this in QmltcTypeResolver
|
||||
|
||||
current.baseClasses = { baseClass };
|
||||
if (!documentRoot) {
|
||||
current.documentRootType = m_visitor->result()->internalName();
|
||||
} else {
|
||||
current.typeCount = int(m_visitor->qmlScopes().size());
|
||||
}
|
||||
|
||||
// add special member functions
|
||||
current.basicCtor.access = QQmlJSMetaMethod::Protected;
|
||||
current.init.access = QQmlJSMetaMethod::Protected;
|
||||
current.finalize.access = QQmlJSMetaMethod::Protected;
|
||||
current.fullCtor.access = QQmlJSMetaMethod::Public;
|
||||
|
||||
current.basicCtor.name = current.cppType;
|
||||
current.fullCtor.name = current.cppType;
|
||||
current.init.name = u"qmltc_init"_qs;
|
||||
current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_qs;
|
||||
current.finalize.name = u"qmltc_finalize"_qs;
|
||||
current.finalize.returnType = u"void"_qs;
|
||||
|
||||
QmltcVariable engine(u"QQmlEngine*"_qs, u"engine"_qs);
|
||||
QmltcVariable parent(u"QObject*"_qs, u"parent"_qs, u"nullptr"_qs);
|
||||
current.basicCtor.parameterList = { parent };
|
||||
current.fullCtor.parameterList = { engine, parent };
|
||||
QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_qs, u"parentContext"_qs);
|
||||
QmltcVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs);
|
||||
if (documentRoot) {
|
||||
current.init.parameterList = { engine, ctxtdata, finalizeFlag };
|
||||
current.finalize.parameterList = { engine, finalizeFlag };
|
||||
} else {
|
||||
current.init.parameterList = { engine, ctxtdata };
|
||||
current.finalize.parameterList = { engine };
|
||||
}
|
||||
|
||||
current.fullCtor.initializerList = { current.basicCtor.name + u"(" + parent.name + u")" };
|
||||
if (baseTypeIsCompiledQml) {
|
||||
// call parent's (QML type's) basic ctor from this. that one will take
|
||||
// care about QObject::setParent()
|
||||
current.basicCtor.initializerList = { baseClass + u"(" + parent.name + u")" };
|
||||
} else {
|
||||
// default call to ctor is enough, but QQml_setParent_noEvent() is
|
||||
// needed (note: faster? version of QObject::setParent())
|
||||
current.basicCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
|
||||
}
|
||||
|
||||
// compilation stub:
|
||||
current.fullCtor.body << u"Q_UNUSED(engine);"_qs;
|
||||
current.init.body << u"Q_UNUSED(engine);"_qs;
|
||||
current.init.body << u"Q_UNUSED(parentContext);"_qs;
|
||||
current.finalize.body << u"Q_UNUSED(engine);"_qs;
|
||||
if (documentRoot) {
|
||||
current.init.body << u"Q_UNUSED(canFinalize);"_qs;
|
||||
current.finalize.body << u"Q_UNUSED(canFinalize);"_qs;
|
||||
}
|
||||
current.init.body << u"return nullptr;"_qs;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "qmltctyperesolver.h"
|
||||
#include "qmltcvisitor.h"
|
||||
#include "qmltcoutputir.h"
|
||||
|
||||
#include <QtCore/qcommandlineparser.h>
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
|
@ -50,14 +51,33 @@ struct QmltcCompilerInfo
|
|||
class QmltcCompiler
|
||||
{
|
||||
public:
|
||||
QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QQmlJSLogger *logger);
|
||||
QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
|
||||
QQmlJSLogger *logger);
|
||||
void compile(const QmltcCompilerInfo &info);
|
||||
|
||||
private:
|
||||
QString m_url; // QML input file url
|
||||
QmltcTypeResolver *m_typeResolver = nullptr;
|
||||
QmltcVisitor *m_visitor = nullptr;
|
||||
QQmlJSLogger *m_logger = nullptr;
|
||||
QmltcCompilerInfo m_info {}; // miscellaneous input/output information
|
||||
|
||||
void compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type);
|
||||
|
||||
bool hasErrors() const { return m_logger->hasErrors(); } // TODO: count warnings as errors?
|
||||
void recordError(const QQmlJS::SourceLocation &location, const QString &message,
|
||||
QQmlJSLoggerCategory category = Log_Compiler)
|
||||
{
|
||||
// pretty much any compiler error is a critical error (we cannot
|
||||
// generate code - compilation fails)
|
||||
m_logger->logCritical(message, category, location);
|
||||
}
|
||||
void recordError(const QV4::CompiledData::Location &location, const QString &message,
|
||||
QQmlJSLoggerCategory category = Log_Compiler)
|
||||
{
|
||||
recordError(QQmlJS::SourceLocation { 0, 0, location.line, location.column }, message,
|
||||
category);
|
||||
}
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -34,14 +34,104 @@
|
|||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qset.h>
|
||||
|
||||
#include <private/qqmljsmetatypes_p.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
// Below are the classes that represent compiled QML types in a string data
|
||||
// form. These classes are used to generate C++ code.
|
||||
|
||||
// Represents C++ variable
|
||||
struct QmltcVariable
|
||||
{
|
||||
QString cppType; // C++ type of a variable
|
||||
QString name; // variable name
|
||||
QString defaultValue; // optional initialization value
|
||||
|
||||
QmltcVariable() = default;
|
||||
// special ctor for QList's emplace back
|
||||
QmltcVariable(const QString &t, const QString &n, const QString &v = QString())
|
||||
: cppType(t), name(n), defaultValue(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Represents QML -> C++ compiled enumeration type
|
||||
struct QmltcEnum
|
||||
{
|
||||
QString cppType; // C++ type of an enum
|
||||
QStringList keys; // enumerator keys
|
||||
QStringList values; // enumerator values
|
||||
|
||||
QmltcEnum() = default;
|
||||
QmltcEnum(const QString &t, const QStringList &ks, const QStringList &vs)
|
||||
: cppType(t), keys(ks), values(vs)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct QmltcMethodBase
|
||||
{
|
||||
QString returnType; // C++ return type
|
||||
QString name; // C++ function name
|
||||
QList<QmltcVariable> parameterList; // C++ function parameter list
|
||||
QStringList body; // C++ function code
|
||||
QQmlJSMetaMethod::Access access = QQmlJSMetaMethod::Public; // access specifier
|
||||
};
|
||||
|
||||
// Represents QML -> C++ compiled function
|
||||
struct QmltcMethod : QmltcMethodBase
|
||||
{
|
||||
QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type
|
||||
};
|
||||
|
||||
// Represents C++ ctor of a type
|
||||
struct QmltcCtor : QmltcMethodBase
|
||||
{
|
||||
QStringList initializerList; // C++ ctor's initializer list
|
||||
};
|
||||
|
||||
// Represents QML -> C++ compiled type
|
||||
struct QmltcType
|
||||
{
|
||||
QString cppType; // C++ type of the QML type
|
||||
QStringList baseClasses; // C++ type names of base classes
|
||||
QStringList mocCode; // Qt MOC code
|
||||
|
||||
// member types: enumerations and child types
|
||||
QList<QmltcEnum> enums;
|
||||
QList<QmltcType> children; // these are pretty much always empty
|
||||
|
||||
// special member functions:
|
||||
QmltcCtor basicCtor = {}; // does basic contruction
|
||||
QmltcCtor fullCtor = {}; // calls basicCtor, calls init
|
||||
QmltcMethod init = {}; // starts object initialization (context setup), calls finalize
|
||||
QmltcMethod finalize = {}; // finalizes object (bindings, special interface calls, etc.)
|
||||
|
||||
// member functions: methods, signals and slots
|
||||
QList<QmltcMethod> functions;
|
||||
// member variables: properties and just variables
|
||||
QList<QmltcVariable> variables;
|
||||
|
||||
// we want to use certain private/protected functions by document root, so
|
||||
// record it to make it a friend
|
||||
std::optional<QString> documentRootType;
|
||||
|
||||
// QML document root specific:
|
||||
std::optional<int> typeCount = 0; // the number of QML types defined in a document
|
||||
};
|
||||
|
||||
// Represents whole QML program, compiled to C++
|
||||
struct QmltcProgram
|
||||
{
|
||||
QString url;
|
||||
QString cppPath;
|
||||
QString hPath;
|
||||
QString url; // QML file url
|
||||
QString cppPath; // C++ output .cpp path
|
||||
QString hPath; // C++ output .h path
|
||||
QSet<QString> includes; // non-default C++ include files
|
||||
|
||||
QList<QmltcType> compiledTypes; // all QML types that are compiled to C++
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#ifndef QMLTCOUTPUTPRIMITIVES_H
|
||||
#define QMLTCOUTPUTPRIMITIVES_H
|
||||
|
||||
#include <QtCore/qstack.h>
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qstringbuilder.h>
|
||||
|
||||
|
@ -56,12 +57,45 @@ public:
|
|||
QmltcOutputWrapper(QmltcOutput &code) : m_code(code) { }
|
||||
const QmltcOutput &code() const { return m_code; }
|
||||
|
||||
QStack<QString> memberScopes; // member name scopes e.g. MyClass::MySubclass::
|
||||
int headerIndent = 0; // header indentation level
|
||||
int cppIndent = 0; // cpp indentation level
|
||||
|
||||
// manages current scope of the generated code, which is necessary for
|
||||
// cpp file generation. Example:
|
||||
// class MyClass { MyClass(); }; - in header
|
||||
// MyClass::MyClass() {} - in cpp
|
||||
// MemberNameScope makes sure "MyClass::" is recorded
|
||||
struct MemberNameScope
|
||||
{
|
||||
QmltcOutputWrapper *m_code;
|
||||
MemberNameScope(QmltcOutputWrapper *code, const QString &str) : m_code(code)
|
||||
{
|
||||
m_code->memberScopes.push(str);
|
||||
}
|
||||
~MemberNameScope() { m_code->memberScopes.pop(); }
|
||||
};
|
||||
|
||||
struct HeaderIndentationScope
|
||||
{
|
||||
QmltcOutputWrapper *m_code;
|
||||
HeaderIndentationScope(QmltcOutputWrapper *code) : m_code(code) { ++m_code->headerIndent; }
|
||||
~HeaderIndentationScope() { --m_code->headerIndent; }
|
||||
};
|
||||
|
||||
struct CppIndentationScope
|
||||
{
|
||||
QmltcOutputWrapper *m_code;
|
||||
CppIndentationScope(QmltcOutputWrapper *code) : m_code(code) { ++m_code->cppIndent; }
|
||||
~CppIndentationScope() { --m_code->cppIndent; }
|
||||
};
|
||||
|
||||
// appends string \a what with extra indentation \a extraIndent to current
|
||||
// header string
|
||||
template<typename String>
|
||||
void rawAppendToHeader(const String &what, int extraIndent = 0)
|
||||
{
|
||||
rawAppend(m_code.header, what, extraIndent);
|
||||
rawAppend(m_code.header, what, headerIndent + extraIndent);
|
||||
}
|
||||
|
||||
// appends string \a what with extra indentation \a extraIndent to current
|
||||
|
@ -69,7 +103,18 @@ public:
|
|||
template<typename String>
|
||||
void rawAppendToCpp(const String &what, int extraIndent = 0)
|
||||
{
|
||||
rawAppend(m_code.cpp, what, extraIndent);
|
||||
rawAppend(m_code.cpp, what, cppIndent + extraIndent);
|
||||
}
|
||||
|
||||
// special case of rawAppendToCpp that makes sure that string "foo()"
|
||||
// becomes "MyClass::foo()"
|
||||
template<typename String>
|
||||
void rawAppendSignatureToCpp(const String &what, int extraIndent = 0)
|
||||
{
|
||||
QString signatureScope;
|
||||
for (const auto &scope : memberScopes)
|
||||
signatureScope += scope + u"::";
|
||||
rawAppendToCpp(signatureScope + what, extraIndent);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the tools applications of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qmltcvisitor.h"
|
||||
|
||||
#include <QtCore/qfileinfo.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, int> &repetitions)
|
||||
{
|
||||
QString possibleName = pieces.join(u'_');
|
||||
const int count = repetitions[possibleName]++;
|
||||
if (count > 0)
|
||||
possibleName.append(u"_" + QString::number(count));
|
||||
return possibleName;
|
||||
}
|
||||
|
||||
QmltcVisitor::QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
|
||||
const QString &implicitImportDirectory, const QStringList &qmltypesFiles)
|
||||
: QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmltypesFiles)
|
||||
{
|
||||
m_qmlTypeNames.append(QFileInfo(logger->fileName()).baseName()); // put document root
|
||||
}
|
||||
|
||||
bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object)
|
||||
{
|
||||
if (!QQmlJSImportVisitor::visit(object))
|
||||
return false;
|
||||
|
||||
Q_ASSERT(m_currentScope->scopeType() == QQmlJSScope::QMLScope);
|
||||
Q_ASSERT(m_currentScope->internalName().isEmpty());
|
||||
Q_ASSERT(!m_currentScope->baseTypeName().isEmpty());
|
||||
|
||||
if (m_currentScope != m_exportedRootScope) // not document root
|
||||
m_qmlTypeNames.append(m_currentScope->baseTypeName());
|
||||
|
||||
// give C++-relevant internal names to QMLScopes, we can use them later in compiler
|
||||
m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *)
|
||||
{
|
||||
m_qmlTypeNames.removeLast();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qlist.h>
|
||||
|
||||
#include <QtQml/private/qqmlirbuilder_p.h>
|
||||
#include <private/qqmljsimportvisitor_p.h>
|
||||
|
@ -43,10 +44,17 @@ class QmltcVisitor : public QQmlJSImportVisitor
|
|||
public:
|
||||
QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
|
||||
const QString &implicitImportDirectory,
|
||||
const QStringList &qmltypesFiles = QStringList())
|
||||
: QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmltypesFiles)
|
||||
{
|
||||
}
|
||||
const QStringList &qmltypesFiles = QStringList());
|
||||
|
||||
bool visit(QQmlJS::AST::UiObjectDefinition *) override;
|
||||
void endVisit(QQmlJS::AST::UiObjectDefinition *) override;
|
||||
|
||||
// NB: overwrite result() method to return ConstPtr
|
||||
QQmlJSScope::ConstPtr result() const { return QQmlJSImportVisitor::result(); }
|
||||
|
||||
protected:
|
||||
QStringList m_qmlTypeNames; // names of QML types arranged as a stack
|
||||
QHash<QString, int> m_qmlTypeNameCounts;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
Loading…
Reference in New Issue