diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 98d73ae4..606550c0 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -7,6 +7,7 @@ #include #include #include +#include enum TokenType { Number, @@ -14,29 +15,6 @@ enum TokenType { Error, }; -class DebugInfo { -public: - DebugInfo(QString file, QStringList lines) { - this->file = file; - this->lines = lines; - }; - QString file; - int line; - bool err; - QStringList lines; - void error(QString expression, QString token) { - int lineNo = 0; - for (QString line_ : lines) { - lineNo++; - if (line_.contains(expression)) { - this->line = lineNo; - break; - } - } - logError(QString("%1:%2: unknown identifier found in expression: '%3'.").arg(file).arg(line).arg(token)); - } -}; - class Token { public: Token(QString value = "", QString type = "") { @@ -60,15 +38,29 @@ public: class ParseUtil { public: - ParseUtil(QString, QString); + ParseUtil(); + void set_root(QString); + QString readTextFile(QString); void strip_comment(QString*); QList* parseAsm(QString); int evaluateDefine(QString, QMap*); - DebugInfo *debug; + QStringList readCArray(QString text, QString label); + QMap readNamedIndexCArray(QString text, QString label); + QString readCIncbin(QString text, QString label); + QMap readCDefines(QString filename, QStringList prefixes); + void readCDefinesSorted(QString, QStringList, QStringList*); + QList* getLabelMacros(QList*, QString); + QStringList* getLabelValues(QList*, QString); + bool tryParseJsonFile(QJsonDocument *out, QString filepath); + private: + QString root; + QString text; + QString file; QList tokenizeExpression(QString expression, QMap* knownIdentifiers); QList generatePostfix(QList tokens); int evaluatePostfix(QList postfix); + void error(QString message, QString expression); }; #endif // PARSEUTIL_H diff --git a/include/project.h b/include/project.h index ec25e689..7669632f 100644 --- a/include/project.h +++ b/include/project.h @@ -5,6 +5,7 @@ #include "blockdata.h" #include "heallocation.h" #include "event.h" +#include "parseutil.h" #include #include @@ -46,6 +47,9 @@ public: QMap metatileBehaviorMap; QMap metatileBehaviorMapInverse; QMap facingDirections; + ParseUtil parser; + + void set_root(QString); struct DataQualifiers { @@ -66,7 +70,6 @@ public: Blockdata* readBlockdata(QString); void loadBlockdata(Map*); - QString readTextFile(QString path); void saveTextFile(QString path, QString text); void appendTextFile(QString path, QString text); void deleteFile(QString path); @@ -80,8 +83,6 @@ public: QString readMapLayoutId(QString map_name); QString readMapLocation(QString map_name); - QList* getLabelMacros(QList*, QString); - QStringList* getLabelValues(QList*, QString); QMap getTopLevelMapFields(); bool loadMapData(Map*); void readMapLayouts(); @@ -107,7 +108,6 @@ public: void saveTilesetTilesImage(Tileset*); void saveTilesetPalettes(Tileset*, bool); - QList* parseAsm(QString text); QStringList getSongNames(); QStringList getVisibilities(); QMap getTilesetLabels(); @@ -136,22 +136,15 @@ public: void saveMapHealEvents(Map *map); - QStringList readCArray(QString text, QString label); - QString readCIncbin(QString text, QString label); - QMap readCDefines(QString filename, QStringList prefixes); - QMap readNamedIndexCArray(QString text, QString label); - static int getNumTilesPrimary(); static int getNumTilesTotal(); static int getNumMetatilesPrimary(); static int getNumMetatilesTotal(); static int getNumPalettesPrimary(); static int getNumPalettesTotal(); - static bool tryParseJsonFile(QJsonDocument *out, QString filepath); + private: void updateMapLayout(Map*); - void readCDefinesSorted(QString, QStringList, QStringList*); - void readCDefinesSorted(QString, QStringList, QStringList*, QString, int); void setNewMapHeader(Map* map, int mapIndex); void setNewMapLayout(Map* map); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 4c57384b..26187d4a 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -2,12 +2,27 @@ #include "parseutil.h" #include +#include +#include #include -ParseUtil::ParseUtil(QString filename, QString text) +ParseUtil::ParseUtil() { +} + +void ParseUtil::set_root(QString dir) { + this->root = dir; +} + +void ParseUtil::error(QString message, QString expression) { QStringList lines = text.split(QRegularExpression("[\r\n]")); - debug = new DebugInfo(filename, lines); + int lineNum = 0, colNum = 0; + for (QString line : lines) { + lineNum++; + colNum = line.indexOf(expression) + 1; + if (colNum) break; + } + logError(QString("%1:%2:%3: %4").arg(file).arg(lineNum).arg(colNum).arg(message)); } void ParseUtil::strip_comment(QString *line) { @@ -24,13 +39,28 @@ void ParseUtil::strip_comment(QString *line) { } } -QList* ParseUtil::parseAsm(QString text) { +QString ParseUtil::readTextFile(QString path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + logError(QString("Could not open '%1': ").arg(path) + file.errorString()); + return QString(); + } + QTextStream in(&file); + in.setCodec("UTF-8"); + QString text = ""; + while (!in.atEnd()) { + text += in.readLine() + "\n"; + } + return text; +} + +QList* ParseUtil::parseAsm(QString filename) { QList *parsed = new QList; + + text = readTextFile(root + "/" + filename); QStringList lines = text.split('\n'); for (QString line : lines) { QString label; - //QString macro; - //QStringList *params; strip_comment(&line); if (line.trimmed().isEmpty()) { } else if (line.contains(':')) { @@ -52,14 +82,6 @@ QList* ParseUtil::parseAsm(QString text) { params.prepend(macro); parsed->append(params); } - //if (macro != NULL) { - // if (macros->contains(macro)) { - // void* function = macros->value(macro); - // if (function != NULL) { - // std::function function(params); - // } - // } - //} } return parsed; } @@ -94,7 +116,16 @@ QList ParseUtil::tokenizeExpression(QString expression, QMaperror(expression, token); + QString message = QString("unknown token '%1' found in expression '%2'") + .arg(token).arg(expression); + error(message, expression); + } + } + else if (tokenType == "operator") { + if (!Token::precedenceMap.contains(token)) { + QString message = QString("unsupported postfix operator: '%1'") + .arg(token); + error(message, expression); } } @@ -167,7 +198,7 @@ QList ParseUtil::generatePostfix(QList tokens) { int ParseUtil::evaluatePostfix(QList postfix) { QStack stack; for (Token token : postfix) { - if (token.type == TokenType::Operator) { + if (token.type == TokenType::Operator && stack.size() > 1) { int op2 = stack.pop().value.toInt(nullptr, 0); int op1 = stack.pop().value.toInt(nullptr, 0); int result = 0; @@ -189,8 +220,6 @@ int ParseUtil::evaluatePostfix(QList postfix) { result = op1 ^ op2; } else if (token.value == "|") { result = op1 | op2; - } else { - logError(QString("Unsupported postfix operator: '%1'").arg(token.value)); } stack.push(Token(QString("%1").arg(result), "decimal")); } else if (token.type != TokenType::Error) { @@ -200,3 +229,183 @@ int ParseUtil::evaluatePostfix(QList postfix) { return stack.pop().value.toInt(nullptr, 0); } + +QString ParseUtil::readCIncbin(QString filename, QString label) { + QString path; + + if (label.isNull()) { + return path; + } + + text = readTextFile(root + "/" + filename); + + QRegExp *re = new QRegExp(QString( + "\\b%1\\b" + "\\s*\\[?\\s*\\]?\\s*=\\s*" + "INCBIN_[US][0-9][0-9]?" + "\\(\"([^\"]*)\"\\)").arg(label)); + + int pos = re->indexIn(text); + if (pos != -1) { + path = re->cap(1); + } + + return path; +} + +QMap ParseUtil::readCDefines(QString filename, QStringList prefixes) { + QMap allDefines; + QMap filteredDefines; + + file = filename; + + if (file.isEmpty()) { + return filteredDefines; + } + + QString filepath = root + "/" + file; + text = readTextFile(filepath); + + if (text.isNull()) { + logError(QString("Failed to read C defines file: '%1'").arg(filepath)); + return filteredDefines; + } + + text.replace(QRegularExpression("(//.*)|(\\/+\\*+[^*]*\\*+\\/+)"), ""); + + QRegularExpression re("#define\\s+(?\\w+)[^\\S\\n]+(?.+)"); + QRegularExpressionMatchIterator iter = re.globalMatch(text); + while (iter.hasNext()) { + QRegularExpressionMatch match = iter.next(); + QString name = match.captured("defineName"); + QString expression = match.captured("defineValue"); + if (expression == " ") continue; + int value = evaluateDefine(expression, &allDefines); + allDefines.insert(name, value); + for (QString prefix : prefixes) { + if (name.startsWith(prefix) || QRegularExpression(prefix).match(name).hasMatch()) { + filteredDefines.insert(name, value); + } + } + } + return filteredDefines; +} + +void ParseUtil::readCDefinesSorted(QString filename, QStringList prefixes, QStringList* definesToSet) { + QMap defines = readCDefines(filename, prefixes); + + // The defines should to be sorted by their underlying value, not alphabetically. + // Reverse the map and read out the resulting keys in order. + QMultiMap definesInverse; + for (QString defineName : defines.keys()) { + definesInverse.insert(defines[defineName], defineName); + } + *definesToSet = definesInverse.values(); +} + +QStringList ParseUtil::readCArray(QString filename, QString label) { + QStringList list; + + if (label.isNull()) { + return list; + } + + file = filename; + text = readTextFile(root + "/" + filename); + + QRegularExpression re(QString("\\b%1\\b\\s*\\[?\\s*\\]?\\s*=\\s*\\{([^\\}]*)\\}").arg(label)); + QRegularExpressionMatch match = re.match(text); + + if (match.hasMatch()) { + QString body = match.captured(1); + QStringList split = body.split(','); + for (QString item : split) { + item = item.trimmed(); + if (!item.contains(QRegularExpression("[^A-Za-z0-9_&()]"))) list.append(item); + // do not print error info here because this is called dozens of times + } + } + + return list; +} + +QMap ParseUtil::readNamedIndexCArray(QString filename, QString label) { + text = readTextFile(root + "/" + filename); + QMap map; + + QRegularExpression re_text(QString("\\b%1\\b\\s*\\[?\\s*\\]?\\s*=\\s*\\{([^\\}]*)\\}").arg(label)); + QString body = re_text.match(text).captured(1).replace(QRegularExpression("\\s*"), ""); + + QRegularExpression re("\\[(?[A-Za-z1-9_]*)\\]=(?[A-Za-z1-9_]*)"); + QRegularExpressionMatchIterator iter = re.globalMatch(body); + + while (iter.hasNext()) { + QRegularExpressionMatch match = iter.next(); + QString key = match.captured("index"); + QString value = match.captured("value"); + map.insert(key, value); + } + + return map; +} + +QList* ParseUtil::getLabelMacros(QList *list, QString label) { + bool in_label = false; + QList *new_list = new QList; + for (int i = 0; i < list->length(); i++) { + QStringList params = list->value(i); + QString macro = params.value(0); + if (macro == ".label") { + if (params.value(1) == label) { + in_label = true; + } else if (in_label) { + // If nothing has been read yet, assume the label + // we're looking for is in a stack of labels. + if (new_list->length() > 0) { + break; + } + } + } else if (in_label) { + new_list->append(params); + } + } + return new_list; +} + +// For if you don't care about filtering by macro, +// and just want all values associated with some label. +QStringList* ParseUtil::getLabelValues(QList *list, QString label) { + list = getLabelMacros(list, label); + QStringList *values = new QStringList; + for (int i = 0; i < list->length(); i++) { + QStringList params = list->value(i); + QString macro = params.value(0); + if (macro == ".align" || macro == ".ifdef" || macro == ".ifndef") { + continue; + } + for (int j = 1; j < params.length(); j++) { + values->append(params.value(j)); + } + } + return values; +} + +bool ParseUtil::tryParseJsonFile(QJsonDocument *out, QString filepath) { + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + logError(QString("Error: Could not open %1 for reading").arg(filepath)); + return false; + } + + QByteArray data = file.readAll(); + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &parseError); + file.close(); + if (parseError.error != QJsonParseError::NoError) { + logError(QString("Error: Failed to parse json file %1: %2").arg(filepath).arg(parseError.errorString())); + return false; + } + + *out = jsonDoc; + return true; +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ea190f84..dad5fbae 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -255,7 +255,7 @@ bool MainWindow::openProject(QString dir) { bool already_open = isProjectOpen() && (editor->project->root == dir); if (!already_open) { editor->project = new Project; - editor->project->root = dir; + editor->project->set_root(dir); setWindowTitle(editor->project->getProjectTitle()); loadDataStructures(); populateMapList(); diff --git a/src/project.cpp b/src/project.cpp index a4e1c8cc..41455dd7 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -47,6 +47,11 @@ Project::Project() tileset_cache = new QMap; } +void Project::set_root(QString dir) { + this->root = dir; + this->parser.set_root(dir); +} + QString Project::getProjectTitle() { if (!root.isNull()) { return root.section('/', -1); @@ -82,51 +87,6 @@ void Project::setNewMapConnections(Map *map) { map->connections.clear(); } -QList* Project::getLabelMacros(QList *list, QString label) { - bool in_label = false; - QList *new_list = new QList; - for (int i = 0; i < list->length(); i++) { - QStringList params = list->value(i); - QString macro = params.value(0); - if (macro == ".label") { - if (params.value(1) == label) { - in_label = true; - } else if (in_label) { - // If nothing has been read yet, assume the label - // we're looking for is in a stack of labels. - if (new_list->length() > 0) { - break; - } - } - } else if (in_label) { - new_list->append(params); - } - } - return new_list; -} - -// For if you don't care about filtering by macro, -// and just want all values associated with some label. -QStringList* Project::getLabelValues(QList *list, QString label) { - list = getLabelMacros(list, label); - QStringList *values = new QStringList; - for (int i = 0; i < list->length(); i++) { - QStringList params = list->value(i); - QString macro = params.value(0); - // Ignore .align - if (macro == ".align") - continue; - if (macro == ".ifdef") - continue; - if (macro == ".ifndef") - continue; - for (int j = 1; j < params.length(); j++) { - values->append(params.value(j)); - } - } - return values; -} - QMap Project::getTopLevelMapFields() { if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald) { return QMap @@ -186,7 +146,7 @@ bool Project::loadMapData(Map* map) { QString mapFilepath = QString("%1/data/maps/%2/map.json").arg(root).arg(map->name); QJsonDocument mapDoc; - if (!tryParseJsonFile(&mapDoc, mapFilepath)) { + if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { logError(QString("Failed to read map data from %1").arg(mapFilepath)); return false; } @@ -382,7 +342,7 @@ QString Project::readMapLayoutId(QString map_name) { QString mapFilepath = QString("%1/data/maps/%2/map.json").arg(root).arg(map_name); QJsonDocument mapDoc; - if (!tryParseJsonFile(&mapDoc, mapFilepath)) { + if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { logError(QString("Failed to read map layout id from %1").arg(mapFilepath)); return QString::null; } @@ -398,7 +358,7 @@ QString Project::readMapLocation(QString map_name) { QString mapFilepath = QString("%1/data/maps/%2/map.json").arg(root).arg(map_name); QJsonDocument mapDoc; - if (!tryParseJsonFile(&mapDoc, mapFilepath)) { + if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { logError(QString("Failed to read map's region map section from %1").arg(mapFilepath)); return QString::null; } @@ -449,7 +409,7 @@ void Project::readMapLayouts() { QString layoutsFilepath = QString("%1/data/layouts/layouts.json").arg(root); QJsonDocument layoutsDoc; - if (!tryParseJsonFile(&layoutsDoc, layoutsFilepath)) { + if (!parser.tryParseJsonFile(&layoutsDoc, layoutsFilepath)) { logError(QString("Failed to read map layouts from %1").arg(layoutsFilepath)); return; } @@ -712,10 +672,10 @@ void Project::saveTilesetTilesImage(Tileset *tileset) { } void Project::saveTilesetPalettes(Tileset *tileset, bool primary) { - PaletteUtil parser; + PaletteUtil paletteParser; for (int i = 0; i < Project::getNumPalettesTotal(); i++) { QString filepath = tileset->palettePaths.at(i); - parser.writeJASC(filepath, tileset->palettes->at(i).toVector(), 0, 16); + paletteParser.writeJASC(filepath, tileset->palettes->at(i).toVector(), 0, 16); } } @@ -729,13 +689,10 @@ void Project::loadMapTilesets(Map* map) { } Tileset* Project::loadTileset(QString label, Tileset *tileset) { - QString filename = "data/tilesets/headers.inc"; - QString headers_text = readTextFile(root + "/" + filename); - ParseUtil *parser = new ParseUtil(filename, headers_text); - if (headers_text.isNull()) { + QStringList *values = parser.getLabelValues(parser.parseAsm("data/tilesets/headers.inc"), label); + if (values->isEmpty()) { return nullptr; } - QStringList *values = getLabelValues(parser->parseAsm(headers_text), label); if (tileset == nullptr) { tileset = new Tileset; } @@ -863,7 +820,7 @@ void Project::saveMap(Map *map) { QString layoutsFilepath = QString("%1/data/layouts/layouts.json").arg(root); QJsonDocument layoutsDoc; - if (!tryParseJsonFile(&layoutsDoc, layoutsFilepath)) { + if (!parser.tryParseJsonFile(&layoutsDoc, layoutsFilepath)) { logError(QString("Failed to read map layouts from %1").arg(layoutsFilepath)); return; } @@ -1038,12 +995,9 @@ void Project::loadTilesetAssets(Tileset* tileset) { QString tilesetName = tileset->name; QString dir_path = root + "/data/tilesets/" + category + "/" + tilesetName.replace("gTileset_", "").toLower(); - QString gfx_filename = "data/tilesets/graphics.inc"; - QString graphics_text = readTextFile(root + "/" + gfx_filename); - ParseUtil* parser = new ParseUtil(gfx_filename, graphics_text); - QList *graphics = parser->parseAsm(graphics_text); - QStringList *tiles_values = getLabelValues(graphics, tileset->tiles_label); - QStringList *palettes_values = getLabelValues(graphics, tileset->palettes_label); + QList *graphics = parser.parseAsm("data/tilesets/graphics.inc"); + QStringList *tiles_values = parser.getLabelValues(graphics, tileset->tiles_label); + QStringList *palettes_values = parser.getLabelValues(graphics, tileset->palettes_label); QString tiles_path; if (!tiles_values->isEmpty()) { @@ -1067,15 +1021,14 @@ void Project::loadTilesetAssets(Tileset* tileset) { } } - QString metatiles_text = readTextFile(root + "/data/tilesets/metatiles.inc"); - QList *metatiles_macros = parser->parseAsm(metatiles_text); - QStringList *metatiles_values = getLabelValues(metatiles_macros, tileset->metatiles_label); + QList *metatiles_macros = parser.parseAsm("data/tilesets/metatiles.inc"); + QStringList *metatiles_values = parser.getLabelValues(metatiles_macros, tileset->metatiles_label); if (!metatiles_values->isEmpty()) { tileset->metatiles_path = root + "/" + metatiles_values->value(0).section('"', 1, 1); } else { tileset->metatiles_path = dir_path + "/metatiles.bin"; } - QStringList *metatile_attrs_values = getLabelValues(metatiles_macros, tileset->metatile_attrs_label); + QStringList *metatile_attrs_values = parser.getLabelValues(metatiles_macros, tileset->metatile_attrs_label); if (!metatile_attrs_values->isEmpty()) { tileset->metatile_attrs_path = root + "/" + metatile_attrs_values->value(0).section('"', 1, 1); } else { @@ -1093,7 +1046,7 @@ void Project::loadTilesetAssets(Tileset* tileset) { for (int i = 0; i < tileset->palettePaths.length(); i++) { QList palette; QString path = tileset->palettePaths.value(i); - QString text = readTextFile(path); + QString text = parser.readTextFile(path); if (!text.isNull()) { QStringList lines = text.split(QRegExp("[\r\n]"),QString::SkipEmptyParts); if (lines.length() == 19 && lines[0] == "JASC-PAL" && lines[1] == "0100" && lines[2] == "16") { @@ -1228,21 +1181,6 @@ Tileset* Project::getTileset(QString label, bool forceLoad) { } } -QString Project::readTextFile(QString path) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1': ").arg(path) + file.errorString()); - return QString(); - } - QTextStream in(&file); - in.setCodec("UTF-8"); - QString text = ""; - while (!in.atEnd()) { - text += in.readLine() + "\n"; - } - return text; -} - void Project::saveTextFile(QString path, QString text) { QFile file(path); if (file.open(QIODevice::WriteOnly)) { @@ -1271,7 +1209,7 @@ void Project::deleteFile(QString path) { void Project::readMapGroups() { QString mapGroupsFilepath = QString("%1/data/maps/map_groups.json").arg(root); QJsonDocument mapGroupsDoc; - if (!tryParseJsonFile(&mapGroupsDoc, mapGroupsFilepath)) { + if (!parser.tryParseJsonFile(&mapGroupsDoc, mapGroupsFilepath)) { logError(QString("Failed to read map groups from %1").arg(mapGroupsFilepath)); return; } @@ -1402,7 +1340,8 @@ QMap Project::getTilesetLabels() { QStringList secondaryTilesets; allTilesets.insert("primary", primaryTilesets); allTilesets.insert("secondary", secondaryTilesets); - QString headers_text = readTextFile(root + "/data/tilesets/headers.inc"); + + QString headers_text = parser.readTextFile(root + "/data/tilesets/headers.inc"); QRegularExpression re("(?