| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/cddl/sema.h" |
| |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <cinttypes> |
| #include <cstdlib> |
| #include <iostream> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "absl/algorithm/container.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "tools/cddl/logging.h" |
| |
| std::vector<CppType*> CppSymbolTable::TypesWithId() { |
| if (!this->TypesWithId_.size()) { |
| for (const std::unique_ptr<CppType>& ptr : this->cpp_types) { |
| if (ptr->type_key == absl::nullopt) { |
| continue; |
| } |
| this->TypesWithId_.emplace_back(ptr.get()); |
| } |
| } |
| return this->TypesWithId_; |
| } |
| |
| CddlType::CddlType() |
| : map(nullptr), op(CddlType::Op::kNone), constraint_type(nullptr) {} |
| CddlType::~CddlType() { |
| switch (which) { |
| case CddlType::Which::kDirectChoice: |
| direct_choice.std::vector<CddlType*>::~vector(); |
| break; |
| case CddlType::Which::kValue: |
| value.std::string::~basic_string(); |
| break; |
| case CddlType::Which::kId: |
| id.std::string::~basic_string(); |
| break; |
| case CddlType::Which::kMap: |
| break; |
| case CddlType::Which::kArray: |
| break; |
| case CddlType::Which::kGroupChoice: |
| break; |
| case CddlType::Which::kGroupnameChoice: |
| break; |
| case CddlType::Which::kTaggedType: |
| tagged_type.~TaggedType(); |
| break; |
| } |
| } |
| |
| CddlGroup::Entry::Entry() : group(nullptr) {} |
| CddlGroup::Entry::~Entry() { |
| switch (which) { |
| case CddlGroup::Entry::Which::kUninitialized: |
| break; |
| case CddlGroup::Entry::Which::kType: |
| type.~EntryType(); |
| break; |
| case CddlGroup::Entry::Which::kGroup: |
| break; |
| } |
| } |
| |
| CppType::CppType() : vector_type() {} |
| CppType::~CppType() { |
| switch (which) { |
| case CppType::Which::kUninitialized: |
| break; |
| case CppType::Which::kUint64: |
| break; |
| case CppType::Which::kString: |
| break; |
| case CppType::Which::kBytes: |
| break; |
| case CppType::Which::kVector: |
| break; |
| case CppType::Which::kEnum: |
| enum_type.~Enum(); |
| break; |
| case CppType::Which::kStruct: |
| struct_type.~Struct(); |
| break; |
| case CppType::Which::kOptional: |
| break; |
| case CppType::Which::kDiscriminatedUnion: |
| discriminated_union.~DiscriminatedUnion(); |
| break; |
| case CppType::Which::kTaggedType: |
| break; |
| } |
| } |
| |
| void CppType::InitVector() { |
| which = Which::kVector; |
| new (&vector_type) Vector(); |
| } |
| |
| void CppType::InitEnum() { |
| which = Which::kEnum; |
| new (&enum_type) Enum(); |
| } |
| |
| void CppType::InitStruct() { |
| which = Which::kStruct; |
| new (&struct_type) Struct(); |
| } |
| |
| void CppType::InitDiscriminatedUnion() { |
| which = Which::kDiscriminatedUnion; |
| new (&discriminated_union) DiscriminatedUnion(); |
| } |
| |
| void CppType::InitBytes() { |
| which = Which::kBytes; |
| } |
| |
| void InitString(std::string* s, absl::string_view value) { |
| new (s) std::string(value); |
| } |
| |
| void InitDirectChoice(std::vector<CddlType*>* direct_choice) { |
| new (direct_choice) std::vector<CddlType*>(); |
| } |
| |
| void InitGroupEntry(CddlGroup::Entry::EntryType* entry) { |
| new (entry) CddlGroup::Entry::EntryType(); |
| } |
| |
| CddlType* AddCddlType(CddlSymbolTable* table, CddlType::Which which) { |
| table->types.emplace_back(new CddlType); |
| CddlType* value = table->types.back().get(); |
| value->which = which; |
| return value; |
| } |
| |
| CddlType* AnalyzeType(CddlSymbolTable* table, const AstNode& type); |
| CddlGroup* AnalyzeGroup(CddlSymbolTable* table, const AstNode& group); |
| |
| CddlType* AnalyzeType2(CddlSymbolTable* table, const AstNode& type2) { |
| const AstNode* node = type2.children; |
| if (node->type == AstNode::Type::kNumber || |
| node->type == AstNode::Type::kText || |
| node->type == AstNode::Type::kBytes) { |
| CddlType* value = AddCddlType(table, CddlType::Which::kValue); |
| InitString(&value->value, node->text); |
| return value; |
| } else if (node->type == AstNode::Type::kTypename) { |
| if (type2.text[0] == '~') { |
| dprintf(STDERR_FILENO, "We don't support the '~' operator.\n"); |
| return nullptr; |
| } |
| CddlType* id = AddCddlType(table, CddlType::Which::kId); |
| InitString(&id->id, node->text); |
| return id; |
| } else if (node->type == AstNode::Type::kType) { |
| if (type2.text[0] == '#' && type2.text[1] == '6' && type2.text[2] == '.') { |
| CddlType* tagged_type = AddCddlType(table, CddlType::Which::kTaggedType); |
| tagged_type->tagged_type.tag_value = |
| atoll(type2.text.substr(3 /* #6. */).data()); |
| tagged_type->tagged_type.type = AnalyzeType(table, *node); |
| return tagged_type; |
| } |
| dprintf(STDERR_FILENO, "Unknown type2 value, expected #6.[uint]\n"); |
| } else if (node->type == AstNode::Type::kGroup) { |
| if (type2.text[0] == '{') { |
| CddlType* map = AddCddlType(table, CddlType::Which::kMap); |
| map->map = AnalyzeGroup(table, *node); |
| return map; |
| } else if (type2.text[0] == '[') { |
| CddlType* array = AddCddlType(table, CddlType::Which::kArray); |
| array->array = AnalyzeGroup(table, *node); |
| return array; |
| } else if (type2.text[0] == '&') { |
| // Represents a choice between options in this group (ie an enum), not a |
| // choice between groups (which is currently unsupported). |
| CddlType* group_choice = |
| AddCddlType(table, CddlType::Which::kGroupChoice); |
| group_choice->group_choice = AnalyzeGroup(table, *node); |
| return group_choice; |
| } |
| } else if (node->type == AstNode::Type::kGroupname) { |
| if (type2.text[0] == '&') { |
| CddlType* group_choice = |
| AddCddlType(table, CddlType::Which::kGroupnameChoice); |
| InitString(&group_choice->id, node->text); |
| return group_choice; |
| } |
| } |
| return nullptr; |
| } |
| |
| CddlType::Op AnalyzeRangeop(const AstNode& rangeop) { |
| if (rangeop.text == "..") { |
| return CddlType::Op::kInclusiveRange; |
| } else if (rangeop.text == "...") { |
| return CddlType::Op::kExclusiveRange; |
| } else { |
| dprintf(STDERR_FILENO, "Unsupported '%s' range operator.\n", |
| rangeop.text.c_str()); |
| return CddlType::Op::kNone; |
| } |
| } |
| |
| CddlType::Op AnalyzeCtlop(const AstNode& ctlop) { |
| if (!ctlop.children) { |
| dprintf(STDERR_FILENO, "Missing id for control operator '%s'.\n", |
| ctlop.text.c_str()); |
| return CddlType::Op::kNone; |
| } |
| const std::string& id = ctlop.children->text; |
| if (id == "size") { |
| return CddlType::Op::kSize; |
| } else if (id == "bits") { |
| return CddlType::Op::kBits; |
| } else if (id == "regexp") { |
| return CddlType::Op::kRegexp; |
| } else if (id == "cbor") { |
| return CddlType::Op::kCbor; |
| } else if (id == "cborseq") { |
| return CddlType::Op::kCborseq; |
| } else if (id == "within") { |
| return CddlType::Op::kWithin; |
| } else if (id == "and") { |
| return CddlType::Op::kAnd; |
| } else if (id == "lt") { |
| return CddlType::Op::kLess; |
| } else if (id == "le") { |
| return CddlType::Op::kLessOrEqual; |
| } else if (id == "gt") { |
| return CddlType::Op::kGreater; |
| } else if (id == "ge") { |
| return CddlType::Op::kGreaterOrEqual; |
| } else if (id == "eq") { |
| return CddlType::Op::kEqual; |
| } else if (id == "ne") { |
| return CddlType::Op::kNotEqual; |
| } else if (id == "default") { |
| return CddlType::Op::kDefault; |
| } else { |
| dprintf(STDERR_FILENO, "Unsupported '%s' control operator.\n", |
| ctlop.text.c_str()); |
| return CddlType::Op::kNone; |
| } |
| } |
| |
| // Produces CddlType by analyzing AST parsed from type1 rule |
| // ABNF rule: type1 = type2 [S (rangeop / ctlop) S type2] |
| CddlType* AnalyzeType1(CddlSymbolTable* table, const AstNode& type1) { |
| if (!type1.children) { |
| dprintf(STDERR_FILENO, "Missing type2 in type1 '%s'.\n", |
| type1.text.c_str()); |
| return nullptr; |
| } |
| const AstNode& target_type = *type1.children; |
| CddlType* analyzed_type = AnalyzeType2(table, target_type); |
| if (!analyzed_type) { |
| dprintf(STDERR_FILENO, "Invalid type2 '%s' in type1 '%s'.\n", |
| target_type.text.c_str(), type1.text.c_str()); |
| return nullptr; |
| } |
| if (!target_type.sibling) { |
| // No optional range or control operator, return type as-is |
| return analyzed_type; |
| } |
| const AstNode& operator_type = *target_type.sibling; |
| CddlType::Op op; |
| if (operator_type.type == AstNode::Type::kRangeop) { |
| op = AnalyzeRangeop(operator_type); |
| } else if (operator_type.type == AstNode::Type::kCtlop) { |
| op = AnalyzeCtlop(operator_type); |
| } else { |
| op = CddlType::Op::kNone; |
| } |
| if (op == CddlType::Op::kNone) { |
| dprintf(STDERR_FILENO, |
| "Unsupported or missing operator '%s' in type1 '%s'.\n", |
| operator_type.text.c_str(), type1.text.c_str()); |
| return nullptr; |
| } |
| if (!operator_type.sibling) { |
| dprintf(STDERR_FILENO, |
| "Missing controller type for operator '%s' in type1 '%s'.\n", |
| operator_type.text.c_str(), type1.text.c_str()); |
| return nullptr; |
| } |
| const AstNode& controller_type = *operator_type.sibling; |
| CddlType* constraint_type = AnalyzeType2(table, controller_type); |
| if (!constraint_type) { |
| dprintf(STDERR_FILENO, |
| "Invalid controller type '%s' for operator '%s' in type1 '%s'.\n", |
| controller_type.text.c_str(), operator_type.text.c_str(), |
| type1.text.c_str()); |
| return nullptr; |
| } |
| analyzed_type->op = op; |
| analyzed_type->constraint_type = constraint_type; |
| return analyzed_type; |
| } |
| |
| CddlType* AnalyzeType(CddlSymbolTable* table, const AstNode& type) { |
| const AstNode* type1 = type.children; |
| if (type1->sibling) { |
| // If the type we are looking at has a type choice, create a top-level |
| // choice object, with a vector containing all valid choices. |
| CddlType* type_choice = AddCddlType(table, CddlType::Which::kDirectChoice); |
| InitDirectChoice(&type_choice->direct_choice); |
| while (type1) { |
| type_choice->direct_choice.push_back(AnalyzeType1(table, *type1)); |
| type1 = type1->sibling; |
| } |
| return type_choice; |
| } else { |
| // Else just return the single choice. |
| return AnalyzeType1(table, *type1); |
| } |
| } |
| |
| bool AnalyzeGroupEntry(CddlSymbolTable* table, |
| const AstNode& group_entry, |
| CddlGroup::Entry* entry); |
| |
| CddlGroup* AnalyzeGroup(CddlSymbolTable* table, const AstNode& group) { |
| // NOTE: |group.children| is a grpchoice, which we don't currently handle. |
| // Therefore, we assume it has no siblings and move on to immediately handling |
| // its grpent children. |
| const AstNode* node = group.children->children; |
| table->groups.emplace_back(new CddlGroup); |
| CddlGroup* group_def = table->groups.back().get(); |
| while (node) { |
| group_def->entries.emplace_back(new CddlGroup::Entry); |
| AnalyzeGroupEntry(table, *node, group_def->entries.back().get()); |
| node = node->sibling; |
| } |
| return group_def; |
| } |
| |
| // Parses a string into an optional uint64_t, with the value being that |
| // represented by the string if it is present and nullopt if it cannot |
| // be parsed. |
| // TODO(rwkeane): Add support for hex and binary options. |
| absl::optional<uint64_t> ParseOptionalUint(const std::string& text) { |
| if (text == "0") { |
| return 0; |
| } |
| |
| uint64_t parsed = std::strtoul(text.c_str(), nullptr, 10); |
| if (!parsed) { |
| return absl::nullopt; |
| } |
| return parsed; |
| } |
| |
| bool AnalyzeGroupEntry(CddlSymbolTable* table, |
| const AstNode& group_entry, |
| CddlGroup::Entry* entry) { |
| const AstNode* node = group_entry.children; |
| |
| // If it's an occurance operator (so the entry is optional), mark it as such |
| // and proceed to the next the node. |
| if (node->type == AstNode::Type::kOccur) { |
| if (node->text == "?") { |
| entry->opt_occurrence_min = CddlGroup::Entry::kOccurrenceMinUnbounded; |
| entry->opt_occurrence_max = 1; |
| } else if (node->text == "+") { |
| entry->opt_occurrence_min = 1; |
| entry->opt_occurrence_max = CddlGroup::Entry::kOccurrenceMaxUnbounded; |
| } else { |
| auto index = node->text.find('*'); |
| if (index == std::string::npos) { |
| return false; |
| } |
| |
| int lower_bound = CddlGroup::Entry::kOccurrenceMinUnbounded; |
| std::string first_half = node->text.substr(0, index); |
| if ((first_half.length() != 1 || first_half.at(0) != '0') && |
| first_half.length() != 0) { |
| lower_bound = std::atoi(first_half.c_str()); |
| if (!lower_bound) { |
| return false; |
| } |
| } |
| |
| int upper_bound = CddlGroup::Entry::kOccurrenceMaxUnbounded; |
| std::string second_half = |
| index >= node->text.length() ? "" : node->text.substr(index + 1); |
| if ((second_half.length() != 1 || second_half.at(0) != '0') && |
| second_half.length() != 0) { |
| upper_bound = std::atoi(second_half.c_str()); |
| if (!upper_bound) { |
| return false; |
| } |
| } |
| |
| entry->opt_occurrence_min = lower_bound; |
| entry->opt_occurrence_max = upper_bound; |
| } |
| entry->occurrence_specified = true; |
| node = node->sibling; |
| } else { |
| entry->opt_occurrence_min = 1; |
| entry->opt_occurrence_max = 1; |
| entry->occurrence_specified = false; |
| } |
| |
| // If it's a member key (key in a map), save it and go to next node. |
| if (node->type == AstNode::Type::kMemberKey) { |
| if (node->text[node->text.size() - 1] == '>') |
| return false; |
| entry->which = CddlGroup::Entry::Which::kType; |
| InitGroupEntry(&entry->type); |
| entry->type.opt_key = std::string(node->children->text); |
| entry->type.integer_key = ParseOptionalUint(node->integer_member_key_text); |
| node = node->sibling; |
| } |
| |
| // If it's a type, process it as such. |
| if (node->type == AstNode::Type::kType) { |
| if (entry->which == CddlGroup::Entry::Which::kUninitialized) { |
| entry->which = CddlGroup::Entry::Which::kType; |
| InitGroupEntry(&entry->type); |
| } |
| entry->type.value = AnalyzeType(table, *node); |
| } else if (node->type == AstNode::Type::kGroupname) { |
| return false; |
| } else if (node->type == AstNode::Type::kGroup) { |
| entry->which = CddlGroup::Entry::Which::kGroup; |
| entry->group = AnalyzeGroup(table, *node); |
| } |
| return true; |
| } |
| |
| std::pair<bool, CddlSymbolTable> BuildSymbolTable(const AstNode& rules) { |
| std::pair<bool, CddlSymbolTable> result; |
| result.first = false; |
| auto& table = result.second; |
| |
| // Parse over all rules iteratively. |
| for (const AstNode* rule = &rules; rule; rule = rule->sibling) { |
| AstNode* node = rule->children; |
| |
| // Ensure that the node is either a type or group definition. |
| if (node->type != AstNode::Type::kTypename && |
| node->type != AstNode::Type::kGroupname) { |
| Logger::Error("Error parsing node with text '%s'. Unexpected node type.", |
| node->text); |
| return result; |
| } |
| bool is_type = node->type == AstNode::Type::kTypename; |
| absl::string_view name = node->text; |
| |
| // Ensure that the node is assignment. |
| node = node->sibling; |
| if (node->type != AstNode::Type::kAssign) { |
| Logger::Error("Error parsing node with text '%s'. Node type != kAssign.", |
| node->text); |
| return result; |
| } |
| |
| // Process the definition. |
| node = node->sibling; |
| if (is_type) { |
| CddlType* type = AnalyzeType(&table, *node); |
| if (rule->type_key != absl::nullopt) { |
| auto parsed_type_key = ParseOptionalUint(rule->type_key.value()); |
| if (parsed_type_key == absl::nullopt) { |
| return result; |
| } |
| type->type_key = parsed_type_key.value(); |
| } |
| if (!type) { |
| Logger::Error( |
| "Error parsing node with text '%s'." |
| "Failed to analyze node type.", |
| node->text); |
| } |
| table.type_map.emplace(std::string(name), type); |
| } else { |
| table.groups.emplace_back(new CddlGroup); |
| CddlGroup* group = table.groups.back().get(); |
| group->entries.emplace_back(new CddlGroup::Entry); |
| AnalyzeGroupEntry(&table, *node, group->entries.back().get()); |
| table.group_map.emplace(std::string(name), group); |
| } |
| } |
| |
| DumpSymbolTable(&result.second); |
| |
| result.first = true; |
| return result; |
| } |
| |
| // Fetches a C++ Type from all known definitons, or inserts a placeholder to be |
| // updated later if the type hasn't been defined yet. |
| CppType* GetCppType(CppSymbolTable* table, const std::string& name) { |
| if (name.empty()) { |
| table->cpp_types.emplace_back(new CppType); |
| return table->cpp_types.back().get(); |
| } |
| auto entry = table->cpp_type_map.find(name); |
| if (entry != table->cpp_type_map.end()) |
| return entry->second; |
| table->cpp_types.emplace_back(new CppType); |
| table->cpp_type_map.emplace(name, table->cpp_types.back().get()); |
| return table->cpp_types.back().get(); |
| } |
| |
| bool IncludeGroupMembersInEnum(CppSymbolTable* table, |
| const CddlSymbolTable& cddl_table, |
| CppType* cpp_type, |
| const CddlGroup& group); |
| |
| bool IncludeGroupMembersInSubEnum(CppSymbolTable* table, |
| const CddlSymbolTable& cddl_table, |
| CppType* cpp_type, |
| const std::string& name) { |
| auto group_entry = cddl_table.group_map.find(name); |
| if (group_entry == cddl_table.group_map.end()) { |
| return false; |
| } |
| if (group_entry->second->entries.size() != 1 || |
| group_entry->second->entries[0]->which != |
| CddlGroup::Entry::Which::kGroup) { |
| return false; |
| } |
| CppType* sub_enum = GetCppType(table, name); |
| if (sub_enum->which == CppType::Which::kUninitialized) { |
| sub_enum->InitEnum(); |
| sub_enum->name = name; |
| if (!IncludeGroupMembersInEnum(table, cddl_table, sub_enum, |
| *group_entry->second->entries[0]->group)) { |
| return false; |
| } |
| } |
| cpp_type->enum_type.sub_members.push_back(sub_enum); |
| return true; |
| } |
| |
| bool IncludeGroupMembersInEnum(CppSymbolTable* table, |
| const CddlSymbolTable& cddl_table, |
| CppType* cpp_type, |
| const CddlGroup& group) { |
| for (const auto& x : group.entries) { |
| if (x->HasOccurrenceOperator() || |
| x->which != CddlGroup::Entry::Which::kType) { |
| return false; |
| } |
| if (x->type.value->which == CddlType::Which::kValue && |
| !x->type.opt_key.empty()) { |
| cpp_type->enum_type.members.emplace_back( |
| x->type.opt_key, atoi(x->type.value->value.c_str())); |
| } else if (x->type.value->which == CddlType::Which::kId) { |
| IncludeGroupMembersInSubEnum(table, cddl_table, cpp_type, |
| x->type.value->id); |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| CppType* MakeCppType(CppSymbolTable* table, |
| const CddlSymbolTable& cddl_table, |
| const std::string& name, |
| const CddlType& type); |
| |
| bool AddMembersToStruct( |
| CppSymbolTable* table, |
| const CddlSymbolTable& cddl_table, |
| CppType* cpp_type, |
| const std::vector<std::unique_ptr<CddlGroup::Entry>>& entries) { |
| for (const auto& x : entries) { |
| if (x->which == CddlGroup::Entry::Which::kType) { |
| if (x->type.opt_key.empty()) { |
| // If the represented node has no text (ie - it's code generated) then |
| // it must have an inner type that is based on the user input. If this |
| // one looks as expected, process it recursively. |
| if (x->type.value->which != CddlType::Which::kId || |
| x->HasOccurrenceOperator()) { |
| return false; |
| } |
| auto group_entry = cddl_table.group_map.find(x->type.value->id); |
| if (group_entry == cddl_table.group_map.end()) |
| return false; |
| if (group_entry->second->entries.size() != 1 || |
| group_entry->second->entries[0]->which != |
| CddlGroup::Entry::Which::kGroup) { |
| return false; |
| } |
| if (!AddMembersToStruct( |
| table, cddl_table, cpp_type, |
| group_entry->second->entries[0]->group->entries)) { |
| return false; |
| } |
| } else { |
| // Here it is a real type definition - so process it as such. |
| CppType* member_type = |
| MakeCppType(table, cddl_table, |
| cpp_type->name + std::string("_") + x->type.opt_key, |
| *x->type.value); |
| if (!member_type) |
| return false; |
| if (member_type->name.empty()) |
| member_type->name = x->type.opt_key; |
| if (x->opt_occurrence_min == |
| CddlGroup::Entry::kOccurrenceMinUnbounded && |
| x->opt_occurrence_max == 1) { |
| // Create an "optional" type, with sub-type being the type that is |
| // optional. This corresponds with occurrence operator '?'. |
| table->cpp_types.emplace_back(new CppType); |
| CppType* optional_type = table->cpp_types.back().get(); |
| optional_type->which = CppType::Which::kOptional; |
| optional_type->optional_type = member_type; |
| cpp_type->struct_type.members.emplace_back( |
| x->type.opt_key, x->type.integer_key, optional_type); |
| } else { |
| cpp_type->struct_type.members.emplace_back( |
| x->type.opt_key, x->type.integer_key, member_type); |
| } |
| } |
| } else { |
| // If it's not a type, it's a group so add its members recursuvely. |
| if (!AddMembersToStruct(table, cddl_table, cpp_type, x->group->entries)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| CppType* MakeCppType(CppSymbolTable* table, |
| const CddlSymbolTable& cddl_table, |
| const std::string& name, |
| const CddlType& type) { |
| CppType* cpp_type = nullptr; |
| switch (type.which) { |
| case CddlType::Which::kId: { |
| if (type.id == "uint") { |
| cpp_type = GetCppType(table, name); |
| cpp_type->which = CppType::Which::kUint64; |
| } else if (type.id == "text") { |
| cpp_type = GetCppType(table, name); |
| cpp_type->which = CppType::Which::kString; |
| } else if (type.id == "bytes") { |
| cpp_type = GetCppType(table, name); |
| cpp_type->InitBytes(); |
| if (type.op == CddlType::Op::kSize) { |
| size_t size = 0; |
| if (!absl::SimpleAtoi(type.constraint_type->value, &size)) { |
| return nullptr; |
| } |
| cpp_type->bytes_type.fixed_size = size; |
| } |
| } else { |
| cpp_type = GetCppType(table, type.id); |
| } |
| } break; |
| case CddlType::Which::kMap: { |
| cpp_type = GetCppType(table, name); |
| cpp_type->InitStruct(); |
| cpp_type->struct_type.key_type = CppType::Struct::KeyType::kMap; |
| cpp_type->name = name; |
| if (!AddMembersToStruct(table, cddl_table, cpp_type, type.map->entries)) |
| return nullptr; |
| } break; |
| case CddlType::Which::kArray: { |
| cpp_type = GetCppType(table, name); |
| if (type.array->entries.size() == 1 && |
| type.array->entries[0]->HasOccurrenceOperator()) { |
| cpp_type->InitVector(); |
| cpp_type->vector_type.min_length = |
| type.array->entries[0]->opt_occurrence_min; |
| cpp_type->vector_type.max_length = |
| type.array->entries[0]->opt_occurrence_max; |
| cpp_type->vector_type.element_type = |
| GetCppType(table, type.array->entries[0]->type.value->id); |
| } else { |
| cpp_type->InitStruct(); |
| cpp_type->struct_type.key_type = CppType::Struct::KeyType::kArray; |
| cpp_type->name = name; |
| if (!AddMembersToStruct(table, cddl_table, cpp_type, |
| type.map->entries)) { |
| return nullptr; |
| } |
| } |
| } break; |
| case CddlType::Which::kGroupChoice: { |
| cpp_type = GetCppType(table, name); |
| cpp_type->InitEnum(); |
| cpp_type->name = name; |
| if (!IncludeGroupMembersInEnum(table, cddl_table, cpp_type, |
| *type.group_choice)) { |
| return nullptr; |
| } |
| } break; |
| case CddlType::Which::kGroupnameChoice: { |
| cpp_type = GetCppType(table, name); |
| cpp_type->InitEnum(); |
| cpp_type->name = name; |
| if (!IncludeGroupMembersInSubEnum(table, cddl_table, cpp_type, type.id)) { |
| return nullptr; |
| } |
| } break; |
| case CddlType::Which::kDirectChoice: { |
| cpp_type = GetCppType(table, name); |
| cpp_type->InitDiscriminatedUnion(); |
| for (const auto* cddl_choice : type.direct_choice) { |
| CppType* member = MakeCppType(table, cddl_table, "", *cddl_choice); |
| if (!member) |
| return nullptr; |
| cpp_type->discriminated_union.members.push_back(member); |
| } |
| return cpp_type; |
| } break; |
| case CddlType::Which::kTaggedType: { |
| cpp_type = GetCppType(table, name); |
| cpp_type->which = CppType::Which::kTaggedType; |
| cpp_type->tagged_type.tag = type.tagged_type.tag_value; |
| cpp_type->tagged_type.real_type = |
| MakeCppType(table, cddl_table, "", *type.tagged_type.type); |
| } break; |
| default: |
| return nullptr; |
| } |
| |
| cpp_type->type_key = type.type_key; |
| return cpp_type; |
| } |
| |
| void PrePopulateCppTypes(CppSymbolTable* table) { |
| std::vector<std::pair<std::string, CppType::Which>> default_types; |
| default_types.emplace_back("text", CppType::Which::kString); |
| default_types.emplace_back("tstr", CppType::Which::kString); |
| default_types.emplace_back("bstr", CppType::Which::kBytes); |
| default_types.emplace_back("bytes", CppType::Which::kBytes); |
| default_types.emplace_back("uint", CppType::Which::kUint64); |
| |
| for (auto& pair : default_types) { |
| auto entry = table->cpp_type_map.find(pair.first); |
| if (entry != table->cpp_type_map.end()) |
| continue; |
| table->cpp_types.emplace_back(new CppType); |
| auto* type = table->cpp_types.back().get(); |
| type->name = pair.first; |
| type->which = pair.second; |
| table->cpp_type_map.emplace(pair.first, type); |
| } |
| } |
| |
| std::pair<bool, CppSymbolTable> BuildCppTypes( |
| const CddlSymbolTable& cddl_table) { |
| std::pair<bool, CppSymbolTable> result; |
| result.first = false; |
| PrePopulateCppTypes(&result.second); |
| auto& table = result.second; |
| for (const auto& type_entry : cddl_table.type_map) { |
| if (!MakeCppType(&table, cddl_table, type_entry.first, |
| *type_entry.second)) { |
| return result; |
| } |
| } |
| |
| result.first = true; |
| return result; |
| } |
| |
| bool VerifyUniqueKeysInMember(std::unordered_set<std::string>* keys, |
| const CppType::Struct::CppMember& member) { |
| return keys->insert(member.name).second && |
| (!member.integer_key.has_value() || |
| keys->insert(std::to_string(member.integer_key.value())).second); |
| } |
| |
| bool HasUniqueKeys(const CppType& type) { |
| std::unordered_set<std::string> keys; |
| return type.which != CppType::Which::kStruct || |
| absl::c_all_of(type.struct_type.members, |
| [&keys](const CppType::Struct::CppMember& member) { |
| return VerifyUniqueKeysInMember(&keys, member); |
| }); |
| } |
| |
| bool IsUniqueEnumValue(std::vector<uint64_t>* values, uint64_t v) { |
| auto it = std::lower_bound(values->begin(), values->end(), v); |
| if (it == values->end() || *it != v) { |
| values->insert(it, v); |
| return true; |
| } |
| return false; |
| } |
| |
| bool HasUniqueEnumValues(std::vector<uint64_t>* values, const CppType& type) { |
| return absl::c_all_of(type.enum_type.sub_members, |
| [values](CppType* sub_member) { |
| return HasUniqueEnumValues(values, *sub_member); |
| }) && |
| absl::c_all_of( |
| type.enum_type.members, |
| [values](const std::pair<std::string, uint64_t>& member) { |
| return IsUniqueEnumValue(values, member.second); |
| }); |
| } |
| |
| bool HasUniqueEnumValues(const CppType& type) { |
| std::vector<uint64_t> values; |
| return type.which != CppType::Which::kEnum || |
| HasUniqueEnumValues(&values, type); |
| } |
| |
| bool ValidateCppTypes(const CppSymbolTable& cpp_symbols) { |
| return absl::c_all_of( |
| cpp_symbols.cpp_types, [](const std::unique_ptr<CppType>& ptr) { |
| return HasUniqueKeys(*ptr) && HasUniqueEnumValues(*ptr); |
| }); |
| } |
| |
| std::string DumpTypeKey(absl::optional<uint64_t> key) { |
| if (key != absl::nullopt) { |
| return " (type key=\"" + std::to_string(key.value()) + "\")"; |
| } |
| return ""; |
| } |
| |
| void DumpType(CddlType* type, int indent_level) { |
| std::string output = ""; |
| for (int i = 0; i <= indent_level; ++i) |
| output += "--"; |
| switch (type->which) { |
| case CddlType::Which::kDirectChoice: |
| output = "kDirectChoice" + DumpTypeKey(type->type_key) + ": "; |
| Logger::Log(output); |
| for (auto& option : type->direct_choice) |
| DumpType(option, indent_level + 1); |
| break; |
| case CddlType::Which::kValue: |
| output += "kValue" + DumpTypeKey(type->type_key) + ": " + type->value; |
| Logger::Log(output); |
| break; |
| case CddlType::Which::kId: |
| output += "kId" + DumpTypeKey(type->type_key) + ": " + type->id; |
| Logger::Log(output); |
| break; |
| case CddlType::Which::kMap: |
| output += "kMap" + DumpTypeKey(type->type_key) + ": "; |
| Logger::Log(output); |
| DumpGroup(type->map, indent_level + 1); |
| break; |
| case CddlType::Which::kArray: |
| output += "kArray" + DumpTypeKey(type->type_key) + ": "; |
| Logger::Log(output); |
| DumpGroup(type->array, indent_level + 1); |
| break; |
| case CddlType::Which::kGroupChoice: |
| output += "kGroupChoice" + DumpTypeKey(type->type_key) + ": "; |
| Logger::Log(output); |
| DumpGroup(type->group_choice, indent_level + 1); |
| break; |
| case CddlType::Which::kGroupnameChoice: |
| output += "kGroupnameChoice" + DumpTypeKey(type->type_key) + ": "; |
| Logger::Log(output); |
| break; |
| case CddlType::Which::kTaggedType: |
| output += "kTaggedType" + DumpTypeKey(type->type_key) + ": " + |
| std::to_string(type->tagged_type.tag_value); |
| Logger::Log(output); |
| DumpType(type->tagged_type.type, indent_level + 1); |
| break; |
| } |
| } |
| |
| void DumpGroup(CddlGroup* group, int indent_level) { |
| for (auto& entry : group->entries) { |
| std::string output = ""; |
| for (int i = 0; i <= indent_level; ++i) |
| output += "--"; |
| switch (entry->which) { |
| case CddlGroup::Entry::Which::kUninitialized: |
| break; |
| case CddlGroup::Entry::Which::kType: |
| output += "kType:"; |
| if (entry->HasOccurrenceOperator()) { |
| output += |
| "minOccurance: " + std::to_string(entry->opt_occurrence_min) + |
| " maxOccurance: " + std::to_string(entry->opt_occurrence_max); |
| } |
| if (!entry->type.opt_key.empty()) { |
| output += " " + entry->type.opt_key + "=>"; |
| } |
| Logger::Log(output); |
| DumpType(entry->type.value, indent_level + 1); |
| break; |
| case CddlGroup::Entry::Which::kGroup: |
| if (entry->HasOccurrenceOperator()) |
| output += |
| "minOccurance: " + std::to_string(entry->opt_occurrence_min) + |
| " maxOccurance: " + std::to_string(entry->opt_occurrence_max); |
| Logger::Log(output); |
| DumpGroup(entry->group, indent_level + 1); |
| break; |
| } |
| } |
| } |
| |
| void DumpSymbolTable(CddlSymbolTable* table) { |
| for (auto& entry : table->type_map) { |
| Logger::Log(entry.first); |
| DumpType(entry.second); |
| } |
| for (auto& entry : table->group_map) { |
| Logger::Log(entry.first); |
| DumpGroup(entry.second); |
| } |
| } |