mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-01 21:17:44 -05:00
Huge refactoring of builtin features into an external plugin
This commit is contained in:
@@ -76,7 +76,7 @@ namespace hex {
|
||||
|
||||
/* Pattern Language Functions */
|
||||
|
||||
void ContentRegistry::PatternLanguageFunctions::add(std::string_view name, u32 parameterCount, const std::function<hex::lang::ASTNode*(hex::lang::LogConsole&, std::vector<hex::lang::ASTNode*>)> &func) {
|
||||
void ContentRegistry::PatternLanguageFunctions::add(std::string_view name, u32 parameterCount, const std::function<hex::lang::ASTNode*(hex::lang::Evaluator&, std::vector<hex::lang::ASTNode*>)> &func) {
|
||||
getEntries()[name.data()] = Function{ parameterCount, func };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,182 +1,10 @@
|
||||
#include <hex/lang/evaluator.hpp>
|
||||
#include <hex.hpp>
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
|
||||
|
||||
namespace hex::lang {
|
||||
|
||||
#define LITERAL_COMPARE(literal, cond) std::visit([&, this](auto &&literal) { return (cond) != 0; }, literal)
|
||||
|
||||
void Evaluator::registerBuiltinFunctions() {
|
||||
/* findSequence */
|
||||
ContentRegistry::PatternLanguageFunctions::add("findSequence", ContentRegistry::PatternLanguageFunctions::MoreParametersThan | 1, [this](auto &console, auto params) {
|
||||
auto& occurrenceIndex = asType<ASTNodeIntegerLiteral>(params[0])->getValue();
|
||||
std::vector<u8> sequence;
|
||||
for (u32 i = 1; i < params.size(); i++) {
|
||||
sequence.push_back(std::visit([&](auto &&value) -> u8 {
|
||||
if (value <= 0xFF)
|
||||
return value;
|
||||
else
|
||||
console.abortEvaluation("sequence bytes need to fit into 1 byte");
|
||||
}, asType<ASTNodeIntegerLiteral>(params[i])->getValue()));
|
||||
}
|
||||
|
||||
std::vector<u8> bytes(sequence.size(), 0x00);
|
||||
u32 occurrences = 0;
|
||||
for (u64 offset = 0; offset < this->m_provider->getSize() - sequence.size(); offset++) {
|
||||
this->m_provider->read(offset, bytes.data(), bytes.size());
|
||||
|
||||
if (bytes == sequence) {
|
||||
if (LITERAL_COMPARE(occurrenceIndex, occurrences < occurrenceIndex)) {
|
||||
occurrences++;
|
||||
continue;
|
||||
}
|
||||
|
||||
return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned64Bit, offset });
|
||||
}
|
||||
}
|
||||
|
||||
console.abortEvaluation("failed to find sequence");
|
||||
});
|
||||
|
||||
/* assert */
|
||||
ContentRegistry::PatternLanguageFunctions::add("readUnsigned", 2, [this](auto &console, auto params) {
|
||||
auto address = asType<ASTNodeIntegerLiteral>(params[0])->getValue();
|
||||
auto size = asType<ASTNodeIntegerLiteral>(params[1])->getValue();
|
||||
|
||||
if (LITERAL_COMPARE(address, address >= this->m_provider->getActualSize()))
|
||||
console.abortEvaluation("address out of range");
|
||||
|
||||
return std::visit([&, this](auto &&address, auto &&size) {
|
||||
if (size <= 0 || size > 16)
|
||||
console.abortEvaluation("invalid read size");
|
||||
|
||||
u8 value[(u8)size];
|
||||
this->m_provider->read(address, value, size);
|
||||
|
||||
switch ((u8)size) {
|
||||
case 1: return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned8Bit, hex::changeEndianess(*reinterpret_cast<u8*>(value), 1, this->getCurrentEndian()) });
|
||||
case 2: return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned16Bit, hex::changeEndianess(*reinterpret_cast<u16*>(value), 2, this->getCurrentEndian()) });
|
||||
case 4: return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned32Bit, hex::changeEndianess(*reinterpret_cast<u32*>(value), 4, this->getCurrentEndian()) });
|
||||
case 8: return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned64Bit, hex::changeEndianess(*reinterpret_cast<u64*>(value), 8, this->getCurrentEndian()) });
|
||||
case 16: return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned128Bit, hex::changeEndianess(*reinterpret_cast<u128*>(value), 16, this->getCurrentEndian()) });
|
||||
default: console.abortEvaluation("invalid read size");
|
||||
}
|
||||
}, address, size);
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("readSigned", 2, [this](auto &console, auto params) {
|
||||
auto address = asType<ASTNodeIntegerLiteral>(params[0])->getValue();
|
||||
auto size = asType<ASTNodeIntegerLiteral>(params[1])->getValue();
|
||||
|
||||
if (LITERAL_COMPARE(address, address >= this->m_provider->getActualSize()))
|
||||
console.abortEvaluation("address out of range");
|
||||
|
||||
return std::visit([&, this](auto &&address, auto &&size) {
|
||||
if (size <= 0 || size > 16)
|
||||
console.abortEvaluation("invalid read size");
|
||||
|
||||
u8 value[(u8)size];
|
||||
this->m_provider->read(address, value, size);
|
||||
|
||||
switch ((u8)size) {
|
||||
case 1: return new ASTNodeIntegerLiteral({ Token::ValueType::Signed8Bit, hex::changeEndianess(*reinterpret_cast<s8*>(value), 1, this->getCurrentEndian()) });
|
||||
case 2: return new ASTNodeIntegerLiteral({ Token::ValueType::Signed16Bit, hex::changeEndianess(*reinterpret_cast<s16*>(value), 2, this->getCurrentEndian()) });
|
||||
case 4: return new ASTNodeIntegerLiteral({ Token::ValueType::Signed32Bit, hex::changeEndianess(*reinterpret_cast<s32*>(value), 4, this->getCurrentEndian()) });
|
||||
case 8: return new ASTNodeIntegerLiteral({ Token::ValueType::Signed64Bit, hex::changeEndianess(*reinterpret_cast<s64*>(value), 8, this->getCurrentEndian()) });
|
||||
case 16: return new ASTNodeIntegerLiteral({ Token::ValueType::Signed128Bit, hex::changeEndianess(*reinterpret_cast<s128*>(value), 16, this->getCurrentEndian()) });
|
||||
default: console.abortEvaluation("invalid read size");
|
||||
}
|
||||
}, address, size);
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("assert", 2, [this](auto &console, auto params) {
|
||||
auto condition = asType<ASTNodeIntegerLiteral>(params[0])->getValue();
|
||||
auto message = asType<ASTNodeStringLiteral>(params[1])->getString();
|
||||
|
||||
if (LITERAL_COMPARE(condition, condition == 0))
|
||||
console.abortEvaluation(hex::format("assert failed \"%s\"", message.data()));
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("warnAssert", 2, [this](auto console, auto params) {
|
||||
auto condition = asType<ASTNodeIntegerLiteral>(params[0])->getValue();
|
||||
auto message = asType<ASTNodeStringLiteral>(params[1])->getString();
|
||||
|
||||
if (LITERAL_COMPARE(condition, condition == 0))
|
||||
console.log(LogConsole::Level::Warning, hex::format("assert failed \"%s\"", message.data()));
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("print", ContentRegistry::PatternLanguageFunctions::MoreParametersThan | 0, [](auto &console, auto params) {
|
||||
std::string message;
|
||||
for (auto& param : params) {
|
||||
if (auto integerLiteral = dynamic_cast<ASTNodeIntegerLiteral*>(param); integerLiteral != nullptr) {
|
||||
switch (integerLiteral->getType()) {
|
||||
case Token::ValueType::Character: message += std::get<s8>(integerLiteral->getValue()); break;
|
||||
case Token::ValueType::Unsigned8Bit: message += std::to_string(std::get<u8>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Signed8Bit: message += std::to_string(std::get<s8>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Unsigned16Bit: message += std::to_string(std::get<u16>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Signed16Bit: message += std::to_string(std::get<s16>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Unsigned32Bit: message += std::to_string(std::get<u32>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Signed32Bit: message += std::to_string(std::get<s32>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Unsigned64Bit: message += std::to_string(std::get<u64>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Signed64Bit: message += std::to_string(std::get<s64>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Unsigned128Bit: message += hex::to_string(std::get<u128>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Signed128Bit: message += hex::to_string(std::get<s128>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Float: message += std::to_string(std::get<float>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Double: message += std::to_string(std::get<double>(integerLiteral->getValue())); break;
|
||||
case Token::ValueType::Boolean: message += std::get<s32>(integerLiteral->getValue()) ? "true" : "false"; break;
|
||||
case Token::ValueType::CustomType: message += "< Custom Type >"; break;
|
||||
}
|
||||
}
|
||||
else if (auto stringLiteral = dynamic_cast<ASTNodeStringLiteral*>(param); stringLiteral != nullptr)
|
||||
message += stringLiteral->getString();
|
||||
}
|
||||
|
||||
console.log(LogConsole::Level::Info, message);
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("addressof", 1, [this](auto &console, auto params) -> ASTNode* {
|
||||
auto name = asType<ASTNodeStringLiteral>(params[0])->getString();
|
||||
|
||||
std::vector<std::string> path = splitString(name, ".");
|
||||
auto pattern = this->patternFromName(path);
|
||||
|
||||
return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned64Bit, u64(pattern->getOffset()) });
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("sizeof", 1, [this](auto &console, auto params) -> ASTNode* {
|
||||
auto name = asType<ASTNodeStringLiteral>(params[0])->getString();
|
||||
|
||||
std::vector<std::string> path = splitString(name, ".");
|
||||
auto pattern = this->patternFromName(path);
|
||||
|
||||
return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned64Bit, u64(pattern->getSize()) });
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("nextAfter", 1, [this](auto &console, auto params) -> ASTNode* {
|
||||
auto name = asType<ASTNodeStringLiteral>(params[0])->getString();
|
||||
|
||||
std::vector<std::string> path = splitString(name, ".");
|
||||
auto pattern = this->patternFromName(path);
|
||||
|
||||
return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned64Bit, u64(pattern->getOffset() + pattern->getSize()) });
|
||||
});
|
||||
|
||||
ContentRegistry::PatternLanguageFunctions::add("alignTo", 2, [this](auto &console, auto params) -> ASTNode* {
|
||||
auto alignment = asType<ASTNodeIntegerLiteral>(params[0])->getValue();
|
||||
auto value = asType<ASTNodeIntegerLiteral>(params[1])->getValue();
|
||||
|
||||
auto result = std::visit([](auto &&alignment, auto &&value) {
|
||||
u64 remainder = u64(value) % u64(alignment);
|
||||
return remainder != 0 ? u64(value) + (u64(alignment) - remainder) : u64(value);
|
||||
}, alignment, value);
|
||||
|
||||
return new ASTNodeIntegerLiteral({ Token::ValueType::Unsigned64Bit, u64(result) });
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,8 +13,6 @@ namespace hex::lang {
|
||||
|
||||
Evaluator::Evaluator(prv::Provider* &provider, std::endian defaultDataEndian)
|
||||
: m_provider(provider), m_defaultDataEndian(defaultDataEndian) {
|
||||
|
||||
this->registerBuiltinFunctions();
|
||||
}
|
||||
|
||||
ASTNodeIntegerLiteral* Evaluator::evaluateScopeResolution(ASTNodeScopeResolution *node) {
|
||||
@@ -157,7 +155,7 @@ namespace hex::lang {
|
||||
this->getConsole().abortEvaluation(hex::format("invalid number of parameters for function '%s'. Expected %d", node->getFunctionName().data(), function.parameterCount));
|
||||
}
|
||||
|
||||
return function.func(this->getConsole(), evaluatedParams);
|
||||
return function.func(*this, evaluatedParams);
|
||||
}
|
||||
|
||||
#define FLOAT_BIT_OPERATION(name) \
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace hex::lang {
|
||||
|
||||
if (MATCHES(sequence(VALUETYPE_PADDING, SEPARATOR_SQUAREBRACKETOPEN)))
|
||||
member = parsePadding();
|
||||
else if (MATCHES((optional(KEYWORD_BE), optional(KEYWORD_LE)) && variant(IDENTIFIER, VALUETYPE_ANY) && sequence(IDENTIFIER, SEPARATOR_SQUAREBRACKETOPEN) && none(sequence(SEPARATOR_SQUAREBRACKETOPEN))))
|
||||
else if (MATCHES((optional(KEYWORD_BE), optional(KEYWORD_LE)) && variant(IDENTIFIER, VALUETYPE_ANY) && sequence(IDENTIFIER, SEPARATOR_SQUAREBRACKETOPEN) && sequence<Not>(SEPARATOR_SQUAREBRACKETOPEN)))
|
||||
member = parseMemberArrayVariable();
|
||||
else if (MATCHES((optional(KEYWORD_BE), optional(KEYWORD_LE)) && variant(IDENTIFIER, VALUETYPE_ANY) && sequence(IDENTIFIER)))
|
||||
member = parseMemberVariable();
|
||||
|
||||
120
plugins/libimhex/source/lang/pattern_language.cpp
Normal file
120
plugins/libimhex/source/lang/pattern_language.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include <hex/lang/pattern_language.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
|
||||
#include <hex/lang/preprocessor.hpp>
|
||||
#include <hex/lang/lexer.hpp>
|
||||
#include <hex/lang/parser.hpp>
|
||||
#include <hex/lang/validator.hpp>
|
||||
#include <hex/lang/evaluator.hpp>
|
||||
#include <hex/lang/pattern_data.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace hex::lang {
|
||||
|
||||
PatternLanguage::PatternLanguage(prv::Provider *provider) : m_provider(provider) {
|
||||
this->m_preprocessor = new Preprocessor();
|
||||
this->m_lexer = new Lexer();
|
||||
this->m_parser = new Parser();
|
||||
this->m_validator = new Validator();
|
||||
this->m_evaluator = new Evaluator(provider);
|
||||
}
|
||||
|
||||
PatternLanguage::~PatternLanguage() {
|
||||
delete this->m_preprocessor;
|
||||
delete this->m_lexer;
|
||||
delete this->m_parser;
|
||||
delete this->m_validator;
|
||||
delete this->m_evaluator;
|
||||
}
|
||||
|
||||
|
||||
std::optional<std::vector<PatternData*>> PatternLanguage::executeString(std::string_view string) {
|
||||
this->m_currError.reset();
|
||||
this->m_evaluator->getConsole().clear();
|
||||
|
||||
hex::lang::Preprocessor preprocessor;
|
||||
|
||||
std::endian defaultEndian;
|
||||
preprocessor.addPragmaHandler("endian", [&defaultEndian](std::string value) {
|
||||
if (value == "big") {
|
||||
defaultEndian = std::endian::big;
|
||||
return true;
|
||||
} else if (value == "little") {
|
||||
defaultEndian = std::endian::little;
|
||||
return true;
|
||||
} else if (value == "native") {
|
||||
defaultEndian = std::endian::native;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
});
|
||||
preprocessor.addDefaultPragmaHandlers();
|
||||
|
||||
auto preprocessedCode = preprocessor.preprocess(string.data());
|
||||
if (!preprocessedCode.has_value()) {
|
||||
this->m_currError = preprocessor.getError();
|
||||
return { };
|
||||
}
|
||||
|
||||
hex::lang::Lexer lexer;
|
||||
auto tokens = lexer.lex(preprocessedCode.value());
|
||||
if (!tokens.has_value()) {
|
||||
this->m_currError = lexer.getError();
|
||||
return { };
|
||||
}
|
||||
|
||||
hex::lang::Parser parser;
|
||||
auto ast = parser.parse(tokens.value());
|
||||
if (!ast.has_value()) {
|
||||
this->m_currError = parser.getError();
|
||||
return { };
|
||||
}
|
||||
|
||||
SCOPE_EXIT( for(auto &node : ast.value()) delete node; );
|
||||
|
||||
hex::lang::Validator validator;
|
||||
auto validatorResult = validator.validate(ast.value());
|
||||
if (!validatorResult) {
|
||||
this->m_currError = validator.getError();
|
||||
return { };
|
||||
}
|
||||
|
||||
auto provider = SharedData::currentProvider;
|
||||
hex::lang::Evaluator evaluator(provider, defaultEndian);
|
||||
|
||||
auto patternData = evaluator.evaluate(ast.value());
|
||||
if (!patternData.has_value())
|
||||
return { };
|
||||
|
||||
return patternData.value();
|
||||
}
|
||||
|
||||
std::optional<std::vector<PatternData*>> PatternLanguage::executeFile(std::string_view path) {
|
||||
FILE *file = fopen(path.data(), "r");
|
||||
if (file == nullptr)
|
||||
return { };
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t size = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
std::string code(size + 1, 0x00);
|
||||
fread(code.data(), size, 1, file);
|
||||
|
||||
fclose(file);
|
||||
|
||||
return this->executeString(code);
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::pair<LogConsole::Level, std::string>> PatternLanguage::getConsoleLog() {
|
||||
return this->m_evaluator->getConsole().getLog();
|
||||
}
|
||||
|
||||
std::optional<std::pair<u32, std::string>> PatternLanguage::getError() {
|
||||
return this->m_currError;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user