Merge pull request #606 from GriffinRichards/parse-enum

Support for parsing enums
This commit is contained in:
GriffinR 2024-09-08 21:38:10 -04:00 committed by GitHub
commit 11bd41d000
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 151 additions and 115 deletions

View file

@ -12,6 +12,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Add a `Close Project` option
- Add charts to the `Wild Pokémon` tab that show species and level distributions.
- An alert will be displayed when attempting to open a seemingly invalid project.
- Add support for defining project values with `enum` where `#define` was expected.
### Changed
- Edits to map connections now have Undo/Redo and can be viewed in exported timelapses.
@ -43,6 +44,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix map connections rendering incorrectly if their dimensions were smaller than the border draw distance.
- Fix the map list filter retaining text between project open/close.
- Fix the map list mishandling value gaps when sorting by Area.
- Fix a freeze on startup if project values are defined with mismatched parentheses.
## [5.4.1] - 2024-03-21
### Fixed

View file

@ -54,9 +54,9 @@ public:
QString readCIncbin(const QString &text, const QString &label);
QMap<QString, QString> readCIncbinMulti(const QString &filepath);
QStringList readCIncbinArray(const QString &filename, const QString &label);
QMap<QString, int> readCDefinesByPrefix(const QString &filename, QStringList prefixes);
QMap<QString, int> readCDefinesByName(const QString &filename, QStringList names);
QStringList readCDefineNames(const QString&, const QStringList&);
QMap<QString, int> readCDefinesByRegex(const QString &filename, const QStringList &regexList);
QMap<QString, int> readCDefinesByName(const QString &filename, const QStringList &names);
QStringList readCDefineNames(const QString &filename, const QStringList &regexList);
QMap<QString, QHash<QString, QString>> readCStructs(const QString &, const QString & = "", const QHash<int, QString> = { });
QList<QStringList> getLabelMacros(const QList<QStringList>&, const QString&);
QStringList getLabelValues(const QList<QStringList>&, const QString&);
@ -97,8 +97,14 @@ private:
void recordErrors(const QStringList &errors);
void logRecordedErrors();
QString createErrorMessage(const QString &message, const QString &expression);
QString readCDefinesFile(const QString &filename);
QMap<QString, int> readCDefines(const QString &filename, const QStringList &searchText, bool fullMatch);
struct ParsedDefines {
QMap<QString,QString> expressions; // Map of all define names encountered to their expressions
QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered
};
ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex);
QMap<QString, int> evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex);
bool defineNameMatchesFilter(const QString &name, const QStringList &filterList, bool useRegex);
static const QRegularExpression re_incScriptLabel;
static const QRegularExpression re_globalIncScriptLabel;

View file

@ -15,6 +15,22 @@ const QRegularExpression ParseUtil::re_poryScriptLabel("\\b(script)(\\((global|l
const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((global)\\))?\\s*\\b(?<label>[\\w_][\\w\\d_]*)");
const QRegularExpression ParseUtil::re_poryRawSection("\\b(raw)\\s*`(?<raw_script>[^`]*)");
static const QMap<QString, int> globalDefineValues = {
{"FALSE", 0},
{"TRUE", 1},
{"SCHAR_MIN", SCHAR_MIN},
{"SCHAR_MAX", SCHAR_MAX},
{"CHAR_MIN", CHAR_MIN},
{"CHAR_MAX", CHAR_MAX},
{"UCHAR_MAX", UCHAR_MAX},
{"SHRT_MIN", SHRT_MIN},
{"SHRT_MAX", SHRT_MAX},
{"USHRT_MAX", USHRT_MAX},
{"INT_MIN", INT_MIN},
{"INT_MAX", INT_MAX},
{"UINT_MAX", UINT_MAX},
};
using OrderedJson = poryjson::Json;
ParseUtil::ParseUtil() { }
@ -225,10 +241,11 @@ QList<Token> ParseUtil::generatePostfix(const QList<Token> &tokens) {
}
while (!operatorStack.isEmpty()) {
if (operatorStack.top().value == "(" || operatorStack.top().value == ")") {
Token token = operatorStack.pop();
if (token.value == "(" || token.value == ")") {
recordError("Mismatched parentheses detected in expression!");
} else {
output.append(operatorStack.pop());
output.append(token);
}
}
@ -353,12 +370,23 @@ QStringList ParseUtil::readCIncbinArray(const QString &filename, const QString &
return paths;
}
QString ParseUtil::readCDefinesFile(const QString &filename)
{
bool ParseUtil::defineNameMatchesFilter(const QString &name, const QStringList &filterList, bool useRegex) {
for (auto filter : filterList) {
if (useRegex) {
// TODO: These QRegularExpression should probably be constructed beforehand,
// otherwise we recreate them for every define we check.
if (QRegularExpression(filter).match(name).hasMatch()) return true;
} else if (name == filter) return true;
}
return false;
}
ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const QStringList &filterList, bool useRegex) {
ParsedDefines result;
this->file = filename;
if (this->file.isEmpty()) {
return QString();
return result;
}
QString filepath = this->root + "/" + this->file;
@ -366,98 +394,98 @@ QString ParseUtil::readCDefinesFile(const QString &filename)
if (this->text.isNull()) {
logError(QString("Failed to read C defines file: '%1'").arg(filepath));
return QString();
return result;
}
static const QRegularExpression re_extraChars("(//.*)|(\\/+\\*+[^*]*\\*+\\/+)");
this->text.replace(re_extraChars, "");
static const QRegularExpression re_extraSpaces("(\\\\\\s+)");
this->text.replace(re_extraSpaces, "");
return this->text;
if (this->text.isEmpty())
return result;
// Capture either the name and value of a #define, or everything between the braces of 'enum { }'
static const QRegularExpression re("#define\\s+(?<defineName>\\w+)[\\s\\n][^\\S\\n]*(?<defineValue>.+)?"
"|\\benum\\b[^{]*{(?<enumBody>[^}]*)}");
QRegularExpressionMatchIterator iter = re.globalMatch(this->text);
while (iter.hasNext()) {
QRegularExpressionMatch match = iter.next();
const QString enumBody = match.captured("enumBody");
if (!enumBody.isNull()) {
// Encountered an enum, extract the elements of the enum and give each an appropriate expression
int baseNum = 0;
QString baseExpression = "0";
// Note: We lazily consider an enum's expression to be any characters after the assignment up until the first comma or EOL.
// This would be a problem for e.g. NAME = MACRO(a, b), but we're currently unable to parse function-like macros anyway.
// If this changes then the regex below needs to be updated.
static const QRegularExpression re_enumElement("\\b(?<name>\\w+)\\b\\s*=?\\s*(?<expression>[^,]*)");
QRegularExpressionMatchIterator elementIter = re_enumElement.globalMatch(enumBody);
while (elementIter.hasNext()) {
QRegularExpressionMatch elementMatch = elementIter.next();
const QString name = elementMatch.captured("name");
QString expression = elementMatch.captured("expression");
if (expression.isEmpty()) {
// enum values may use tokens that we don't know how to evaluate yet.
// For now we define each element to be 1 + the previous element's expression.
expression = QString("((%1)+%2)").arg(baseExpression).arg(baseNum++);
} else {
// This element was explicitly assigned an expression with '=', reset the bases for any subsequent elements.
baseExpression = expression;
baseNum = 1;
}
result.expressions.insert(name, expression);
if (defineNameMatchesFilter(name, filterList, useRegex))
result.filteredNames.append(name);
}
} else {
// Encountered a #define
const QString name = match.captured("defineName");
result.expressions.insert(name, match.captured("defineValue"));
if (defineNameMatchesFilter(name, filterList, useRegex))
result.filteredNames.append(name);
}
}
return result;
}
// Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on).
// If 'fullMatch' is true, 'searchText' is a list of exact define names to evaluate and return.
// If 'fullMatch' is false, 'searchText' is a list of prefixes or regexes for define names to evaluate and return.
QMap<QString, int> ParseUtil::readCDefines(const QString &filename, const QStringList &searchText, bool fullMatch)
{
QMap<QString, int> filteredValues;
this->text = this->readCDefinesFile(filename);
if (this->text.isEmpty()) {
return filteredValues;
}
// Extract all the define names and expressions
QMap<QString, QString> allExpressions;
QMap<QString, QString> filteredExpressions;
static const QRegularExpression re("#define\\s+(?<defineName>\\w+)[^\\S\\n]+(?<defineValue>.+)");
QRegularExpressionMatchIterator iter = re.globalMatch(this->text);
while (iter.hasNext()) {
QRegularExpressionMatch match = iter.next();
const QString name = match.captured("defineName");
const QString expression = match.captured("defineValue");
// If name matches the search text record it for evaluation.
for (auto s : searchText) {
if ((fullMatch && name == s) || (!fullMatch && (name.startsWith(s) || QRegularExpression(s).match(name).hasMatch()))) {
filteredExpressions.insert(name, expression);
break;
}
}
allExpressions.insert(name, expression);
}
QMap<QString, int> allValues;
allValues.insert("FALSE", 0);
allValues.insert("TRUE", 1);
QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex) {
ParsedDefines defines = readCDefines(filename, filterList, useRegex);
// Evaluate defines
QMap<QString, int> filteredValues;
QMap<QString, int> allValues = globalDefineValues;
this->errorMap.clear();
while (!filteredExpressions.isEmpty()) {
const QString name = filteredExpressions.firstKey();
const QString expression = filteredExpressions.take(name);
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, &allExpressions));
filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions));
logRecordedErrors(); // Only log errors for defines that Porymap is looking for
}
return filteredValues;
}
// Find and evaluate an unknown list of defines with a known name prefix.
QMap<QString, int> ParseUtil::readCDefinesByPrefix(const QString &filename, QStringList prefixes) {
prefixes.removeDuplicates();
return this->readCDefines(filename, prefixes, false);
}
// Find and evaluate a specific set of defines with known names.
QMap<QString, int> ParseUtil::readCDefinesByName(const QString &filename, QStringList names) {
names.removeDuplicates();
return this->readCDefines(filename, names, true);
QMap<QString, int> ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names) {
return evaluateCDefines(filename, names, false);
}
// Similar to readCDefines, but for cases where we only need to show a list of define names.
// We can skip reading/evaluating any expressions (and by extension skip reporting any errors from this process).
QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList &prefixes) {
QStringList filteredNames;
// Find and evaluate an unknown list of defines with a known name pattern.
QMap<QString, int> ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList &regexList) {
return evaluateCDefines(filename, regexList, true);
}
this->text = this->readCDefinesFile(filename);
if (this->text.isEmpty()) {
return filteredNames;
}
static const QRegularExpression re("#define\\s+(?<defineName>\\w+)[^\\S\\n]+");
QRegularExpressionMatchIterator iter = re.globalMatch(this->text);
while (iter.hasNext()) {
QRegularExpressionMatch match = iter.next();
QString name = match.captured("defineName");
for (QString prefix : prefixes) {
if (name.startsWith(prefix) || QRegularExpression(prefix).match(name).hasMatch()) {
filteredNames.append(name);
}
}
}
return filteredNames;
// Find an unknown list of defines with a known name pattern.
// Similar to readCDefinesByRegex, but for cases where we only need to show a list of define names.
// We can skip evaluating any expressions (and by extension skip reporting any errors from this process).
QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList &regexList) {
return readCDefines(filename, regexList, true).filteredNames;
}
QStringList ParseUtil::readCArray(const QString &filename, const QString &label) {

View file

@ -1558,8 +1558,8 @@ bool Project::readTilesetMetatileLabels() {
QString metatileLabelsFilename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels);
fileWatcher.addPath(root + "/" + metatileLabelsFilename);
const QStringList prefixes = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))};
QMap<QString, int> defines = parser.readCDefinesByPrefix(metatileLabelsFilename, prefixes);
const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))};
QMap<QString, int> defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList);
for (QString label : defines.keys()) {
uint32_t metatileId = static_cast<uint32_t>(defines[label]);
@ -2162,10 +2162,10 @@ bool Project::readRegionMapSections() {
this->mapSectionNameToValue.clear();
this->mapSectionValueToName.clear();
const QStringList prefixes = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))};
const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))};
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections);
fileWatcher.addPath(root + "/" + filename);
this->mapSectionNameToValue = parser.readCDefinesByPrefix(filename, prefixes);
this->mapSectionNameToValue = parser.readCDefinesByRegex(filename, regexList);
if (this->mapSectionNameToValue.isEmpty()) {
logError(QString("Failed to read region map sections from %1.").arg(filename));
return false;
@ -2180,13 +2180,13 @@ bool Project::readRegionMapSections() {
// Read the constants to preserve any "unused" heal locations when writing the file later
bool Project::readHealLocationConstants() {
this->healLocationNameToValue.clear();
const QStringList prefixes = {
const QStringList regexList = {
QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix)),
QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_spawn_prefix))
};
QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_heal_locations);
fileWatcher.addPath(root + "/" + constantsFilename);
this->healLocationNameToValue = parser.readCDefinesByPrefix(constantsFilename, prefixes);
this->healLocationNameToValue = parser.readCDefinesByRegex(constantsFilename, regexList);
// No need to check if empty, not finding any heal location constants is ok
return true;
}
@ -2285,40 +2285,40 @@ bool Project::readHealLocations() {
}
bool Project::readItemNames() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items);
fileWatcher.addPath(root + "/" + filename);
itemNames = parser.readCDefineNames(filename, prefixes);
itemNames = parser.readCDefineNames(filename, regexList);
if (itemNames.isEmpty())
logWarn(QString("Failed to read item constants from %1").arg(filename));
return true;
}
bool Project::readFlagNames() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags);
fileWatcher.addPath(root + "/" + filename);
flagNames = parser.readCDefineNames(filename, prefixes);
flagNames = parser.readCDefineNames(filename, regexList);
if (flagNames.isEmpty())
logWarn(QString("Failed to read flag constants from %1").arg(filename));
return true;
}
bool Project::readVarNames() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars);
fileWatcher.addPath(root + "/" + filename);
varNames = parser.readCDefineNames(filename, prefixes);
varNames = parser.readCDefineNames(filename, regexList);
if (varNames.isEmpty())
logWarn(QString("Failed to read var constants from %1").arg(filename));
return true;
}
bool Project::readMovementTypes() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement);
fileWatcher.addPath(root + "/" + filename);
movementTypes = parser.readCDefineNames(filename, prefixes);
movementTypes = parser.readCDefineNames(filename, regexList);
if (movementTypes.isEmpty())
logWarn(QString("Failed to read movement type constants from %1").arg(filename));
return true;
@ -2334,30 +2334,30 @@ bool Project::readInitialFacingDirections() {
}
bool Project::readMapTypes() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types);
fileWatcher.addPath(root + "/" + filename);
mapTypes = parser.readCDefineNames(filename, prefixes);
mapTypes = parser.readCDefineNames(filename, regexList);
if (mapTypes.isEmpty())
logWarn(QString("Failed to read map type constants from %1").arg(filename));
return true;
}
bool Project::readMapBattleScenes() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types);
fileWatcher.addPath(root + "/" + filename);
mapBattleScenes = parser.readCDefineNames(filename, prefixes);
mapBattleScenes = parser.readCDefineNames(filename, regexList);
if (mapBattleScenes.isEmpty())
logWarn(QString("Failed to read map battle scene constants from %1").arg(filename));
return true;
}
bool Project::readWeatherNames() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather);
fileWatcher.addPath(root + "/" + filename);
weatherNames = parser.readCDefineNames(filename, prefixes);
weatherNames = parser.readCDefineNames(filename, regexList);
if (weatherNames.isEmpty())
logWarn(QString("Failed to read weather constants from %1").arg(filename));
return true;
@ -2367,10 +2367,10 @@ bool Project::readCoordEventWeatherNames() {
if (!projectConfig.eventWeatherTriggerEnabled)
return true;
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather);
fileWatcher.addPath(root + "/" + filename);
coordEventWeatherNames = parser.readCDefineNames(filename, prefixes);
coordEventWeatherNames = parser.readCDefineNames(filename, regexList);
if (coordEventWeatherNames.isEmpty())
logWarn(QString("Failed to read coord event weather constants from %1").arg(filename));
return true;
@ -2380,30 +2380,30 @@ bool Project::readSecretBaseIds() {
if (!projectConfig.eventSecretBaseEnabled)
return true;
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases);
fileWatcher.addPath(root + "/" + filename);
secretBaseIds = parser.readCDefineNames(filename, prefixes);
secretBaseIds = parser.readCDefineNames(filename, regexList);
if (secretBaseIds.isEmpty())
logWarn(QString("Failed to read secret base id constants from '%1'").arg(filename));
return true;
}
bool Project::readBgEventFacingDirections() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg);
fileWatcher.addPath(root + "/" + filename);
bgEventFacingDirections = parser.readCDefineNames(filename, prefixes);
bgEventFacingDirections = parser.readCDefineNames(filename, regexList);
if (bgEventFacingDirections.isEmpty())
logWarn(QString("Failed to read bg event facing direction constants from %1").arg(filename));
return true;
}
bool Project::readTrainerTypes() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types);
fileWatcher.addPath(root + "/" + filename);
trainerTypes = parser.readCDefineNames(filename, prefixes);
trainerTypes = parser.readCDefineNames(filename, regexList);
if (trainerTypes.isEmpty())
logWarn(QString("Failed to read trainer type constants from %1").arg(filename));
return true;
@ -2413,10 +2413,10 @@ bool Project::readMetatileBehaviors() {
this->metatileBehaviorMap.clear();
this->metatileBehaviorMapInverse.clear();
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)};
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors);
fileWatcher.addPath(root + "/" + filename);
QMap<QString, int> defines = parser.readCDefinesByPrefix(filename, prefixes);
QMap<QString, int> defines = parser.readCDefinesByRegex(filename, regexList);
if (defines.isEmpty()) {
// Not having any metatile behavior names is ok (their values will be displayed instead).
// If the user's metatiles can have nonzero values then warn them, as they likely want names.
@ -2435,10 +2435,10 @@ bool Project::readMetatileBehaviors() {
}
bool Project::readSongNames() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)};
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs);
fileWatcher.addPath(root + "/" + filename);
this->songNames = parser.readCDefineNames(filename, prefixes);
this->songNames = parser.readCDefineNames(filename, regexList);
if (this->songNames.isEmpty())
logWarn(QString("Failed to read song names from %1.").arg(filename));
@ -2450,10 +2450,10 @@ bool Project::readSongNames() {
}
bool Project::readObjEventGfxConstants() {
const QStringList prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)};
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)};
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events);
fileWatcher.addPath(root + "/" + filename);
this->gfxDefines = parser.readCDefinesByPrefix(filename, prefixes);
this->gfxDefines = parser.readCDefinesByRegex(filename, regexList);
if (this->gfxDefines.isEmpty())
logWarn(QString("Failed to read object event graphics constants from %1.").arg(filename));
return true;
@ -2648,10 +2648,10 @@ bool Project::readSpeciesIconPaths() {
const QMap<QString, QString> iconIncbins = parser.readCIncbinMulti(incfilename);
// Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it).
const QStringList prefixes = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix))};
const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix))};
const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species);
fileWatcher.addPath(root + "/" + constantsFilename);
QStringList speciesNames = parser.readCDefineNames(constantsFilename, prefixes);
QStringList speciesNames = parser.readCDefineNames(constantsFilename, regexList);
if (speciesNames.isEmpty())
speciesNames = monIconNames.keys();