diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 59d4ae49..a8a7939d 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -60,7 +60,7 @@ public: QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); void loadGlobalCDefines(const QMap &defines); - void resetGlobalCDefines(); + void resetCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -93,10 +93,20 @@ private: QString curDefine; QHash fileCache; QHash errorMap; + + // The maps of define names to values/expressions that are available while parsing C defines. + // As the parser reads and evaluates more defines it will update these maps accordingly. + QMap knownDefineValues; + QMap knownDefineExpressions; + + // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. + // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. QMap globalDefineValues; QMap globalDefineExpressions; - int evaluateDefine(const QString&, const QString &, QMap*, QMap*); - QList tokenizeExpression(QString, QMap*, QMap*); + + int evaluateDefine(const QString &identifier, bool *ok = nullptr); + int evaluateExpression(const QString &expression); + QList tokenizeExpression(QString expression); QList generatePostfix(const QList &tokens); int evaluatePostfix(const QList &postfix); void recordError(const QString &message); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 3a8adf93..e9c6f1c0 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -16,7 +16,7 @@ const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((gl const QRegularExpression ParseUtil::re_poryRawSection("\\b(raw)\\s*`(?[^`]*)"); ParseUtil::ParseUtil() { - resetGlobalCDefines(); + resetCDefines(); } QString ParseUtil::pathWithRoot(const QString &path) { @@ -125,28 +125,48 @@ QList ParseUtil::parseAsm(const QString &filename) { return parsed; } -// 'identifier' is the name of the #define to evaluate, e.g. 'FOO' in '#define FOO (BAR+1)' -// 'expression' is the text of the #define to evaluate, e.g. '(BAR+1)' in '#define FOO (BAR+1)' -// 'knownValues' is a pointer to a map of identifier->values for defines that have already been evaluated. -// 'unevaluatedExpressions' is a pointer to a map of identifier->expressions for defines that have not been evaluated. If this map contains any -// identifiers found in 'expression' then this function will be called recursively to evaluate that define first. -// This function will maintain the passed maps appropriately as new #defines are evaluated. -int ParseUtil::evaluateDefine(const QString &identifier, const QString &expression, QMap *knownValues, QMap *unevaluatedExpressions) { - if (unevaluatedExpressions->contains(identifier)) - unevaluatedExpressions->remove(identifier); +// Try to evaluate the given #define/enum 'identifier' name using the information the parser has. +// If it recognizes the name as an identifier it's aware of (either from having parsed it or having been told about +// it using 'loadGlobalCDefines') it will evaluate it if necessary then return the resulting value and set 'ok' to true. +// Evaluated identifiers are cached, and will only be re-evaluated if the parser encounters a new expression for that identifier. +// If it doesn't recognize it, 'ok' will be set to false and it will return 0. +int ParseUtil::evaluateDefine(const QString &identifier, bool *ok) { + if (ok) *ok = true; - if (knownValues->contains(identifier)) - return knownValues->value(identifier); + // Global defines take precedence + if (this->globalDefineExpressions.contains(identifier)) { + int value = evaluateExpression(this->globalDefineExpressions.take(identifier)); + this->globalDefineValues.insert(identifier, value); + return value; + } + auto it = this->globalDefineValues.constFind(identifier); + if (it != this->globalDefineValues.constEnd()) { + return it.value(); + } - QList tokens = tokenizeExpression(expression, knownValues, unevaluatedExpressions); - QList postfixExpression = generatePostfix(tokens); - int value = evaluatePostfix(postfixExpression); + // Check known expressions before checking known values. + // If an identifier is redefined then we'll receive a new expression for it, and we want to make sure we re-evaluate it. + if (this->knownDefineExpressions.contains(identifier)) { + int value = evaluateExpression(this->knownDefineExpressions.take(identifier)); + this->knownDefineValues.insert(identifier, value); + return value; + } + it = this->knownDefineValues.constFind(identifier); + if (it != this->knownDefineValues.constEnd()) { + return it.value(); + } - knownValues->insert(identifier, value); - return value; + if (ok) *ok = false; + return 0; } -QList ParseUtil::tokenizeExpression(QString expression, QMap *knownValues, QMap *unevaluatedExpressions) { +int ParseUtil::evaluateExpression(const QString &expression) { + QList tokens = tokenizeExpression(expression); + QList postfixExpression = generatePostfix(tokens); + return evaluatePostfix(postfixExpression); +} + +QList ParseUtil::tokenizeExpression(QString expression) { QList tokens; static const QStringList tokenTypes = {"hex", "decimal", "identifier", "operator", "leftparen", "rightparen"}; @@ -163,18 +183,14 @@ QList ParseUtil::tokenizeExpression(QString expression, QMapcontains(token)) { - evaluateDefine(token, unevaluatedExpressions->value(token), knownValues, unevaluatedExpressions); - } else if (this->globalDefineExpressions.contains(token)) { - int value = evaluateDefine(token, this->globalDefineExpressions.value(token), &this->globalDefineValues, &this->globalDefineExpressions); - knownValues->insert(token, value); - } - - if (knownValues->contains(token)) { + bool ok; + int tokenValue = evaluateDefine(token, &ok); + if (ok) { // Any errors encountered when this identifier was evaluated should be recorded for this expression as well. recordErrors(this->errorMap.value(token)); - QString actualToken = QString("%1").arg(knownValues->value(token)); + + // Replace token with evaluated expression + QString actualToken = QString::number(tokenValue); expression = expression.replace(0, token.length(), actualToken); token = actualToken; tokenType = "decimal"; @@ -467,6 +483,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const result.filteredNames.append(name); } } + this->knownDefineExpressions.insert(result.expressions); return result; } @@ -476,14 +493,10 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS // Evaluate defines QMap filteredValues; - QMap allValues = this->globalDefineValues; this->errorMap.clear(); while (!defines.filteredNames.isEmpty()) { - const QString name = defines.filteredNames.takeFirst(); - const QString expression = defines.expressions.take(name); - if (expression == " ") continue; - this->curDefine = name; - filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); // TODO: Unite map with global expressions? Allows users to overwrite project defines + this->curDefine = defines.filteredNames.takeFirst(); + filteredValues.insert(this->curDefine, evaluateDefine(this->curDefine)); logRecordedErrors(); // Only log errors for defines that Porymap is looking for } @@ -517,7 +530,7 @@ void ParseUtil::loadGlobalCDefines(const QMap &defines) { this->globalDefineExpressions.insert(defines); } -void ParseUtil::resetGlobalCDefines() { +void ParseUtil::resetCDefines() { static const QMap defaultDefineValues = { {"FALSE", 0}, {"TRUE", 1}, @@ -535,6 +548,8 @@ void ParseUtil::resetGlobalCDefines() { }; this->globalDefineValues = defaultDefineValues; this->globalDefineExpressions.clear(); + this->knownDefineValues.clear(); + this->knownDefineExpressions.clear(); } QStringList ParseUtil::readCArray(const QString &filename, const QString &label) { diff --git a/src/project.cpp b/src/project.cpp index 33cac9ce..b798a203 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2744,7 +2744,7 @@ bool Project::readMiscellaneousConstants() { } bool Project::readGlobalConstants() { - this->parser.resetGlobalCDefines(); + this->parser.resetCDefines(); for (const auto &path : projectConfig.globalConstantsFilepaths) { QString error; this->parser.loadGlobalCDefinesFromFile(path, &error);