1
0
mirror of synced 2024-12-15 09:11:15 +01:00
ImHex/lib/libimhex/source/pattern_language/preprocessor.cpp

258 lines
11 KiB
C++
Raw Normal View History

#include <hex/pattern_language/preprocessor.hpp>
2021-08-29 22:15:18 +02:00
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/paths.hpp>
#include <hex/helpers/file.hpp>
2021-08-29 22:15:18 +02:00
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
#include <filesystem>
namespace hex::pl {
std::optional<std::string> Preprocessor::preprocess(const std::string &code, bool initialRun) {
2022-02-01 22:09:44 +01:00
u32 offset = 0;
u32 lineNumber = 1;
bool isInString = false;
2020-11-21 14:39:01 +01:00
if (initialRun) {
this->m_defines.clear();
2020-11-21 14:39:01 +01:00
this->m_pragmas.clear();
}
std::string output;
output.reserve(code.length());
try {
bool startOfLine = true;
while (offset < code.length()) {
if (offset > 0 && code[offset - 1] != '\\' && code[offset] == '\"')
isInString = !isInString;
else if (isInString) {
output += code[offset];
offset += 1;
continue;
}
if (code[offset] == '#' && startOfLine) {
offset += 1;
if (code.substr(offset, 7) == "include") {
offset += 7;
while (std::isblank(code[offset]) || std::isspace(code[offset]))
offset += 1;
if (code[offset] != '<' && code[offset] != '"')
throwPreprocessorError("expected '<' or '\"' before file name", lineNumber);
char endChar = code[offset];
if (endChar == '<') endChar = '>';
offset += 1;
std::string includeFile;
while (code[offset] != endChar) {
includeFile += code[offset];
offset += 1;
if (offset >= code.length())
throwPreprocessorError(hex::format("missing terminating '{0}' character", endChar), lineNumber);
}
offset += 1;
fs::path includePath = includeFile;
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
if (includeFile[0] != '/') {
for (const auto &dir : hex::getPath(ImHexPath::PatternsInclude)) {
fs::path tempPath = dir / includePath;
2022-01-13 14:33:30 +01:00
if (fs::exists(tempPath)) {
includePath = tempPath;
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
break;
}
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
}
}
File file(includePath, File::Mode::Read);
if (!file.isValid()) {
if (includePath.parent_path().filename().string() == "std")
throwPreprocessorError(hex::format("{0}: No such file or directory.\n\nThis file might be part of the standard library.\nYou can install the standard library though\nthe Content Store found under Help -> Content Store.", includeFile.c_str()), lineNumber);
else
throwPreprocessorError(hex::format("{0}: No such file or directory", includeFile.c_str()), lineNumber);
}
Preprocessor preprocessor;
preprocessor.addDefaultPragmaHandlers();
preprocessor.m_defines = this->m_defines;
preprocessor.m_onceIncludedFiles = this->m_onceIncludedFiles;
auto preprocessedInclude = preprocessor.preprocess(file.readString(), /*initialRun =*/false);
if (!preprocessedInclude.has_value())
throw *preprocessor.m_error;
if (preprocessor.shouldOnlyIncludeOnce()) {
auto [iter, added] = this->m_onceIncludedFiles.insert(includePath);
if (added) {
auto content = preprocessedInclude.value();
std::replace(content.begin(), content.end(), '\n', ' ');
std::replace(content.begin(), content.end(), '\r', ' ');
output += content;
}
}
this->m_defines = preprocessor.m_defines;
this->m_onceIncludedFiles = preprocessor.m_onceIncludedFiles;
} else if (code.substr(offset, 6) == "define") {
offset += 6;
while (std::isblank(code[offset])) {
offset += 1;
}
std::string defineName;
while (!std::isblank(code[offset])) {
defineName += code[offset];
if (offset >= code.length() || code[offset] == '\n' || code[offset] == '\r')
throwPreprocessorError("no value given in #define directive", lineNumber);
offset += 1;
}
while (std::isblank(code[offset])) {
offset += 1;
if (offset >= code.length())
throwPreprocessorError("no value given in #define directive", lineNumber);
}
std::string replaceValue;
while (code[offset] != '\n' && code[offset] != '\r') {
if (offset >= code.length())
throwPreprocessorError("missing new line after #define directive", lineNumber);
replaceValue += code[offset];
offset += 1;
}
if (replaceValue.empty())
throwPreprocessorError("no value given in #define directive", lineNumber);
2020-11-21 14:39:01 +01:00
this->m_defines.emplace(defineName, replaceValue, lineNumber);
} else if (code.substr(offset, 6) == "pragma") {
offset += 6;
while (std::isblank(code[offset])) {
offset += 1;
2020-11-21 14:39:01 +01:00
if (code[offset] == '\n' || code[offset] == '\r')
throwPreprocessorError("no instruction given in #pragma directive", lineNumber);
}
std::string pragmaKey;
while (!std::isblank(code[offset]) && code[offset] != '\n' && code[offset] != '\r') {
pragmaKey += code[offset];
2020-11-21 14:39:01 +01:00
if (offset >= code.length())
throwPreprocessorError("no instruction given in #pragma directive", lineNumber);
2020-11-21 14:39:01 +01:00
offset += 1;
}
2020-11-21 14:39:01 +01:00
while (std::isblank(code[offset]))
offset += 1;
2020-11-21 14:39:01 +01:00
std::string pragmaValue;
while (code[offset] != '\n' && code[offset] != '\r') {
if (offset >= code.length())
throwPreprocessorError("missing new line after #pragma directive", lineNumber);
2020-11-21 14:39:01 +01:00
pragmaValue += code[offset];
offset += 1;
}
2020-11-21 14:39:01 +01:00
this->m_pragmas.emplace(pragmaKey, pragmaValue, lineNumber);
} else
throwPreprocessorError("unknown preprocessor directive", lineNumber);
} else if (code.substr(offset, 2) == "//") {
while (code[offset] != '\n' && offset < code.length())
2020-11-21 14:39:01 +01:00
offset += 1;
} else if (code.substr(offset, 2) == "/*") {
while (code.substr(offset, 2) != "*/" && offset < code.length()) {
if (code[offset] == '\n') {
output += '\n';
lineNumber++;
}
2020-11-21 14:39:01 +01:00
offset += 1;
}
2020-11-21 14:39:01 +01:00
offset += 2;
if (offset >= code.length())
throwPreprocessorError("unterminated comment", lineNumber - 1);
}
if (code[offset] == '\n') {
lineNumber++;
startOfLine = true;
} else if (!std::isspace(code[offset]))
startOfLine = false;
output += code[offset];
offset += 1;
}
// Apply defines
std::vector<std::tuple<std::string, std::string, u32>> sortedDefines;
std::copy(this->m_defines.begin(), this->m_defines.end(), std::back_inserter(sortedDefines));
std::sort(sortedDefines.begin(), sortedDefines.end(), [](const auto &left, const auto &right) {
return std::get<0>(left).size() > std::get<0>(right).size();
});
for (const auto &[define, value, defineLine] : sortedDefines) {
i32 index = 0;
while ((index = output.find(define, index)) != std::string::npos) {
output.replace(index, define.length(), value);
index += value.length();
}
}
2020-11-21 14:39:01 +01:00
// Handle pragmas
for (const auto &[type, value, pragmaLine] : this->m_pragmas) {
if (this->m_pragmaHandlers.contains(type)) {
if (!this->m_pragmaHandlers[type](value))
throwPreprocessorError(hex::format("invalid value provided to '{0}' #pragma directive", type.c_str()), pragmaLine);
} else
throwPreprocessorError(hex::format("no #pragma handler registered for type {0}", type.c_str()), pragmaLine);
2020-11-21 14:39:01 +01:00
}
} catch (PatternLanguageError &e) {
this->m_error = e;
return std::nullopt;
}
return output;
}
void Preprocessor::addPragmaHandler(const std::string &pragmaType, const std::function<bool(const std::string &)> &function) {
this->m_pragmaHandlers[pragmaType] = function;
2020-11-21 14:39:01 +01:00
}
void Preprocessor::removePragmaHandler(const std::string &pragmaType) {
this->m_pragmaHandlers.erase(pragmaType);
}
void Preprocessor::addDefaultPragmaHandlers() {
this->addPragmaHandler("MIME", [](const std::string &value) {
2020-11-21 14:39:01 +01:00
return !std::all_of(value.begin(), value.end(), isspace) && !value.ends_with('\n') && !value.ends_with('\r');
});
this->addPragmaHandler("endian", [](const std::string &value) {
return value == "big" || value == "little" || value == "native";
});
this->addPragmaHandler("once", [this](const std::string &value) {
this->m_onlyIncludeOnce = true;
return value.empty();
});
2020-11-21 14:39:01 +01:00
}
}