diff --git a/include/lang/ast_node.hpp b/include/lang/ast_node.hpp index 7c0c7dc4f..7c8e3a369 100644 --- a/include/lang/ast_node.hpp +++ b/include/lang/ast_node.hpp @@ -15,6 +15,7 @@ namespace hex::lang { TypeDecl, Struct, Enum, + Bitfield, Scope, }; @@ -66,6 +67,18 @@ namespace hex::lang { std::vector m_nodes; }; + class ASTNodeBitField : public ASTNode { + public: + explicit ASTNodeBitField(std::string name, std::vector> fields) + : ASTNode(Type::Bitfield), m_name(name), m_fields(fields) { } + + const std::string& getName() const { return this->m_name; } + std::vector> &getFields() { return this->m_fields; } + private: + std::string m_name; + std::vector> m_fields; + }; + class ASTNodeTypeDecl : public ASTNode { public: explicit ASTNodeTypeDecl(const Token::TypeToken::Type &type, const std::string &name, const std::string& customTypeName = "") diff --git a/include/lang/evaluator.hpp b/include/lang/evaluator.hpp index 6eb201d72..187af1eca 100644 --- a/include/lang/evaluator.hpp +++ b/include/lang/evaluator.hpp @@ -22,6 +22,7 @@ namespace hex::lang { std::pair createStructPattern(ASTNodeVariableDecl *varDeclNode, u64 offset); std::pair createEnumPattern(ASTNodeVariableDecl *varDeclNode, u64 offset); + std::pair createBitfieldPattern(ASTNodeVariableDecl *varDeclNode, u64 offset); std::pair createArrayPattern(ASTNodeVariableDecl *varDeclNode, u64 offset); std::pair createStringPattern(ASTNodeVariableDecl *varDeclNode, u64 offset); std::pair createCustomTypePattern(ASTNodeVariableDecl *varDeclNode, u64 offset); diff --git a/include/lang/pattern_data.hpp b/include/lang/pattern_data.hpp index 2f43a9ad1..c3186f6b6 100644 --- a/include/lang/pattern_data.hpp +++ b/include/lang/pattern_data.hpp @@ -117,7 +117,6 @@ namespace hex::lang { protected: void createDefaultEntry(std::string value) { - static u64 id = 0; ImGui::TableNextRow(); ImGui::TreeNodeEx(this->getName().c_str(), ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); ImGui::TableNextColumn(); @@ -125,7 +124,7 @@ namespace hex::lang { ImGui::TableNextColumn(); ImGui::Text("%s", this->getName().c_str()); ImGui::TableNextColumn(); - ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize()); + ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize() - 1); ImGui::TableNextColumn(); ImGui::Text("0x%04lx", this->getSize()); ImGui::TableNextColumn(); @@ -267,7 +266,7 @@ namespace hex::lang { ImGui::TableNextColumn(); bool open = ImGui::TreeNodeEx(this->getName().c_str(), ImGuiTreeNodeFlags_SpanFullWidth); ImGui::TableNextColumn(); - ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize()); + ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize() - 1); ImGui::TableNextColumn(); ImGui::Text("0x%04lx", this->getSize()); ImGui::TableNextColumn(); @@ -312,7 +311,7 @@ namespace hex::lang { ImGui::TableNextColumn(); bool open = ImGui::TreeNodeEx(this->getName().c_str(), ImGuiTreeNodeFlags_SpanFullWidth); ImGui::TableNextColumn(); - ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize()); + ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize() - 1); ImGui::TableNextColumn(); ImGui::Text("0x%04lx", this->getSize()); ImGui::TableNextColumn(); @@ -394,5 +393,65 @@ namespace hex::lang { std::vector> m_enumValues; }; + class PatternDataBitfield : public PatternData { + public: + PatternDataBitfield(u64 offset, size_t size, const std::string &name, const std::string &bitfieldName, std::vector> fields, u32 color = 0) + : PatternData(Type::Enum, offset, size, name, color), m_bitfieldName(bitfieldName), m_fields(fields) { } + + void createEntry(prv::Provider* &provider) override { + u64 value = 0; + provider->read(this->getOffset(), &value, this->getSize()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::ColorButton("color", ImColor(0x00FFFFFF), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_AlphaPreview); + ImGui::TableNextColumn(); + bool open = ImGui::TreeNodeEx(this->getName().c_str(), ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextColumn(); + ImGui::Text("0x%08lx : 0x%08lx", this->getOffset(), this->getOffset() + this->getSize() - 1); + ImGui::TableNextColumn(); + ImGui::Text("0x%04lx", this->getSize()); + ImGui::TableNextColumn(); + ImGui::Text("%s", this->getTypeName().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", "{ ... }"); + + if (open) { + u16 bitOffset = 0; + for (auto &[entryName, entrySize] : this->m_fields) { + ImGui::TableNextRow(); + ImGui::TreeNodeEx(this->getName().c_str(), ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextColumn(); + ImGui::ColorButton("color", ImColor(this->getColor()), ImGuiColorEditFlags_NoTooltip); + ImGui::TableNextColumn(); + ImGui::Text("%s", entryName.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("0x%08lx : 0x%08lx", this->getOffset() + (bitOffset >> 3), this->getOffset() + ((bitOffset + entrySize) >> 3) - 1); + ImGui::TableNextColumn(); + if (entrySize == 1) + ImGui::Text("%llu bit", entrySize); + else + ImGui::Text("%llu bits", entrySize); + ImGui::TableNextColumn(); + ImGui::Text("%s", entryName.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%llx", hex::extract((bitOffset + entrySize) - 1, bitOffset, value)); + bitOffset += entrySize; + } + + ImGui::TreePop(); + } + + } + + std::string getTypeName() override { + return "bitfield " + this->m_bitfieldName; + } + + private: + std::string m_bitfieldName; + std::vector> m_fields; + }; + } \ No newline at end of file diff --git a/include/lang/token.hpp b/include/lang/token.hpp index 79f526a94..28f456188 100644 --- a/include/lang/token.hpp +++ b/include/lang/token.hpp @@ -26,7 +26,8 @@ namespace hex::lang { enum class Keyword { Struct, Using, - Enum + Enum, + Bitfield } keyword; } keywordToken; struct IdentifierToken { diff --git a/include/utils.hpp b/include/utils.hpp index b082197d7..03606c4fd 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -75,6 +75,11 @@ namespace hex { return result; } + [[nodiscard]] constexpr inline u64 extract(u8 from, u8 to, const u64 &value) { + u64 mask = (std::numeric_limits::max() >> (63 - (from - to))) << to; + return (value & mask) >> to; + } + class ScopeExit { public: diff --git a/source/lang/evaluator.cpp b/source/lang/evaluator.cpp index ddb33a6c4..acd3de974 100644 --- a/source/lang/evaluator.cpp +++ b/source/lang/evaluator.cpp @@ -1,5 +1,6 @@ #include "lang/evaluator.hpp" +#include #include namespace hex::lang { @@ -87,6 +88,22 @@ namespace hex::lang { return { new PatternDataEnum(offset, size, varDeclNode->getVariableName(), enumType->getName(), enumType->getValues()), size }; } + std::pair Evaluator::createBitfieldPattern(ASTNodeVariableDecl *varDeclNode, u64 offset) { + + auto *bitfieldType = static_cast(this->m_types[varDeclNode->getCustomVariableTypeName()]); + + if (bitfieldType == nullptr) + return { nullptr, 0 }; + + size_t size = 0; + for (auto &[fieldName, fieldSize] : bitfieldType->getFields()) + size += fieldSize; + + size = std::bit_ceil(size) / 8; + + return { new PatternDataBitfield(offset, size, varDeclNode->getVariableName(), bitfieldType->getName(), bitfieldType->getFields()), size }; + } + std::pair Evaluator::createArrayPattern(ASTNodeVariableDecl *varDeclNode, u64 offset) { std::vector entries; @@ -135,6 +152,8 @@ namespace hex::lang { return this->createStructPattern(varDeclNode, offset); case ASTNode::Type::Enum: return this->createEnumPattern(varDeclNode, offset); + case ASTNode::Type::Bitfield: + return this->createBitfieldPattern(varDeclNode, offset); case ASTNode::Type::TypeDecl: return this->createBuiltInTypePattern(varDeclNode, offset); } @@ -195,6 +214,12 @@ namespace hex::lang { this->m_types.emplace(enumNode->getName(), enumNode); } break; + case ASTNode::Type::Bitfield: + { + auto *bitfieldNode = static_cast(node); + this->m_types.emplace(bitfieldNode->getName(), bitfieldNode); + } + break; case ASTNode::Type::TypeDecl: { auto *typeDeclNode = static_cast(node); diff --git a/source/lang/lexer.cpp b/source/lang/lexer.cpp index 11aa9d3d4..3639913aa 100644 --- a/source/lang/lexer.cpp +++ b/source/lang/lexer.cpp @@ -102,31 +102,31 @@ namespace hex::lang { if (std::isblank(c) || std::isspace(c)) { offset += 1; } else if (c == ';') { - tokens.push_back({.type = Token::Type::EndOfExpression}); + tokens.push_back({ .type = Token::Type::EndOfExpression }); offset += 1; } else if (c == '{') { - tokens.push_back({.type = Token::Type::ScopeOpen}); + tokens.push_back({ .type = Token::Type::ScopeOpen }); offset += 1; } else if (c == '}') { - tokens.push_back({.type = Token::Type::ScopeClose}); + tokens.push_back({ .type = Token::Type::ScopeClose }); offset += 1; } else if (c == '[') { - tokens.push_back({.type = Token::Type::ArrayOpen}); + tokens.push_back({ .type = Token::Type::ArrayOpen }); offset += 1; } else if (c == ']') { tokens.push_back({.type = Token::Type::ArrayClose}); offset += 1; } else if (c == ',') { - tokens.push_back({.type = Token::Type::Separator}); + tokens.push_back({ .type = Token::Type::Separator }); offset += 1; } else if (c == '@') { - tokens.push_back({.type = Token::Type::Operator, .operatorToken = { .op = Token::OperatorToken::Operator::AtDeclaration}}); + tokens.push_back({ .type = Token::Type::Operator, .operatorToken = { .op = Token::OperatorToken::Operator::AtDeclaration } }); offset += 1; } else if (c == '=') { - tokens.push_back({.type = Token::Type::Operator, .operatorToken = { .op = Token::OperatorToken::Operator::Assignment}}); + tokens.push_back({ .type = Token::Type::Operator, .operatorToken = { .op = Token::OperatorToken::Operator::Assignment } }); offset += 1; } else if (c == ':') { - tokens.push_back({.type = Token::Type::Operator, .operatorToken = { .op = Token::OperatorToken::Operator::Inherit}}); + tokens.push_back({ .type = Token::Type::Operator, .operatorToken = { .op = Token::OperatorToken::Operator::Inherit } }); offset += 1; } else if (std::isalpha(c)) { std::string identifier = matchTillInvalid(&code[offset], [](char c) -> bool { return std::isalnum(c) || c == '_'; }); @@ -134,42 +134,44 @@ namespace hex::lang { // Check for reserved keywords if (identifier == "struct") - tokens.push_back({.type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Struct}}); + tokens.push_back({ .type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Struct } }); else if (identifier == "using") - tokens.push_back({.type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Using}}); + tokens.push_back({ .type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Using } }); else if (identifier == "enum") - tokens.push_back({.type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Enum}}); + tokens.push_back({ .type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Enum } }); + else if (identifier == "bitfield") + tokens.push_back({ .type = Token::Type::Keyword, .keywordToken = { .keyword = Token::KeywordToken::Keyword::Bitfield } }); // Check for built-in types else if (identifier == "u8") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned8Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned8Bit } }); else if (identifier == "s8") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed8Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed8Bit } }); else if (identifier == "u16") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned16Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned16Bit } }); else if (identifier == "s16") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed16Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed16Bit } }); else if (identifier == "u32") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned32Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned32Bit } }); else if (identifier == "s32") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed32Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed32Bit } }); else if (identifier == "u64") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned64Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned64Bit } }); else if (identifier == "s64") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed64Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed64Bit } }); else if (identifier == "u128") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned128Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Unsigned128Bit } }); else if (identifier == "s128") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed128Bit }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Signed128Bit } }); else if (identifier == "float") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Float }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Float } }); else if (identifier == "double") - tokens.push_back({.type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Double }}); + tokens.push_back({ .type = Token::Type::Type, .typeToken = { .type = Token::TypeToken::Type::Double } }); // If it's not a keyword and a builtin type, it has to be an identifier else - tokens.push_back({.type = Token::Type::Identifier, .identifierToken = { .identifier = identifier}}); + tokens.push_back({.type = Token::Type::Identifier, .identifierToken = { .identifier = identifier } }); offset += identifier.length(); } else if (std::isdigit(c)) { @@ -181,12 +183,12 @@ namespace hex::lang { if (!integer.has_value()) return { ResultLexicalError, {}}; - tokens.push_back({.type = Token::Type::Integer, .integerToken = { .integer = integer.value() }}); + tokens.push_back({ .type = Token::Type::Integer, .integerToken = { .integer = integer.value() } }); offset += (end - &code[offset]); } else return { ResultLexicalError, {}}; } - tokens.push_back({.type = Token::Type::EndOfProgram}); + tokens.push_back({ .type = Token::Type::EndOfProgram }); return { ResultSuccess, tokens }; } diff --git a/source/lang/parser.cpp b/source/lang/parser.cpp index 14912e818..5fd4bd54e 100644 --- a/source/lang/parser.cpp +++ b/source/lang/parser.cpp @@ -121,6 +121,26 @@ namespace hex::lang { return enumNode; } + ASTNode *parseBitField(TokenIter &curr) { + const std::string &bitfieldName = curr[-2].identifierToken.identifier; + std::vector> fields; + + while (!tryConsume(curr, {Token::Type::ScopeClose})) { + if (tryConsume(curr, {Token::Type::Identifier, Token::Type::Operator, Token::Type::Integer, Token::Type::EndOfExpression})) { + if (curr[-3].operatorToken.op != Token::OperatorToken::Operator::Inherit) + return nullptr; + + fields.emplace_back(curr[-4].identifierToken.identifier, curr[-2].integerToken.integer); + } + else break; + } + + if (!tryConsume(curr, {Token::Type::EndOfExpression})) + return nullptr; + + return new ASTNodeBitField(bitfieldName, fields); + } + ASTNode *parseScope(TokenIter &curr) { return new ASTNodeScope(parseTillToken(curr, Token::Type::ScopeClose)); } @@ -163,12 +183,21 @@ namespace hex::lang { } program.push_back(structAst); + } else if (curr[-3].keywordToken.keyword == Token::KeywordToken::Keyword::Bitfield) { + auto bitfieldAst = parseBitField(curr); + + if (bitfieldAst == nullptr) { + for(auto &node : program) delete node; + return { }; + } + + program.push_back(bitfieldAst); } return program; } // Enum - if (tryConsume(curr, { Token::Type::Keyword, Token::Type::Identifier, Token::Type::Operator, Token::Type::Type, Token::Type::ScopeOpen })) { + else if (tryConsume(curr, { Token::Type::Keyword, Token::Type::Identifier, Token::Type::Operator, Token::Type::Type, Token::Type::ScopeOpen })) { if (curr[-5].keywordToken.keyword == Token::KeywordToken::Keyword::Enum) { auto enumAst = parseEnum(curr); @@ -181,7 +210,6 @@ namespace hex::lang { } return program; - // Scope } else if (tryConsume(curr, { Token::Type::ScopeOpen })) { program.push_back(parseScope(curr)); diff --git a/source/lang/validator.cpp b/source/lang/validator.cpp index 6760b2704..ae752fee8 100644 --- a/source/lang/validator.cpp +++ b/source/lang/validator.cpp @@ -61,6 +61,27 @@ namespace hex::lang { if (!constantNames.insert(name).second) return false; } + case ASTNode::Type::Bitfield: + { + // Check for duplicate type name + auto bitfieldNode = static_cast(node); + if (!typeNames.insert(bitfieldNode->getName()).second) + return false; + + size_t bitfieldSize = 0; + + // Check for duplicate constant names + std::unordered_set flagNames; + for (const auto &[name, size] : bitfieldNode->getFields()) { + if (!flagNames.insert(name).second) + return false; + + bitfieldSize += size; + } + + if (bitfieldSize > 64) + return false; + } break; } } diff --git a/source/views/view_help.cpp b/source/views/view_help.cpp index 4672e1e00..0fc44e59e 100644 --- a/source/views/view_help.cpp +++ b/source/views/view_help.cpp @@ -109,6 +109,18 @@ namespace hex { "}" ); + DrawTitle("Bitfields"); + ImGui::TextWrapped( + "To decode values that are stored in fields that don't follow the typical 8 bit alignment, bitfields can be used. " + "The size of these fields get specified in numbers of bits."); + DrawCodeSegment("bitfield", 70, + "bitfield Permission {\n" + " r : 1;\n" + " w : 1;\n" + " x : 1;\n" + "}" + ); + DrawTitle("Enum"); ImGui::TextWrapped( "If a value can only be a few specific values with special meaning, an enum can be used. " diff --git a/source/views/view_pattern.cpp b/source/views/view_pattern.cpp index e707342e0..3178e11f3 100644 --- a/source/views/view_pattern.cpp +++ b/source/views/view_pattern.cpp @@ -14,7 +14,7 @@ namespace hex { static TextEditor::LanguageDefinition langDef; if (!initialized) { static const char* const keywords[] = { - "using", "struct", "enum" + "using", "struct", "enum", "bitfield" }; for (auto& k : keywords) langDef.mKeywords.insert(k); @@ -96,6 +96,10 @@ namespace hex { if (ImGui::Begin("Pattern", &this->m_windowOpen, ImGuiWindowFlags_None)) { this->m_textEditor.Render("Pattern"); + + if (this->m_textEditor.IsTextChanged()) { + this->parsePattern(this->m_textEditor.GetText().data()); + } } ImGui::End();