Generating code

This commit is contained in:
Jakub Melka
2020-03-21 16:36:27 +01:00
parent 96d2e33692
commit bc6ca3fc46
15 changed files with 966 additions and 9 deletions

View File

@@ -17,6 +17,12 @@
#include "codegenerator.h"
#include <QFile>
#include <QTextStream>
#include <QTextLayout>
#include <QApplication>
#include <QFontMetrics>
namespace codegen
{
@@ -57,6 +63,18 @@ void GeneratedCodeStorage::removeFunction(GeneratedFunction* function)
m_functions.removeOne(function);
}
void GeneratedCodeStorage::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const
{
stream << Qt::endl << Qt::endl;
for (const QObject* object : m_functions)
{
const GeneratedFunction* generatedFunction = qobject_cast<const GeneratedFunction*>(object);
generatedFunction->generateCode(stream, parameters);
stream << Qt::endl << Qt::endl;
}
}
QObject* Serializer::load(const QDomElement& element, QObject* parent)
{
QString className = element.attribute("class");
@@ -223,6 +241,95 @@ void CodeGenerator::store(QDomDocument& document)
}
}
void CodeGenerator::generateCode(QString headerName, QString sourceName) const
{
QString startMark = "/* START GENERATED CODE */";
QString endMark = "/* END GENERATED CODE */";
QString className = "PDFDocumentBuilder";
const int indent = 4;
QFile headerFile(headerName);
if (headerFile.exists())
{
if (headerFile.open(QFile::ReadOnly))
{
QString utfCode = QString::fromUtf8(headerFile.readAll());
headerFile.close();
int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length();
int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive);
QString frontPart = utfCode.left(startIndex);
QString backPart = utfCode.mid(endIndex);
QString headerGeneratedCode = generateHeader(indent);
QString allCode = frontPart + headerGeneratedCode + backPart;
headerFile.open(QFile::WriteOnly | QFile::Truncate);
headerFile.write(allCode.toUtf8());
headerFile.close();
}
}
QFile sourceFile(sourceName);
if (sourceFile.exists())
{
if (sourceFile.open(QFile::ReadOnly))
{
QString utfCode = QString::fromUtf8(sourceFile.readAll());
sourceFile.close();
int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length();
int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive);
QString frontPart = utfCode.left(startIndex);
QString backPart = utfCode.mid(endIndex);
QString sourceGeneratedCode = generateSource(className, indent);
QString allCode = frontPart + sourceGeneratedCode + backPart;
sourceFile.open(QFile::WriteOnly | QFile::Truncate);
sourceFile.write(allCode.toUtf8());
sourceFile.close();
}
}
}
QString CodeGenerator::generateHeader(int indent) const
{
QByteArray ba;
{
QTextStream stream(&ba, QIODevice::WriteOnly);
stream.setCodec("UTF-8");
stream.setRealNumberPrecision(3);
stream.setRealNumberNotation(QTextStream::FixedNotation);
CodeGeneratorParameters parameters;
parameters.header = true;
parameters.indent = indent;
m_storage->generateCode(stream, parameters);
}
return QString::fromUtf8(ba);
}
QString CodeGenerator::generateSource(QString className, int indent) const
{
QByteArray ba;
{
QTextStream stream(&ba, QIODevice::WriteOnly);
stream.setCodec("UTF-8");
stream.setRealNumberPrecision(3);
stream.setRealNumberNotation(QTextStream::FixedNotation);
CodeGeneratorParameters parameters;
parameters.header = false;
parameters.indent = indent;
parameters.className = className;
m_storage->generateCode(stream, parameters);
}
return QString::fromUtf8(ba);
}
GeneratedFunction::GeneratedFunction(QObject* parent) :
BaseClass(parent)
{
@@ -274,6 +381,91 @@ GeneratedFunction* GeneratedFunction::clone(QObject* parent)
return qobject_cast<GeneratedFunction*>(Serializer::clone(this, parent));
}
void GeneratedFunction::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const
{
QStringList parameterCaptions;
QStringList parameterTexts;
std::function<void (const GeneratedBase*, Pass)> gatherParameters = [&](const GeneratedBase* object, Pass pass)
{
if (pass != Pass::Enter)
{
return;
}
if (const GeneratedParameter* generatedParameter = qobject_cast<const GeneratedParameter*>(object))
{
parameterCaptions << QString("%1 %2").arg(generatedParameter->getParameterName(), generatedParameter->getParameterDescription());
parameterTexts << QString("%1 %2").arg(getCppType(generatedParameter->getParameterDataType()), generatedParameter->getParameterName());
}
};
applyFunctor(gatherParameters);
if (parameters.header)
{
// Generate header source code
// Function comments
for (const QString& string : getFormattedTextWithLayout("/// ", "/// ", getFunctionDescription(), parameters.indent))
{
stream << string << Qt::endl;
}
// Function parameter comments
for (const QString& parameterCaption : parameterCaptions)
{
for (const QString& string : getFormattedTextWithLayout("/// \\param ", "/// ", parameterCaption, parameters.indent))
{
stream << string << Qt::endl;
}
}
// Function declaration
QString functionHeader = QString("%1 %2(").arg(getCppType(getReturnType()), getFunctionName());
QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space));
QString functionFooter = QString(");");
for (QString& str : parameterTexts)
{
str += ",";
}
parameterTexts.back().replace(",", functionFooter);
QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, parameters.indent);
for (const QString& functionDeclarationItem : functionDeclaration)
{
stream << functionDeclarationItem << Qt::endl;
}
}
else
{
// Generate c++ source code
QString functionHeader = QString("%1 %2::%3(").arg(getCppType(getReturnType()), parameters.className, getFunctionName());
QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space));
QString functionFooter = QString(")");
for (QString& str : parameterTexts)
{
str += ",";
}
parameterTexts.back().replace(",", functionFooter);
QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, 0);
for (const QString& functionDeclarationItem : functionDeclaration)
{
stream << functionDeclarationItem << Qt::endl;
}
QString indent(parameters.indent, QChar(QChar::Space));
stream << "{" << Qt::endl;
stream << indent << "PDFObjectFactory objectBuilder;" << Qt::endl << Qt::endl;
generateSourceCode(stream, parameters);
stream << "}" << Qt::endl;
}
}
bool GeneratedFunction::hasField(GeneratedBase::FieldType fieldType) const
{
switch (fieldType)
@@ -552,6 +744,86 @@ void GeneratedAction::setCode(const QString& code)
m_code = code;
}
void GeneratedAction::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const
{
if (pass == Pass::Enter && getActionType() == Code)
{
QString indent(parameters.indent, QChar(QChar::Space));
QStringList lines = getCode().split(QChar('\n'), Qt::KeepEmptyParts, Qt::CaseInsensitive);
for (QString string : lines)
{
string = string.trimmed();
if (!string.isEmpty())
{
stream << indent << string << Qt::endl;
}
else
{
stream << Qt::endl;
}
}
}
if (pass == Pass::Leave && getActionType() == CreateObject)
{
QString indent(parameters.indent, QChar(QChar::Space));
switch (getVariableType())
{
case _PDFObject:
{
stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = objectBuilder.takeObject();";
break;
}
case _PDFObjectReference:
{
stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = addObject(objectBuilder.takeObject());";
break;
}
default:
Q_ASSERT(false);
break;
}
}
if (pass == Pass::Leave && getActionType() != Parameters && !parameters.isLastItem)
{
stream << Qt::endl;
}
}
void GeneratedBase::generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const
{
generateSourceCodeImpl(stream, parameters, Pass::Enter);
for (const QObject* object : m_items)
{
const GeneratedBase* generatedBase = qobject_cast<const GeneratedBase*>(object);
CodeGeneratorParameters parametersTemporary = parameters;
parametersTemporary.isFirstItem = object == m_items.front();
parametersTemporary.isLastItem = object == m_items.back();
generatedBase->generateSourceCode(stream, parametersTemporary);
}
generateSourceCodeImpl(stream, parameters, Pass::Leave);
}
void GeneratedBase::applyFunctor(std::function<void (const GeneratedBase*, Pass)>& functor) const
{
functor(this, Pass::Enter);
for (const QObject* object : m_items)
{
const GeneratedBase* generatedBase = qobject_cast<const GeneratedBase*>(object);
generatedBase->applyFunctor(functor);
}
functor(this, Pass::Leave);
}
bool GeneratedBase::canPerformOperation(Operation operation) const
{
const bool isFunction = qobject_cast<const GeneratedFunction*>(this) != nullptr;
@@ -679,6 +951,73 @@ void GeneratedBase::clearItems()
m_items.clear();
}
void GeneratedBase::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const
{
Q_UNUSED(stream);
Q_UNUSED(parameters);
Q_UNUSED(pass);
}
QString GeneratedBase::getCppType(DataType type) const
{
return Serializer::convertEnumToString(type).mid(1);
}
QStringList GeneratedBase::getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const
{
QFont font = QApplication::font();
QFontMetrics fontMetrics(font);
int usedLength = indent + qMax(firstPrefix.length(), prefix.length());
int length = 80 - usedLength;
QString testText(length, QChar('A'));
int width = fontMetrics.width(testText);
QTextLayout layout(text, font);
layout.setCacheEnabled(false);
QTextOption textOption = layout.textOption();
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
layout.setTextOption(textOption);
layout.beginLayout();
while (true)
{
QTextLine textLine = layout.createLine();
if (!textLine.isValid())
{
break;
}
textLine.setLineWidth(width);
}
layout.endLayout();
QStringList texts;
const int lineCount = layout.lineCount();
for (int i = 0; i < lineCount; ++i)
{
QTextLine line = layout.lineAt(i);
texts << text.mid(line.textStart(), line.textLength());
}
return getFormattedTextBlock(firstPrefix, prefix, texts, indent);
}
QStringList GeneratedBase::getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const
{
QString indentText(indent, QChar(QChar::Space));
firstPrefix.prepend(indentText);
prefix.prepend(indentText);
QStringList result;
QString currentPrefix = firstPrefix;
for (const QString& text : texts)
{
result << QString("%1%2").arg(currentPrefix, text);
currentPrefix = prefix;
}
return result;
}
GeneratedPDFObject::GeneratedPDFObject(QObject* parent) :
BaseClass(parent)
{
@@ -844,6 +1183,89 @@ void GeneratedPDFObject::setDictionaryItemName(const QString& dictionaryItemName
m_dictionaryItemName = dictionaryItemName;
}
void GeneratedPDFObject::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const
{
QString indent(parameters.indent, QChar(QChar::Space));
QString writeTo("objectBuilder << ");
switch (getObjectType())
{
case codegen::GeneratedPDFObject::Object:
{
if (pass == Pass::Enter)
{
stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl;
}
break;
}
case codegen::GeneratedPDFObject::ArraySimple:
{
if (pass == Pass::Enter)
{
stream << indent << "objectBuilder.beginArray();" << Qt::endl;
stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl;
stream << indent << "objectBuilder.endArray();" << Qt::endl;
}
break;
}
case codegen::GeneratedPDFObject::ArrayComplex:
{
if (pass == Pass::Enter)
{
stream << indent << "objectBuilder.beginArray();" << Qt::endl;
}
if (pass == Pass::Leave)
{
stream << indent << "objectBuilder.endArray();" << Qt::endl;
}
break;
}
case codegen::GeneratedPDFObject::Dictionary:
{
if (pass == Pass::Enter)
{
stream << indent << "objectBuilder.beginDictionary();" << Qt::endl;
}
if (pass == Pass::Leave)
{
stream << indent << "objectBuilder.endDictionary();" << Qt::endl;
}
break;
}
case codegen::GeneratedPDFObject::DictionaryItemSimple:
{
if (pass == Pass::Enter)
{
stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl;
stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl;
stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl;
}
break;
}
case codegen::GeneratedPDFObject::DictionaryItemComplex:
{
if (pass == Pass::Enter)
{
stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl;
}
if (pass == Pass::Leave)
{
stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl;
}
break;
}
default:
Q_ASSERT(false);
break;
}
}
GeneratedParameter::GeneratedParameter(QObject* parent) :
BaseClass(parent)
{