| // Copyright 2017-2020 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "verilog/analysis/symbol_table.h" |
| |
| #include <iostream> |
| #include <memory> |
| #include <sstream> |
| #include <stack> |
| |
| #include "absl/status/status.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "common/strings/display_utils.h" |
| #include "common/text/concrete_syntax_leaf.h" |
| #include "common/text/concrete_syntax_tree.h" |
| #include "common/text/token_info.h" |
| #include "common/text/tree_context_visitor.h" |
| #include "common/text/tree_utils.h" |
| #include "common/text/visitors.h" |
| #include "common/util/enum_flags.h" |
| #include "common/util/logging.h" |
| #include "common/util/spacer.h" |
| #include "common/util/tree_operations.h" |
| #include "common/util/value_saver.h" |
| #include "verilog/CST/class.h" |
| #include "verilog/CST/declaration.h" |
| #include "verilog/CST/functions.h" |
| #include "verilog/CST/macro.h" |
| #include "verilog/CST/module.h" |
| #include "verilog/CST/net.h" |
| #include "verilog/CST/package.h" |
| #include "verilog/CST/parameters.h" |
| #include "verilog/CST/port.h" |
| #include "verilog/CST/seq_block.h" |
| #include "verilog/CST/statement.h" |
| #include "verilog/CST/tasks.h" |
| #include "verilog/CST/type.h" |
| #include "verilog/CST/verilog_nonterminals.h" |
| #include "verilog/analysis/verilog_project.h" |
| #include "verilog/parser/verilog_parser.h" |
| #include "verilog/parser/verilog_token_enum.h" |
| |
| namespace verilog { |
| |
| using verible::AutoTruncate; |
| using verible::StringSpanOfSymbol; |
| using verible::SyntaxTreeLeaf; |
| using verible::SyntaxTreeNode; |
| using verible::TokenInfo; |
| using verible::TreeContextVisitor; |
| using verible::ValueSaver; |
| |
| // Returns string_view of `text` with outermost double-quotes removed. |
| // If `text` is not wrapped in quotes, return it as-is. |
| static absl::string_view StripOuterQuotes(absl::string_view text) { |
| return absl::StripSuffix(absl::StripPrefix(text, "\""), "\""); |
| } |
| |
| static const verible::EnumNameMap<SymbolMetaType>& SymbolMetaTypeNames() { |
| static const verible::EnumNameMap<SymbolMetaType> kSymbolMetaTypeNames({ |
| // short-hand annotation for identifier reference type |
| {"<root>", SymbolMetaType::kRoot}, |
| {"class", SymbolMetaType::kClass}, |
| {"module", SymbolMetaType::kModule}, |
| {"package", SymbolMetaType::kPackage}, |
| {"parameter", SymbolMetaType::kParameter}, |
| {"typedef", SymbolMetaType::kTypeAlias}, |
| {"data/net/var/instance", SymbolMetaType::kDataNetVariableInstance}, |
| {"function", SymbolMetaType::kFunction}, |
| {"task", SymbolMetaType::kTask}, |
| {"struct", SymbolMetaType::kStruct}, |
| {"enum", SymbolMetaType::kEnumType}, |
| {"<enum constant>", SymbolMetaType::kEnumConstant}, |
| {"interface", SymbolMetaType::kInterface}, |
| {"<unspecified>", SymbolMetaType::kUnspecified}, |
| {"<callable>", SymbolMetaType::kCallable}, |
| }); |
| return kSymbolMetaTypeNames; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, SymbolMetaType symbol_type) { |
| return SymbolMetaTypeNames().Unparse(symbol_type, stream); |
| } |
| |
| static absl::string_view SymbolMetaTypeAsString(SymbolMetaType type) { |
| return SymbolMetaTypeNames().EnumName(type); |
| } |
| |
| // Root SymbolTableNode has no key, but we identify it as "$root" |
| static constexpr absl::string_view kRoot("$root"); |
| |
| std::ostream& SymbolTableNodeFullPath(std::ostream& stream, |
| const SymbolTableNode& node) { |
| if (node.Parent() != nullptr) { |
| SymbolTableNodeFullPath(stream, *node.Parent()) << "::" << *node.Key(); |
| } else { |
| stream << kRoot; |
| } |
| return stream; |
| } |
| |
| static std::string ContextFullPath(const SymbolTableNode& context) { |
| std::ostringstream stream; |
| SymbolTableNodeFullPath(stream, context); |
| return stream.str(); |
| } |
| |
| std::ostream& ReferenceNodeFullPath(std::ostream& stream, |
| const ReferenceComponentNode& node) { |
| if (node.Parent() != nullptr) { |
| ReferenceNodeFullPath(stream, *node.Parent()); // recursive |
| } |
| return node.Value().PrintPathComponent(stream); |
| } |
| |
| static std::string ReferenceNodeFullPathString( |
| const ReferenceComponentNode& node) { |
| std::ostringstream stream; |
| ReferenceNodeFullPath(stream, node); |
| return stream.str(); |
| } |
| |
| static std::ostream& operator<<(std::ostream& stream, |
| const ReferenceComponentNode& ref_node) { |
| PrintTree( |
| ref_node, &stream, |
| [](std::ostream& s, const ReferenceComponent& ref_comp) -> std::ostream& { |
| return s << ref_comp; |
| }); |
| return stream; |
| } |
| |
| // Validates iterator/pointer stability when appending new child. |
| // Detects unwanted reallocation. |
| static ReferenceComponentNode* CheckedNewChildReferenceNode( |
| ReferenceComponentNode* parent, const ReferenceComponent& component) { |
| auto& siblings = parent->Children(); |
| if (!siblings.empty()) { |
| CHECK_LT(siblings.size(), siblings.capacity()) |
| << "\nReallocation would invalidate pointers to reference nodes at:\n" |
| << *parent << "\nWhile attempting to add child:\n" |
| << component << "\nFix: pre-allocate child nodes."; |
| } |
| // Otherwise, this first node had no prior siblings, so no need to check. |
| siblings.emplace_back(component); // copy |
| return &siblings.back(); |
| } |
| |
| static absl::Status DiagnoseMemberSymbolResolutionFailure( |
| absl::string_view name, const SymbolTableNode& context) { |
| const absl::string_view context_name = |
| context.Parent() == nullptr ? kRoot : *context.Key(); |
| return absl::NotFoundError( |
| absl::StrCat("No member symbol \"", name, "\" in parent scope (", |
| SymbolMetaTypeAsString(context.Value().metatype), ") ", |
| context_name, ".")); |
| } |
| |
| static const SymbolTableNode* LookupSymbolUpwards( |
| const SymbolTableNode& context, absl::string_view symbol); |
| |
| class SymbolTable::Builder : public TreeContextVisitor { |
| public: |
| Builder(const VerilogSourceFile& source, SymbolTable* symbol_table, |
| VerilogProject* project) |
| : source_(&source), |
| token_context_(MakeTokenContext()), |
| symbol_table_(symbol_table), |
| current_scope_(&symbol_table_->MutableRoot()) {} |
| |
| std::vector<absl::Status> TakeDiagnostics() { |
| return std::move(diagnostics_); |
| } |
| |
| private: // methods |
| void Visit(const SyntaxTreeNode& node) final { |
| const auto tag = static_cast<NodeEnum>(node.Tag().tag); |
| VLOG(1) << __FUNCTION__ << " [node]: " << tag; |
| switch (tag) { |
| case NodeEnum::kModuleDeclaration: |
| DeclareModule(node); |
| break; |
| case NodeEnum::kGenerateIfClause: |
| DeclareGenerateIf(node); |
| break; |
| case NodeEnum::kGenerateElseClause: |
| DeclareGenerateElse(node); |
| break; |
| case NodeEnum::kPackageDeclaration: |
| DeclarePackage(node); |
| break; |
| case NodeEnum::kClassDeclaration: |
| DeclareClass(node); |
| break; |
| case NodeEnum::kFunctionPrototype: // fall-through |
| case NodeEnum::kFunctionDeclaration: |
| DeclareFunction(node); |
| break; |
| case NodeEnum::kFunctionHeader: |
| SetupFunctionHeader(node); |
| break; |
| case NodeEnum::kClassConstructorPrototype: |
| DeclareConstructor(node); |
| break; |
| case NodeEnum::kTaskPrototype: // fall-through |
| case NodeEnum::kTaskDeclaration: |
| DeclareTask(node); |
| break; |
| // No special handling needed for kTaskHeader |
| case NodeEnum::kPortList: |
| DeclarePorts(node); |
| break; |
| case NodeEnum::kPortItem: // fall-through |
| // for function/task parameters |
| case NodeEnum::kPortDeclaration: // fall-through |
| case NodeEnum::kNetDeclaration: // fall-through |
| case NodeEnum::kStructUnionMember: // fall-through |
| case NodeEnum::kTypeDeclaration: // fall-through |
| case NodeEnum::kDataDeclaration: |
| DeclareData(node); |
| break; |
| case NodeEnum::kParamDeclaration: |
| DeclareParameter(node); |
| break; |
| case NodeEnum::kTypeInfo: // fall-through |
| case NodeEnum::kDataType: |
| DescendDataType(node); |
| break; |
| case NodeEnum::kReferenceCallBase: |
| DescendReferenceExpression(node); |
| break; |
| case NodeEnum::kActualParameterList: |
| DescendActualParameterList(node); |
| break; |
| case NodeEnum::kPortActualList: |
| DescendPortActualList(node); |
| break; |
| case NodeEnum::kArgumentList: |
| DescendCallArgumentList(node); |
| break; |
| case NodeEnum::kGateInstanceRegisterVariableList: { |
| // TODO: reserve() to guarantee pointer/iterator stability in VectorTree |
| Descend(node); |
| break; |
| } |
| case NodeEnum::kNetVariable: |
| DeclareNet(node); |
| break; |
| case NodeEnum::kRegisterVariable: |
| DeclareRegister(node); |
| break; |
| case NodeEnum::kGateInstance: |
| DeclareInstance(node); |
| break; |
| case NodeEnum::kVariableDeclarationAssignment: |
| DeclareVariable(node); |
| break; |
| case NodeEnum::kQualifiedId: |
| HandleQualifiedId(node); |
| break; |
| case NodeEnum::kPreprocessorInclude: |
| EnterIncludeFile(node); |
| break; |
| case NodeEnum::kExtendsList: |
| DescendExtends(node); |
| break; |
| case NodeEnum::kStructType: |
| DescendStructType(node); |
| break; |
| case NodeEnum::kEnumType: |
| DescendEnumType(node); |
| break; |
| case NodeEnum::kLPValue: |
| HandlePossibleImplicitDeclaration(node); |
| break; |
| case NodeEnum::kBindDirective: |
| // TODO(#1241) Not handled right now. |
| // TODO(#1255) Not handled right now. |
| break; |
| default: |
| Descend(node); |
| break; |
| } |
| VLOG(1) << "end of " << __FUNCTION__ << " [node]: " << tag; |
| } |
| |
| // This overload enters 'scope' for the duration of the call. |
| // New declared symbols will belong to that scope. |
| void Descend(const SyntaxTreeNode& node, SymbolTableNode* scope) { |
| const ValueSaver<SymbolTableNode*> save_scope(¤t_scope_, scope); |
| Descend(node); |
| } |
| |
| void Descend(const SyntaxTreeNode& node) { |
| TreeContextVisitor::Visit(node); // maintains syntax tree Context() stack. |
| } |
| |
| // RAII-class balance the Builder::references_builders_ stack. |
| // The work of moving collecting references into the current scope is done in |
| // the destructor. |
| class CaptureDependentReference { |
| public: |
| explicit CaptureDependentReference(Builder* builder) |
| : builder_(builder), |
| saved_branch_point_(builder_->reference_branch_point_) { |
| // Push stack space to capture references. |
| builder_->reference_builders_.emplace(/* DependentReferences */); |
| // Reset the branch point to start new named parameter/port chains |
| // from the same context. |
| builder_->reference_branch_point_ = nullptr; |
| } |
| |
| ~CaptureDependentReference() { |
| // This completes the capture of a chain of dependent references. |
| // Ref() can be empty if the subtree doesn't reference any identifiers. |
| // Empty refs are non-actionable and must be excluded. |
| DependentReferences& ref(Ref()); |
| if (!ref.Empty()) { |
| builder_->current_scope_->Value().local_references_to_bind.emplace_back( |
| std::move(ref)); |
| } |
| builder_->reference_builders_.pop(); |
| builder_->reference_branch_point_ = saved_branch_point_; // restore |
| } |
| |
| // Returns the chain of dependent references that were built. |
| DependentReferences& Ref() const { |
| return builder_->reference_builders_.top(); |
| } |
| |
| private: |
| Builder* builder_; |
| ReferenceComponentNode* saved_branch_point_; |
| }; |
| |
| void DescendReferenceExpression(const SyntaxTreeNode& reference) { |
| // capture expressions referenced from the current scope |
| const CaptureDependentReference capture(this); |
| |
| // subexpressions' references will be collected before this one |
| Descend(reference); // no scope change |
| } |
| |
| void DescendExtends(const SyntaxTreeNode& extends) { |
| VLOG(2) << __FUNCTION__ << " from: " << CurrentScopeFullPath(); |
| { |
| // At this point we are already inside the scope of the class declaration, |
| // however, the base classes should be resolved starting from the scope |
| // that *contains* this class declaration. |
| const ValueSaver<SymbolTableNode*> save(¤t_scope_, |
| current_scope_->Parent()); |
| |
| // capture the one base class type referenced by 'extends' |
| const CaptureDependentReference capture(this); |
| Descend(extends); |
| } |
| |
| // Link this new type reference as the base type of the current class being |
| // declared. |
| const DependentReferences& recent_ref = |
| current_scope_->Parent()->Value().local_references_to_bind.back(); |
| const ReferenceComponentNode* base_type_ref = |
| recent_ref.LastTypeComponent(); |
| SymbolInfo& current_declared_class_info = current_scope_->Value(); |
| current_declared_class_info.parent_type.user_defined_type = base_type_ref; |
| } |
| |
| // Traverse a subtree for a data type and collects type references |
| // originating from the current context. |
| // If the context is such that this type is used in a declaration, |
| // then capture that type information to be used later. |
| // |
| // The state/stack management here is intended to accommodate type references |
| // of arbitrary complexity. |
| // A generalized type could look like: |
| // "A#(.B(1))::C#(.D(E#(.F(0))))::G" |
| // This should produce the following reference trees: |
| // A -+- ::B |
| // | |
| // \- ::C -+- ::D |
| // | |
| // \- ::G |
| // E -+- ::F |
| // |
| void DescendDataType(const SyntaxTreeNode& data_type_node) { |
| VLOG(1) << __FUNCTION__ << ": " << StringSpanOfSymbol(data_type_node); |
| const CaptureDependentReference capture(this); |
| |
| { |
| // Inform that named parameter identifiers will yield parallel children |
| // from this reference branch point. Start this out as nullptr, and set |
| // it once an unqualified identifier is encountered that starts a |
| // reference tree. |
| const ValueSaver<ReferenceComponentNode*> set_branch( |
| &reference_branch_point_, nullptr); |
| |
| Descend(data_type_node); |
| // declaration_type_info_ will be restored after this closes. |
| } |
| |
| if (declaration_type_info_ != nullptr) { |
| // 'declaration_type_info_' holds the declared type we want to capture. |
| if (verible::GetLeftmostLeaf(data_type_node) != nullptr) { |
| declaration_type_info_->syntax_origin = &data_type_node; |
| // Otherwise, if the type subtree contains no leaves (e.g. implicit or |
| // void), then do not assign a syntax origin. |
| } |
| |
| const DependentReferences& type_ref(capture.Ref()); |
| if (!type_ref.Empty()) { |
| // then some user-defined type was referenced |
| declaration_type_info_->user_defined_type = |
| type_ref.LastTypeComponent(); |
| } |
| VLOG(2) << "declared type: " << *declaration_type_info_; |
| } |
| |
| // In all cases, a type is being referenced from the current scope, so add |
| // it to the list of references to resolve (done by 'capture'). |
| VLOG(1) << "end of " << __FUNCTION__; |
| } |
| |
| void DescendActualParameterList(const SyntaxTreeNode& node) { |
| if (reference_branch_point_ != nullptr) { |
| // Pre-allocate siblings to guarantee pointer/iterator stability. |
| // FindAll* will also catch actual port connections inside preprocessing |
| // conditionals. |
| const size_t num_params = FindAllNamedParams(node).size(); |
| // +1 to accommodate the slot needed for a nested type reference |
| // e.g. for "B" in "A#(.X(), .Y(), ...)::B" |
| reference_branch_point_->Children().reserve(num_params + 1); |
| } |
| Descend(node); |
| } |
| |
| void DescendPortActualList(const SyntaxTreeNode& node) { |
| if (reference_branch_point_ != nullptr) { |
| // Pre-allocate siblings to guarantee pointer/iterator stability. |
| // FindAll* will also catch actual port connections inside preprocessing |
| // conditionals. |
| const size_t num_ports = FindAllActualNamedPort(node).size(); |
| reference_branch_point_->Children().reserve(num_ports); |
| } |
| Descend(node); |
| } |
| |
| void DescendCallArgumentList(const SyntaxTreeNode& node) { |
| if (reference_branch_point_ != nullptr) { |
| // Pre-allocate siblings to guarantee pointer/iterator stability. |
| // FindAll* will also catch call arguments inside preprocessing |
| // conditionals. |
| const size_t num_args = FindAllNamedParams(node).size(); |
| reference_branch_point_->Children().reserve(num_args); |
| } |
| Descend(node); |
| } |
| |
| void DescendStructType(const SyntaxTreeNode& struct_type) { |
| CHECK(struct_type.MatchesTag(NodeEnum::kStructType)); |
| // Structs do not inherently have names, so they are all anonymous. |
| // Type declarations (typedefs) create named alias elsewhere. |
| const absl::string_view anon_name = |
| current_scope_->Value().CreateAnonymousScope("struct"); |
| SymbolTableNode* new_struct = DeclareScopedElementAndDescend( |
| struct_type, anon_name, SymbolMetaType::kStruct); |
| |
| // Create a self-reference to this struct type so that it can be linked |
| // for declarations that use this type. |
| const ReferenceComponent anon_type_ref{ |
| .identifier = anon_name, |
| .ref_type = ReferenceType::kImmediate, |
| .required_metatype = SymbolMetaType::kStruct, |
| // pre-resolve this symbol immediately |
| .resolved_symbol = new_struct, |
| }; |
| |
| const CaptureDependentReference capture(this); |
| capture.Ref().PushReferenceComponent(anon_type_ref); |
| |
| if (declaration_type_info_ != nullptr) { |
| declaration_type_info_->user_defined_type = capture.Ref().LastLeaf(); |
| } |
| } |
| |
| void DescendEnumType(const SyntaxTreeNode& enum_type) { |
| CHECK(enum_type.MatchesTag(NodeEnum::kEnumType)); |
| const absl::string_view anon_name = |
| current_scope_->Value().CreateAnonymousScope("enum"); |
| SymbolTableNode* new_enum = DeclareScopedElementAndDescend( |
| enum_type, anon_name, SymbolMetaType::kEnumType); |
| |
| const ReferenceComponent anon_type_ref{ |
| .identifier = anon_name, |
| .ref_type = ReferenceType::kImmediate, |
| .required_metatype = SymbolMetaType::kEnumType, |
| // pre-resolve this symbol immediately |
| .resolved_symbol = new_enum, |
| }; |
| |
| const CaptureDependentReference capture(this); |
| capture.Ref().PushReferenceComponent(anon_type_ref); |
| |
| if (declaration_type_info_ != nullptr) { |
| declaration_type_info_->user_defined_type = capture.Ref().LastLeaf(); |
| } |
| |
| // Iterate over enumeration constants |
| for (const auto& itr : *new_enum) { |
| const auto enum_constant_name = itr.first; |
| const auto& symbol = itr.second; |
| const auto& syntax_origin = |
| *ABSL_DIE_IF_NULL(symbol.Value().syntax_origin); |
| |
| const ReferenceComponent itr_ref{ |
| .identifier = enum_constant_name, |
| .ref_type = ReferenceType::kImmediate, |
| .required_metatype = SymbolMetaType::kEnumConstant, |
| // pre-resolve this symbol immediately |
| .resolved_symbol = &symbol, |
| }; |
| |
| // CaptureDependentReference class doesn't support |
| // copy constructor |
| const CaptureDependentReference cap(this); |
| cap.Ref().PushReferenceComponent(anon_type_ref); |
| cap.Ref().PushReferenceComponent(itr_ref); |
| |
| // Create default DeclarationTypeInfo |
| DeclarationTypeInfo decl_type_info; |
| const ValueSaver<DeclarationTypeInfo*> save_type(&declaration_type_info_, |
| &decl_type_info); |
| declaration_type_info_->syntax_origin = &syntax_origin; |
| declaration_type_info_->user_defined_type = cap.Ref().LastLeaf(); |
| |
| // Constants should be visible in current scope so we create |
| // variable instances with references to enum constants |
| // |
| // Consider using something different than kTypeAlias here |
| // (which is technically an alias for a type and not for a data/variable) |
| // e.g. kConstantAlias or even kGenericAlias. |
| EmplaceTypedElementInCurrentScope(syntax_origin, enum_constant_name, |
| SymbolMetaType::kTypeAlias); |
| } |
| } |
| |
| void HandlePossibleImplicitDeclaration(const SyntaxTreeNode& node) { |
| VLOG(2) << __FUNCTION__; |
| |
| // Only left-hand side of continuous assignment statements are allowed to |
| // implicitly declare nets (LRM 6.10: Implicit declarations). |
| if (Context().DirectParentsAre( |
| {NodeEnum::kNetVariableAssignment, NodeEnum::kAssignmentList, |
| NodeEnum::kContinuousAssignmentStatement})) { |
| CHECK(node.MatchesTag(NodeEnum::kLPValue)); |
| |
| DeclarationTypeInfo decl_type_info; |
| const ValueSaver<DeclarationTypeInfo*> save_type(&declaration_type_info_, |
| &decl_type_info); |
| declaration_type_info_->implicit = true; |
| Descend(node); |
| } else { |
| Descend(node); |
| } |
| } |
| |
| void HandleIdentifier(const SyntaxTreeLeaf& leaf) { |
| const absl::string_view text = leaf.get().text(); |
| VLOG(2) << __FUNCTION__ << ": " << text; |
| VLOG(2) << "current context: " << CurrentScopeFullPath(); |
| if (Context().DirectParentIs(NodeEnum::kParamType)) { |
| // This identifier declares a value parameter. |
| EmplaceTypedElementInCurrentScope(leaf, text, SymbolMetaType::kParameter); |
| return; |
| } |
| if (Context().DirectParentIs(NodeEnum::kTypeAssignment)) { |
| // This identifier declares a type parameter. |
| EmplaceElementInCurrentScope(leaf, text, SymbolMetaType::kParameter); |
| return; |
| } |
| if (Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, NodeEnum::kPortDeclaration}) || |
| Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, |
| NodeEnum::kDataTypeImplicitBasicIdDimensions, |
| NodeEnum::kPortItem})) { |
| // This identifier declares a (non-parameter) port (of a module, |
| // function, task). |
| EmplaceTypedElementInCurrentScope( |
| leaf, text, SymbolMetaType::kDataNetVariableInstance); |
| // TODO(fangism): Add attributes to distinguish public ports from |
| // private internals members. |
| return; |
| } |
| |
| if (Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, NodeEnum::kFunctionHeader})) { |
| // We deferred adding a declared function to the current scope until this |
| // point (from DeclareFunction()). |
| // Note that this excludes the out-of-line definition case, |
| // which is handled in DescendThroughOutOfLineDefinition(). |
| |
| const SyntaxTreeNode* decl_syntax = |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { |
| return node.MatchesTagAnyOf( |
| {NodeEnum::kFunctionDeclaration, NodeEnum::kFunctionPrototype}); |
| }); |
| if (decl_syntax == nullptr) return; |
| SymbolTableNode* declared_function = &EmplaceTypedElementInCurrentScope( |
| *decl_syntax, text, SymbolMetaType::kFunction); |
| // After this point, we've registered the new function with its return |
| // type, so we can switch context over to the newly declared function |
| // for its port interface and definition internals. |
| current_scope_ = declared_function; |
| return; |
| } |
| |
| if (Context().DirectParentIs(NodeEnum::kClassConstructorPrototype)) { |
| // This is a constructor or its prototype. |
| // From verilog.y, the "new" token is directly under this prototype node, |
| // and the full constructor definition also contains its prototype. |
| const SyntaxTreeNode* decl_syntax = &Context().top(); |
| SymbolTableNode* declared_function = &EmplaceTypedElementInCurrentScope( |
| *decl_syntax, text, SymbolMetaType::kFunction); |
| current_scope_ = declared_function; |
| return; |
| } |
| |
| if (Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, NodeEnum::kTaskHeader})) { |
| // We deferred adding a declared task to the current scope until this |
| // point (from DeclareFunction()). |
| // Note that this excludes the out-of-line definition case, |
| // which is handled in DescendThroughOutOfLineDefinition(). |
| |
| const SyntaxTreeNode* decl_syntax = |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { |
| return node.MatchesTagAnyOf( |
| {NodeEnum::kTaskDeclaration, NodeEnum::kTaskPrototype}); |
| }); |
| if (decl_syntax == nullptr) return; |
| SymbolTableNode* declared_task = EmplaceElementInCurrentScope( |
| *decl_syntax, text, SymbolMetaType::kTask); |
| // After this point, we've registered the new task, |
| // so we can switch context over to the newly declared function |
| // for its port interface and definition internals. |
| current_scope_ = declared_task; |
| return; |
| } |
| |
| if (Context().DirectParentsAre({NodeEnum::kDataTypeImplicitIdDimensions, |
| NodeEnum::kStructUnionMember})) { |
| // This is a struct/union member. Add it to the enclosing scope. |
| // e.g. "foo" in "struct { int foo; }" |
| EmplaceTypedElementInCurrentScope( |
| leaf, text, SymbolMetaType::kDataNetVariableInstance); |
| return; |
| } |
| if (Context().DirectParentsAre( |
| {NodeEnum::kVariableDeclarationAssignment, |
| NodeEnum::kVariableDeclarationAssignmentList, |
| NodeEnum::kStructUnionMember})) { |
| // This is part of a declaration covered by kVariableDeclarationAssignment |
| // already, so do not interpret this as a reference. |
| // e.g. "z" in "struct { int y, z; }" |
| return; |
| } |
| |
| if (Context().DirectParentsAre( |
| {NodeEnum::kEnumName, NodeEnum::kEnumNameList})) { |
| EmplaceTypedElementInCurrentScope(leaf, text, |
| SymbolMetaType::kEnumConstant); |
| return; |
| } |
| |
| // In DeclareInstance(), we already planted a self-reference that is |
| // resolved to the instance being declared. |
| if (Context().DirectParentIs(NodeEnum::kGateInstance)) return; |
| |
| if (Context().DirectParentIs(NodeEnum::kTypeDeclaration)) { |
| // This identifier declares a type alias (typedef). |
| EmplaceTypedElementInCurrentScope(leaf, text, SymbolMetaType::kTypeAlias); |
| return; |
| } |
| |
| // Capture only referencing identifiers, omit declarative identifiers. |
| // This is set up when traversing references, e.g. types, expressions. |
| // All of the code below takes effect inside a CaptureDependentReferences |
| // RAII block. |
| if (reference_builders_.empty()) return; |
| |
| // Building a reference, possible part of a chain or qualified |
| // reference. |
| DependentReferences& ref(reference_builders_.top()); |
| |
| const ReferenceComponent new_ref{ |
| .identifier = text, |
| .ref_type = InferReferenceType(), |
| .required_metatype = InferMetaType(), |
| }; |
| |
| // For instances' named ports, and types' named parameters, |
| // add references as siblings of the same parent. |
| // (Recall that instances form self-references). |
| if (Context().DirectParentIsOneOf( |
| {NodeEnum::kActualNamedPort, NodeEnum::kParamByName})) { |
| CheckedNewChildReferenceNode(ABSL_DIE_IF_NULL(reference_branch_point_), |
| new_ref); |
| return; |
| } |
| |
| // Handle possible implicit declarations here |
| if (declaration_type_info_ != nullptr && declaration_type_info_->implicit) { |
| const SymbolTableNode* resolved = |
| LookupSymbolUpwards(*ABSL_DIE_IF_NULL(current_scope_), text); |
| if (resolved == nullptr) { |
| // No explicit declaration found, declare here |
| SymbolTableNode& implicit_declaration = |
| EmplaceTypedElementInCurrentScope( |
| leaf, text, SymbolMetaType::kDataNetVariableInstance); |
| |
| const ReferenceComponent implicit_ref{ |
| .identifier = text, |
| .ref_type = InferReferenceType(), |
| .required_metatype = InferMetaType(), |
| // pre-resolve |
| .resolved_symbol = &implicit_declaration, |
| }; |
| |
| ref.PushReferenceComponent(implicit_ref); |
| return; |
| } |
| } |
| |
| // For all other cases, grow the reference chain deeper. |
| // For type references, which may contained named parameters, |
| // when encountering the first unqualified reference, establish its |
| // reference node as the point from which named parameter references |
| // get added as siblings. |
| // e.g. "A#(.B(...), .C(...))" would result in a reference tree: |
| // A -+- ::B |
| // | |
| // \- ::C |
| reference_branch_point_ = ref.PushReferenceComponent(new_ref); |
| } |
| |
| void Visit(const SyntaxTreeLeaf& leaf) final { |
| const auto tag = leaf.Tag().tag; |
| VLOG(1) << __FUNCTION__ << " [leaf]: " << VerboseToken(leaf.get()); |
| switch (tag) { |
| case verilog_tokentype::TK_new: // constructor name |
| case verilog_tokentype::SymbolIdentifier: |
| HandleIdentifier(leaf); |
| break; |
| |
| case verilog_tokentype::TK_SCOPE_RES: // "::" |
| case '.': |
| last_hierarchy_operator_ = &leaf.get(); |
| break; |
| |
| default: |
| // TODO(hzeller): use verilog::IsIdentifierLike() ? |
| // Using that would result in some mis-classifications. |
| break; |
| } |
| VLOG(1) << "end " << __FUNCTION__ << " [leaf]:" << VerboseToken(leaf.get()); |
| } |
| |
| // Distinguish between '.' and "::" hierarchy in reference components. |
| ReferenceType InferReferenceType() const { |
| CHECK(!reference_builders_.empty()) |
| << "Not currently in a reference context."; |
| const DependentReferences& ref(reference_builders_.top()); |
| if (ref.Empty() || last_hierarchy_operator_ == nullptr) { |
| // The root component is always treated as unqualified. |
| |
| // Out-of-line definitions' base/outer references must be resolved |
| // immediately. |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, |
| NodeEnum::kQualifiedId, // out-of-line |
| NodeEnum::kFunctionHeader})) { |
| return ReferenceType::kImmediate; |
| } |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, |
| NodeEnum::kQualifiedId, // out-of-line |
| NodeEnum::kTaskHeader})) { |
| return ReferenceType::kImmediate; |
| } |
| |
| return ReferenceType::kUnqualified; |
| } |
| if (Context().DirectParentIs(NodeEnum::kParamByName)) { |
| // Even though named parameters are referenced with ".PARAM", |
| // they are branched off of a base reference that already points |
| // to the type whose scope should be used, so no additional typeof() |
| // indirection is needed. |
| return ReferenceType::kDirectMember; |
| } |
| return ABSL_DIE_IF_NULL(last_hierarchy_operator_)->token_enum() == '.' |
| ? ReferenceType::kMemberOfTypeOfParent |
| : ReferenceType::kDirectMember; |
| } |
| |
| bool QualifiedIdComponentInLastPosition() const { |
| const SyntaxTreeNode* qualified_id = |
| Context().NearestParentWithTag(NodeEnum::kQualifiedId); |
| const SyntaxTreeNode* unqualified_id = |
| Context().NearestParentWithTag(NodeEnum::kUnqualifiedId); |
| return ABSL_DIE_IF_NULL(qualified_id)->children().back().get() == |
| unqualified_id; |
| } |
| |
| // Does the context necessitate that the symbol being referenced have a |
| // particular metatype? |
| SymbolMetaType InferMetaType() const { |
| const DependentReferences& ref(reference_builders_.top()); |
| // Out-of-line definitions' base/outer references must be resolved |
| // immediately to a class. |
| // Member references (inner) is a function or task, depending on header |
| // type. |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, |
| NodeEnum::kQualifiedId, // out-of-line |
| NodeEnum::kFunctionHeader})) { |
| return ref.Empty() ? SymbolMetaType::kClass : SymbolMetaType::kFunction; |
| } |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, |
| NodeEnum::kQualifiedId, // out-of-line |
| NodeEnum::kTaskHeader})) { |
| return ref.Empty() ? SymbolMetaType::kClass : SymbolMetaType::kTask; |
| } |
| |
| // TODO: import references bases must be resolved as |
| // SymbolMetaType::kPackage. |
| if (Context().DirectParentIs(NodeEnum::kActualNamedPort)) { |
| return SymbolMetaType::kDataNetVariableInstance; |
| } |
| |
| // module type or class parameters by name |
| if (Context().DirectParentsAre( |
| {NodeEnum::kParamByName, NodeEnum::kActualParameterByNameList})) { |
| return SymbolMetaType::kParameter; |
| } |
| |
| // function call arguments by name |
| if (Context().DirectParentsAre( |
| {NodeEnum::kParamByName, NodeEnum::kArgumentList})) { |
| return SymbolMetaType::kDataNetVariableInstance; |
| } |
| |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, |
| NodeEnum::kLocalRoot, |
| NodeEnum::kFunctionCall})) { |
| // bare call like "function_name(...)" |
| return SymbolMetaType::kCallable; |
| } |
| |
| if (Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, NodeEnum::kQualifiedId, |
| NodeEnum::kLocalRoot, NodeEnum::kFunctionCall})) { |
| // qualified call like "pkg_or_class::function_name(...)" |
| // Only the last component needs to be callable. |
| if (QualifiedIdComponentInLastPosition()) { |
| return SymbolMetaType::kCallable; |
| } |
| // TODO(fangism): could require parents to be kPackage or kClass |
| } |
| |
| if (Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, NodeEnum::kMethodCallExtension})) { |
| // method call like "obj.method_name(...)" |
| return SymbolMetaType::kCallable; |
| // TODO(fangism): check that method is non-static |
| } |
| |
| if (Context().DirectParentsAre( |
| {NodeEnum::kUnqualifiedId, NodeEnum::kExtendsList})) { |
| // e.g. "base" in "class derived extends base;" |
| return SymbolMetaType::kClass; |
| } |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, |
| NodeEnum::kQualifiedId, |
| NodeEnum::kExtendsList})) { |
| // base class is a qualified type like "pkg_or_class::class_name" |
| // Only the last component needs to be a type. |
| if (QualifiedIdComponentInLastPosition()) { |
| return SymbolMetaType::kClass; |
| } |
| // TODO(fangism): could require parents to be kPackage or kClass |
| } |
| |
| // Default: no specific metatype. |
| return SymbolMetaType::kUnspecified; |
| } |
| |
| // Creates a named element in the current scope. |
| // Suitable for SystemVerilog language elements: functions, tasks, packages, |
| // classes, modules, etc... |
| SymbolTableNode* EmplaceElementInCurrentScope(const verible::Symbol& element, |
| absl::string_view name, |
| SymbolMetaType metatype) { |
| const auto p = current_scope_->TryEmplace( |
| name, SymbolInfo{metatype, source_, &element}); |
| if (!p.second) { |
| DiagnoseSymbolAlreadyExists(name, p.first->first); |
| } |
| return &p.first->second; // scope of the new (or pre-existing symbol) |
| } |
| |
| // Creates a named typed element in the current scope. |
| // Suitable for SystemVerilog language elements: nets, parameter, variables, |
| // instances, functions (using their return types). |
| SymbolTableNode& EmplaceTypedElementInCurrentScope( |
| const verible::Symbol& element, absl::string_view name, |
| SymbolMetaType metatype) { |
| VLOG(1) << __FUNCTION__ << ": " << name << " in " << CurrentScopeFullPath(); |
| VLOG(1) << " type info: " << *ABSL_DIE_IF_NULL(declaration_type_info_); |
| VLOG(1) << " full text: " << AutoTruncate{StringSpanOfSymbol(element), 40}; |
| const auto p = current_scope_->TryEmplace( |
| name, SymbolInfo{ |
| metatype, source_, &element, |
| // associate this instance with its declared type |
| *ABSL_DIE_IF_NULL(declaration_type_info_), // copy |
| }); |
| if (!p.second) { |
| DiagnoseSymbolAlreadyExists(name, p.first->first); |
| } |
| VLOG(1) << "end of " << __FUNCTION__ << ": " << name; |
| return p.first->second; // scope of the new (or pre-existing symbol) |
| } |
| |
| // Creates a named element in the current scope, and traverses its subtree |
| // inside the new element's scope. |
| // Returns the new scope. |
| SymbolTableNode* DeclareScopedElementAndDescend(const SyntaxTreeNode& element, |
| absl::string_view name, |
| SymbolMetaType type) { |
| SymbolTableNode* enter_scope = |
| EmplaceElementInCurrentScope(element, name, type); |
| Descend(element, enter_scope); |
| return enter_scope; |
| } |
| |
| void DeclareModule(const SyntaxTreeNode& module) { |
| const SyntaxTreeLeaf* module_name = GetModuleName(module); |
| if (!module_name) return; |
| DeclareScopedElementAndDescend(module, module_name->get().text(), |
| SymbolMetaType::kModule); |
| } |
| |
| absl::string_view GetScopeNameFromGenerateBody(const SyntaxTreeNode& body) { |
| if (body.MatchesTag(NodeEnum::kGenerateBlock)) { |
| const SyntaxTreeNode* gen_block = GetGenerateBlockBegin(body); |
| const TokenInfo* label = |
| gen_block ? GetBeginLabelTokenInfo(*gen_block) : nullptr; |
| if (label != nullptr) { |
| // TODO: Check for a matching end-label here, and if its name matches |
| // the begin label, then immediately create a resolved reference because |
| // it only makes sense for it resolve to this begin. |
| // Otherwise, do nothing with the end label. |
| return label->text(); |
| } |
| } |
| return current_scope_->Value().CreateAnonymousScope("generate"); |
| } |
| |
| void DeclareGenerateIf(const SyntaxTreeNode& generate_if) { |
| const SyntaxTreeNode* body(GetIfClauseGenerateBody(generate_if)); |
| if (body) { |
| DeclareScopedElementAndDescend(generate_if, |
| GetScopeNameFromGenerateBody(*body), |
| SymbolMetaType::kGenerate); |
| } |
| } |
| |
| void DeclareGenerateElse(const SyntaxTreeNode& generate_else) { |
| const SyntaxTreeNode* body(GetElseClauseGenerateBody(generate_else)); |
| if (!body) return; |
| |
| if (body->MatchesTag(NodeEnum::kConditionalGenerateConstruct)) { |
| // else-if chained. Flatten the else block by not creating a new scope |
| // and let the if-clause inside create a scope directly under the current |
| // scope. |
| Descend(*body); |
| } else { |
| DeclareScopedElementAndDescend(generate_else, |
| GetScopeNameFromGenerateBody(*body), |
| SymbolMetaType::kGenerate); |
| } |
| } |
| |
| void DeclarePackage(const SyntaxTreeNode& package) { |
| const auto* token = GetPackageNameToken(package); |
| if (!token) return; |
| DeclareScopedElementAndDescend(package, token->text(), |
| SymbolMetaType::kPackage); |
| } |
| |
| void DeclareClass(const SyntaxTreeNode& class_node) { |
| const SyntaxTreeLeaf* class_name = GetClassName(class_node); |
| if (!class_name) return; |
| DeclareScopedElementAndDescend(class_node, class_name->get().text(), |
| SymbolMetaType::kClass); |
| } |
| |
| void DeclareTask(const SyntaxTreeNode& task_node) { |
| const ValueSaver<SymbolTableNode*> reserve_for_task_decl( |
| ¤t_scope_); // no scope change yet |
| Descend(task_node); |
| } |
| |
| void DeclareFunction(const SyntaxTreeNode& function_node) { |
| // Reserve a slot for the function's scope on the stack, but do not set it |
| // until we add it in HandleIdentifier(). This deferral allows us to |
| // evaluate the return type of the declared function as a reference in the |
| // current context. |
| const ValueSaver<SymbolTableNode*> reserve_for_function_decl( |
| ¤t_scope_); // no scope change yet |
| Descend(function_node); |
| } |
| |
| void DeclareConstructor(const SyntaxTreeNode& constructor_node) { |
| // Reserve a slot for the constructor's scope on the stack, but do not set |
| // it until we add it in HandleIdentifier(). The effective return type of |
| // the constructor is the class type. |
| const ValueSaver<SymbolTableNode*> reserve_for_function_decl( |
| ¤t_scope_); // no scope change yet |
| |
| const SyntaxTreeLeaf* new_keyword = |
| GetConstructorPrototypeNewKeyword(constructor_node); |
| // Create a self-reference to this class. |
| const ReferenceComponent class_type_ref{ |
| .identifier = new_keyword->get().text(), // "new" |
| .ref_type = ReferenceType::kImmediate, |
| .required_metatype = SymbolMetaType::kClass, |
| // pre-resolve this symbol to the enclosing class immediately |
| .resolved_symbol = current_scope_, // the current class |
| }; |
| |
| // Build-up a reference to the constructor, rooted at the class node. |
| const CaptureDependentReference capture(this); |
| capture.Ref().PushReferenceComponent(class_type_ref); |
| |
| DeclarationTypeInfo decl_type_info{ |
| // There is no actual source text that references the type here. |
| // We arbitrarily designate the 'new' keyword as the reference point. |
| .syntax_origin = new_keyword, |
| .user_defined_type = capture.Ref().LastLeaf(), |
| }; |
| const ValueSaver<DeclarationTypeInfo*> function_return_type( |
| &declaration_type_info_, &decl_type_info); |
| |
| Descend(constructor_node); |
| } |
| |
| void DeclarePorts(const SyntaxTreeNode& port_list) { |
| // For out-of-line function declarations, do not re-declare ports that |
| // already came from the method prototype. |
| // We designate the prototype as the source-of-truth because in Verilog, |
| // port *names* are part of the public interface (allowing calling with |
| // named parameter assignments, unlike C++ function calls). |
| // LRM 8.24: "The out-of-block method declaration shall match the prototype |
| // declaration exactly, with the following exceptions..." |
| { |
| const SyntaxTreeNode* function_header = |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { |
| return node.MatchesTag(NodeEnum::kFunctionHeader); |
| }); |
| if (function_header != nullptr) { |
| const SyntaxTreeNode& id = verible::SymbolCastToNode( |
| *ABSL_DIE_IF_NULL(GetFunctionHeaderId(*function_header))); |
| if (id.MatchesTag(NodeEnum::kQualifiedId)) { |
| // For now, ignore the out-of-line port declarations. |
| // TODO: Diagnose port type/name mismatches between prototypes' and |
| // out-of-line headers' ports. |
| return; |
| } |
| } |
| } |
| { |
| const SyntaxTreeNode* task_header = |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { |
| return node.MatchesTag(NodeEnum::kTaskHeader); |
| }); |
| if (task_header != nullptr) { |
| const SyntaxTreeNode& id = verible::SymbolCastToNode( |
| *ABSL_DIE_IF_NULL(GetTaskHeaderId(*task_header))); |
| if (id.MatchesTag(NodeEnum::kQualifiedId)) { |
| // For now, ignore the out-of-line port declarations. |
| // TODO: Diagnose port type/name mismatches between prototypes' and |
| // out-of-line headers' ports. |
| return; |
| } |
| } |
| } |
| // In all other cases, declare ports normally at the declaration site. |
| Descend(port_list); |
| } |
| |
| // Capture the declared function's return type. |
| void SetupFunctionHeader(const SyntaxTreeNode& function_header) { |
| DeclarationTypeInfo decl_type_info; |
| const ValueSaver<DeclarationTypeInfo*> function_return_type( |
| &declaration_type_info_, &decl_type_info); |
| Descend(function_header); |
| // decl_type_info will be safely copied away in HandleIdentifier(). |
| } |
| |
| // TODO: functions and tasks, which could appear as out-of-line definitions. |
| |
| void DeclareParameter(const SyntaxTreeNode& param_decl_node) { |
| CHECK(param_decl_node.MatchesTag(NodeEnum::kParamDeclaration)); |
| DeclarationTypeInfo decl_type_info; |
| // Set declaration_type_info_ to capture any user-defined type used to |
| // declare data/variables/instances. |
| const ValueSaver<DeclarationTypeInfo*> save_type(&declaration_type_info_, |
| &decl_type_info); |
| Descend(param_decl_node); |
| } |
| |
| // Declares one or more variables/instances/nets. |
| void DeclareData(const SyntaxTreeNode& data_decl_node) { |
| VLOG(1) << __FUNCTION__; |
| DeclarationTypeInfo decl_type_info; |
| // Set declaration_type_info_ to capture any user-defined type used to |
| // declare data/variables/instances. |
| const ValueSaver<DeclarationTypeInfo*> save_type(&declaration_type_info_, |
| &decl_type_info); |
| Descend(data_decl_node); |
| VLOG(1) << "end of " << __FUNCTION__; |
| } |
| |
| // Declare one (of potentially multiple) instances in a single declaration |
| // statement. |
| void DeclareInstance(const SyntaxTreeNode& instance) { |
| const verible::TokenInfo* instance_name_token = |
| GetModuleInstanceNameTokenInfoFromGateInstance(instance); |
| if (!instance_name_token) return; |
| const absl::string_view instance_name(instance_name_token->text()); |
| const SymbolTableNode& new_instance(EmplaceTypedElementInCurrentScope( |
| instance, instance_name, SymbolMetaType::kDataNetVariableInstance)); |
| |
| // Also create a DependentReferences chain starting with this named instance |
| // so that named port references are direct children of this reference root. |
| // This is a self-reference. |
| const CaptureDependentReference capture(this); |
| capture.Ref().PushReferenceComponent(ReferenceComponent{ |
| .identifier = instance_name, |
| .ref_type = ReferenceType::kUnqualified, |
| .required_metatype = SymbolMetaType::kDataNetVariableInstance, |
| // Start with its type already resolved to the node we just declared. |
| .resolved_symbol = &new_instance, |
| }); |
| |
| // Inform that named port identifiers will yield parallel children from |
| // this reference branch point. |
| const ValueSaver<ReferenceComponentNode*> set_branch( |
| &reference_branch_point_, capture.Ref().components.get()); |
| |
| // No change of scope, but named ports will be resolved with respect to the |
| // decl_type_info's scope later. |
| Descend(instance); // visit parameter/port connections, etc. |
| } |
| |
| void DeclareNet(const SyntaxTreeNode& net_variable) { |
| const SyntaxTreeLeaf* net_variable_name = |
| GetNameLeafOfNetVariable(net_variable); |
| if (!net_variable_name) return; |
| const absl::string_view net_name(net_variable_name->get().text()); |
| EmplaceTypedElementInCurrentScope(net_variable, net_name, |
| SymbolMetaType::kDataNetVariableInstance); |
| Descend(net_variable); |
| } |
| |
| void DeclareRegister(const SyntaxTreeNode& reg_variable) { |
| const SyntaxTreeLeaf* register_variable_name = |
| GetNameLeafOfRegisterVariable(reg_variable); |
| if (!register_variable_name) return; |
| const absl::string_view net_name(register_variable_name->get().text()); |
| EmplaceTypedElementInCurrentScope(reg_variable, net_name, |
| SymbolMetaType::kDataNetVariableInstance); |
| Descend(reg_variable); |
| } |
| |
| void DeclareVariable(const SyntaxTreeNode& variable) { |
| const SyntaxTreeLeaf* unqualified_id = |
| GetUnqualifiedIdFromVariableDeclarationAssignment(variable); |
| if (unqualified_id) { |
| const absl::string_view var_name(unqualified_id->get().text()); |
| EmplaceTypedElementInCurrentScope( |
| variable, var_name, SymbolMetaType::kDataNetVariableInstance); |
| } |
| Descend(variable); |
| } |
| |
| void DiagnoseSymbolAlreadyExists(absl::string_view name, |
| absl::string_view previous) { |
| std::ostringstream here_print; |
| here_print << source_->GetTextStructure()->GetRangeForText(name); |
| |
| std::ostringstream previous_print; |
| previous_print << source_->GetTextStructure()->GetRangeForText(previous); |
| |
| // TODO(hzeller): output in some structured form easy to use downstream. |
| diagnostics_.push_back(absl::AlreadyExistsError(absl::StrCat( |
| source_->ReferencedPath(), ":", here_print.str(), " Symbol \"", name, |
| "\" is already defined in the ", CurrentScopeFullPath(), " scope at ", |
| previous_print.str()))); |
| } |
| |
| absl::StatusOr<SymbolTableNode*> LookupOrInjectOutOfLineDefinition( |
| const SyntaxTreeNode& qualified_id, SymbolMetaType metatype, |
| const SyntaxTreeNode* definition_syntax) { |
| // e.g. "function int class_c::func(...); ... endfunction" |
| // Use a DependentReference object to establish a self-reference. |
| CaptureDependentReference capture(this); |
| Descend(qualified_id); |
| |
| DependentReferences& ref(capture.Ref()); |
| // Expecting only two-level reference "outer::inner". |
| CHECK_EQ(ABSL_DIE_IF_NULL(ref.components)->Children().size(), 1); |
| |
| // Must resolve base, instead of deferring to resolve phase. |
| // Do not inject the outer_scope (class name) into the current scope. |
| // Reject injections into non-classes. |
| const auto outer_scope_or_status = |
| ref.ResolveOnlyBaseLocally(current_scope_); |
| if (!outer_scope_or_status.ok()) { |
| return outer_scope_or_status.status(); |
| } |
| SymbolTableNode* outer_scope = ABSL_DIE_IF_NULL(*outer_scope_or_status); |
| |
| // Lookup inner symbol in outer_scope, but also allow injection of the |
| // inner symbol name into the outer_scope (with diagnostic). |
| ReferenceComponent& inner_ref = ref.components->Children().front().Value(); |
| const absl::string_view inner_key = inner_ref.identifier; |
| |
| const auto p = outer_scope->TryEmplace( |
| inner_key, SymbolInfo{metatype, source_, definition_syntax}); |
| SymbolTableNode* inner_symbol = &p.first->second; |
| if (p.second) { |
| // If injection succeeded, then the outer_scope did not already contain a |
| // forward declaration of the inner symbol to be defined. |
| // Diagnose this non-fatally, but continue. |
| diagnostics_.push_back( |
| DiagnoseMemberSymbolResolutionFailure(inner_key, *outer_scope)); |
| } else { |
| // Use pre-existing symbol table entry created from the prototype. |
| // Check that out-of-line and prototype symbol metatypes match. |
| const SymbolMetaType original_metatype = inner_symbol->Value().metatype; |
| if (original_metatype != metatype) { |
| return absl::AlreadyExistsError( |
| absl::StrCat(SymbolMetaTypeAsString(original_metatype), " ", |
| ContextFullPath(*inner_symbol), |
| " cannot be redefined out-of-line as a ", |
| SymbolMetaTypeAsString(metatype))); |
| } |
| } |
| // Resolve this self-reference immediately. |
| inner_ref.resolved_symbol = inner_symbol; |
| return inner_symbol; // mutable for purpose of constructing definition |
| } |
| |
| void DescendThroughOutOfLineDefinition(const SyntaxTreeNode& qualified_id, |
| SymbolMetaType type, |
| const SyntaxTreeNode* decl_syntax) { |
| const auto inner_symbol_or_status = |
| LookupOrInjectOutOfLineDefinition(qualified_id, type, decl_syntax); |
| // Change the current scope (which was set up on the stack by |
| // kFunctionDeclaration or kTaskDeclaration) for the rest of the |
| // definition. |
| if (inner_symbol_or_status.ok()) { |
| current_scope_ = *inner_symbol_or_status; |
| Descend(qualified_id); |
| } else { |
| // On failure, skip the entire definition because there is no place |
| // to add its local symbols. |
| diagnostics_.push_back(inner_symbol_or_status.status()); |
| } |
| } |
| |
| void HandleQualifiedId(const SyntaxTreeNode& qualified_id) { |
| switch (static_cast<NodeEnum>(Context().top().Tag().tag)) { |
| case NodeEnum::kFunctionHeader: { |
| const SyntaxTreeNode* decl_syntax = |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { |
| return node.MatchesTagAnyOf({NodeEnum::kFunctionDeclaration, |
| NodeEnum::kFunctionPrototype}); |
| }); |
| DescendThroughOutOfLineDefinition(qualified_id, |
| SymbolMetaType::kFunction, |
| ABSL_DIE_IF_NULL(decl_syntax)); |
| break; |
| } |
| case NodeEnum::kTaskHeader: { |
| const SyntaxTreeNode* decl_syntax = |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { |
| return node.MatchesTagAnyOf( |
| {NodeEnum::kTaskDeclaration, NodeEnum::kTaskPrototype}); |
| }); |
| DescendThroughOutOfLineDefinition(qualified_id, SymbolMetaType::kTask, |
| ABSL_DIE_IF_NULL(decl_syntax)); |
| break; |
| } |
| default: |
| // Treat this as a reference, not an out-of-line definition. |
| Descend(qualified_id); |
| break; |
| } |
| } |
| |
| void EnterIncludeFile(const SyntaxTreeNode& preprocessor_include) { |
| const SyntaxTreeLeaf* included_filename = |
| GetFileFromPreprocessorInclude(preprocessor_include); |
| if (included_filename == nullptr) return; |
| |
| const absl::string_view filename_text = included_filename->get().text(); |
| |
| // Remove the double quotes from the filename. |
| const absl::string_view filename_unquoted = StripOuterQuotes(filename_text); |
| VLOG(1) << "got: `include \"" << filename_unquoted << "\""; |
| |
| // Opening included file requires a VerilogProject. |
| // Open this file (could be first time, or previously opened). |
| VerilogProject* project = symbol_table_->project_; |
| if (project == nullptr) return; // Without project, ignore. |
| |
| const auto status_or_file = project->OpenIncludedFile(filename_unquoted); |
| if (!status_or_file.ok()) { |
| diagnostics_.push_back(status_or_file.status()); |
| // Errors can be retrieved later. |
| return; |
| } |
| |
| VerilogSourceFile* const included_file = *status_or_file; |
| if (included_file == nullptr) return; |
| VLOG(1) << "opened include file: " << included_file->ResolvedPath(); |
| |
| const auto parse_status = included_file->Parse(); |
| if (!parse_status.ok()) { |
| diagnostics_.push_back(parse_status); |
| // For now, don't bother attempting to parse a partial syntax tree. |
| // This would be best handled in the future with actual preprocessing. |
| return; |
| } |
| |
| // Depending on application, one may wish to avoid re-processing the same |
| // included file. If desired, add logic to return early here. |
| |
| { // Traverse included file's syntax tree. |
| const ValueSaver<const VerilogSourceFile*> includer(&source_, |
| included_file); |
| const ValueSaver<TokenInfo::Context> save_context_text( |
| &token_context_, MakeTokenContext()); |
| included_file->GetTextStructure()->SyntaxTree()->Accept(this); |
| } |
| } |
| |
| std::string CurrentScopeFullPath() const { |
| return ContextFullPath(*current_scope_); |
| } |
| |
| verible::TokenWithContext VerboseToken(const TokenInfo& token) const { |
| return verible::TokenWithContext{token, token_context_}; |
| } |
| |
| TokenInfo::Context MakeTokenContext() const { |
| return TokenInfo::Context( |
| source_->GetTextStructure()->Contents(), |
| [](std::ostream& stream, int e) { stream << verilog_symbol_name(e); }); |
| } |
| |
| private: // data |
| // Points to the source file that is the origin of symbols. |
| // This changes when opening preprocess-included files. |
| // TODO(fangism): maintain a vector/stack of these for richer diagnostics |
| const VerilogSourceFile* source_; |
| |
| // For human-readable debugging. |
| // This should be constructed using MakeTokenContext(), after setting |
| // 'source_'. |
| TokenInfo::Context token_context_; |
| |
| // The symbol table to build, never nullptr. |
| SymbolTable* const symbol_table_; |
| |
| // The remaining fields are mutable state: |
| |
| // This is the current scope where encountered definitions register their |
| // symbols, never nullptr. |
| // There is no need to maintain a stack because SymbolTableNodes already link |
| // to their parents. |
| SymbolTableNode* current_scope_; |
| |
| // Stack of references. |
| // A stack is needed to support nested type references like "A#(B(#(C)))", |
| // and nested expressions like "f(g(h))" |
| std::stack<DependentReferences> reference_builders_; |
| |
| // When creating branched references, like with instances' named ports, |
| // set this to the nearest branch point. |
| // This will signal to the reference builder that parallel children |
| // are to be added, as opposed to deeper descendants. |
| ReferenceComponentNode* reference_branch_point_ = nullptr; |
| |
| // For a data/instance/variable declaration statement, this is the declared |
| // type (could be primitive or named-user-defined). |
| // For functions, this is the return type. |
| // For constructors, this is the class type. |
| // Set this type before traversing declared instances and variables to capture |
| // the type of the declaration. Unset this to prevent type capture. |
| // Such declarations cannot nest, so a stack is not needed. |
| DeclarationTypeInfo* declaration_type_info_ = nullptr; |
| |
| // Update to either "::" or '.'. |
| const TokenInfo* last_hierarchy_operator_ = nullptr; |
| |
| // Collection of findings that might be considered compiler/tool errors in a |
| // real toolchain. For example: attempt to redefine symbol. |
| std::vector<absl::Status> diagnostics_; |
| }; |
| |
| void ReferenceComponent::VerifySymbolTableRoot( |
| const SymbolTableNode* root) const { |
| if (resolved_symbol != nullptr) { |
| CHECK_EQ(resolved_symbol->Root(), root) |
| << "Resolved symbols must point to a node in the same SymbolTable."; |
| } |
| } |
| |
| absl::Status ReferenceComponent::MatchesMetatype( |
| SymbolMetaType found_metatype) const { |
| switch (required_metatype) { |
| case SymbolMetaType::kUnspecified: |
| return absl::OkStatus(); |
| case SymbolMetaType::kCallable: |
| if (found_metatype == SymbolMetaType::kFunction || |
| found_metatype == SymbolMetaType::kTask) { |
| return absl::OkStatus(); |
| } |
| break; |
| case SymbolMetaType::kClass: |
| if (found_metatype == SymbolMetaType::kClass || |
| found_metatype == SymbolMetaType::kTypeAlias) { |
| // Where a class is expected, a typedef could be accepted. |
| return absl::OkStatus(); |
| } |
| break; |
| default: |
| if (required_metatype == found_metatype) return absl::OkStatus(); |
| break; |
| } |
| // Otherwise, mismatched metatype. |
| return absl::InvalidArgumentError( |
| absl::StrCat("Expecting reference \"", identifier, "\" to resolve to a ", |
| SymbolMetaTypeAsString(required_metatype), ", but found a ", |
| SymbolMetaTypeAsString(found_metatype), ".")); |
| } |
| |
| absl::Status ReferenceComponent::ResolveSymbol( |
| const SymbolTableNode& resolved) { |
| // Verify metatype match. |
| if (auto metatype_match_status = MatchesMetatype(resolved.Value().metatype); |
| !metatype_match_status.ok()) { |
| VLOG(2) << metatype_match_status.message(); |
| return metatype_match_status; |
| } |
| |
| VLOG(2) << " resolved: " << ContextFullPath(resolved); |
| resolved_symbol = &resolved; |
| return absl::OkStatus(); |
| } |
| |
| const ReferenceComponentNode* DependentReferences::LastLeaf() const { |
| if (components == nullptr) return nullptr; |
| const ReferenceComponentNode* node = components.get(); |
| while (!is_leaf(*node)) node = &node->Children().front(); |
| return node; |
| } |
| |
| // RefType can be ReferenceComponentNode or const ReferenceComponentNode. |
| template <typename RefType> |
| static RefType* ReferenceLastTypeComponent(RefType* node) { |
| // This references a type that may be nested and have name parameters. |
| // From A#(.B())::C#(.D()), we want C as the desired type component. |
| while (!is_leaf(*node)) { |
| // There should be at most one non-parameter at each branch point, |
| // so stop at the first one found. |
| // In the above example, this would find "C" among {"B", "C"} inside "A". |
| auto& branches(node->Children()); |
| const auto found = std::find_if( |
| branches.begin(), branches.end(), [](const ReferenceComponentNode& n) { |
| return n.Value().required_metatype != SymbolMetaType::kParameter; |
| }); |
| // If there are only parameters referenced, then this code is the last |
| // component we want. |
| if (found == branches.end()) return node; |
| // Otherwise, continue searching along the non-parameter branch. |
| node = &*found; |
| } |
| return node; |
| } |
| |
| const ReferenceComponentNode* DependentReferences::LastTypeComponent() const { |
| // This references a type that may be nested and have name parameters. |
| // From A#(.B())::C#(.D()), we want C as the desired type component. |
| if (components == nullptr) return nullptr; |
| const ReferenceComponentNode* node = components.get(); |
| return ReferenceLastTypeComponent(node); |
| } |
| |
| ReferenceComponentNode* DependentReferences::LastTypeComponent() { |
| if (components == nullptr) return nullptr; |
| ReferenceComponentNode* node = components.get(); |
| return ReferenceLastTypeComponent(node); |
| } |
| |
| ReferenceComponentNode* DependentReferences::PushReferenceComponent( |
| const ReferenceComponent& component) { |
| VLOG(3) << __FUNCTION__ << ", id: " << component.identifier; |
| ReferenceComponentNode* new_child; |
| if (Empty()) { |
| components = std::make_unique<ReferenceComponentNode>(component); // copy |
| new_child = components.get(); |
| } else { |
| // Find the last node from which references can be grown. |
| // Exclude type named parameters. |
| ReferenceComponentNode* node = LastTypeComponent(); |
| new_child = CheckedNewChildReferenceNode(node, component); |
| } |
| VLOG(3) << "end of " << __FUNCTION__ << ":\n" << *this; |
| return new_child; |
| } |
| |
| void DependentReferences::VerifySymbolTableRoot( |
| const SymbolTableNode* root) const { |
| if (components != nullptr) { |
| ApplyPreOrder(*components, [=](const ReferenceComponent& component) { |
| component.VerifySymbolTableRoot(root); |
| }); |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const DependentReferences& dep_refs) { |
| if (dep_refs.components == nullptr) return stream << "(empty-ref)"; |
| return stream << *dep_refs.components; |
| } |
| |
| // Follow type aliases through canonical type. |
| static const SymbolTableNode* CanonicalizeTypeForMemberLookup( |
| const SymbolTableNode& context) { |
| VLOG(2) << __FUNCTION__; |
| const SymbolTableNode* current_context = &context; |
| do { |
| VLOG(2) << " -> " << ContextFullPath(*current_context); |
| if (current_context->Value().metatype != SymbolMetaType::kTypeAlias) break; |
| const ReferenceComponentNode* ref_type = |
| current_context->Value().declared_type.user_defined_type; |
| if (ref_type == nullptr) { |
| // Could be a primitive type. |
| return nullptr; |
| } |
| current_context = ref_type->Value().resolved_symbol; |
| // TODO: We haven't guaranteed that typedefs have been resolved in order, |
| // so these will need to be resolved on-demand in the future. |
| } while (current_context != nullptr); |
| // TODO: the return value currently does not distinguish between a failed |
| // resolution of a dependent symbol and a primitive referenced type; |
| // both yield a nullptr. |
| return current_context; |
| } |
| |
| // Search through base class's scopes for a symbol. |
| static const SymbolTableNode* LookupSymbolThroughInheritedScopes( |
| const SymbolTableNode& context, absl::string_view symbol) { |
| const SymbolTableNode* current_context = &context; |
| do { |
| // Look directly in current scope. |
| const auto found = current_context->Find(symbol); |
| if (found != current_context->end()) { |
| return &found->second; |
| } |
| // TODO: lookup imported namespaces and symbols |
| |
| // Point to next inherited scope. |
| const auto* base_type = |
| current_context->Value().parent_type.user_defined_type; |
| if (base_type == nullptr) break; |
| |
| const SymbolTableNode* resolved_base = base_type->Value().resolved_symbol; |
| // TODO: attempt to resolve on-demand because resolve ordering is not |
| // guaranteed. |
| if (resolved_base == nullptr) return nullptr; |
| |
| // base type could be a typedef, so canonicalize |
| current_context = CanonicalizeTypeForMemberLookup(*resolved_base); |
| } while (current_context != nullptr); |
| return nullptr; // resolution failed |
| } |
| |
| // Search up-scope, stopping at the first symbol found in the nearest scope. |
| static const SymbolTableNode* LookupSymbolUpwards( |
| const SymbolTableNode& context, absl::string_view symbol) { |
| const SymbolTableNode* current_context = &context; |
| do { |
| const SymbolTableNode* found = |
| LookupSymbolThroughInheritedScopes(*current_context, symbol); |
| if (found != nullptr) return found; |
| |
| // Point to next enclosing scope. |
| current_context = current_context->Parent(); |
| } while (current_context != nullptr); |
| return nullptr; // resolution failed |
| } |
| |
| static absl::Status DiagnoseUnqualifiedSymbolResolutionFailure( |
| absl::string_view name, const SymbolTableNode& context) { |
| return absl::NotFoundError(absl::StrCat("Unable to resolve symbol \"", name, |
| "\" from context ", |
| ContextFullPath(context), ".")); |
| } |
| |
| static void ResolveReferenceComponentNodeLocal(ReferenceComponentNode* node, |
| const SymbolTableNode& context) { |
| ReferenceComponent& component(node->Value()); |
| VLOG(2) << __FUNCTION__ << ": " << component; |
| // If already resolved, skip. |
| if (component.resolved_symbol != nullptr) return; // already bound |
| const absl::string_view key(component.identifier); |
| CHECK(node->Parent() == nullptr); // is root |
| // root node: lookup this symbol from its context upward |
| CHECK_EQ(component.ref_type, ReferenceType::kUnqualified); |
| |
| // Only try to resolve using the same scope in which the reference appeared, |
| // local, without upward search. |
| const auto found = context.Find(key); |
| if (found != context.end()) { |
| component.resolved_symbol = &found->second; |
| } |
| } |
| |
| static void ResolveUnqualifiedName(ReferenceComponent* component, |
| const SymbolTableNode& context, |
| std::vector<absl::Status>* diagnostics) { |
| VLOG(2) << __FUNCTION__ << ": " << component; |
| const absl::string_view key(component->identifier); |
| // Find the first symbol whose name matches, without regard to its metatype. |
| const SymbolTableNode* resolved = LookupSymbolUpwards(context, key); |
| if (resolved == nullptr) { |
| diagnostics->emplace_back( |
| DiagnoseUnqualifiedSymbolResolutionFailure(key, context)); |
| return; |
| } |
| |
| const auto resolve_status = component->ResolveSymbol(*resolved); |
| if (!resolve_status.ok()) { |
| diagnostics->push_back(resolve_status); |
| } |
| VLOG(2) << "end of " << __FUNCTION__; |
| } |
| |
| // Search this scope directly for a symbol, without any upward/inheritance |
| // lookups. |
| static void ResolveImmediateMember(ReferenceComponent* component, |
| const SymbolTableNode& context, |
| std::vector<absl::Status>* diagnostics) { |
| VLOG(2) << __FUNCTION__ << ": " << component; |
| const absl::string_view key(component->identifier); |
| const auto found = context.Find(key); |
| if (found == context.end()) { |
| diagnostics->emplace_back( |
| DiagnoseMemberSymbolResolutionFailure(key, context)); |
| return; |
| } |
| |
| const SymbolTableNode& found_symbol = found->second; |
| const auto resolve_status = component->ResolveSymbol(found_symbol); |
| if (!resolve_status.ok()) { |
| diagnostics->push_back(resolve_status); |
| } |
| VLOG(2) << "end of " << __FUNCTION__; |
| } |
| |
| static void ResolveDirectMember(ReferenceComponent* component, |
| const SymbolTableNode& context, |
| std::vector<absl::Status>* diagnostics) { |
| VLOG(2) << __FUNCTION__ << ": " << component; |
| |
| // Canonicalize context if it an alias. |
| const SymbolTableNode* canonical_context = |
| CanonicalizeTypeForMemberLookup(context); |
| if (canonical_context == nullptr) { |
| // TODO: diagnostic could be improved by following each typedef indirection. |
| diagnostics->push_back(absl::InvalidArgumentError( |
| absl::StrCat("Canonical type of ", ContextFullPath(context), |
| " does not have any members."))); |
| return; |
| } |
| |
| const absl::string_view key(component->identifier); |
| const auto* found = |
| LookupSymbolThroughInheritedScopes(*canonical_context, key); |
| if (found == nullptr) { |
| diagnostics->emplace_back( |
| DiagnoseMemberSymbolResolutionFailure(key, *canonical_context)); |
| return; |
| } |
| |
| const SymbolTableNode& found_symbol = *found; |
| const auto resolve_status = component->ResolveSymbol(found_symbol); |
| if (!resolve_status.ok()) { |
| diagnostics->push_back(resolve_status); |
| } |
| VLOG(2) << "end of " << __FUNCTION__; |
| } |
| |
| // This is the primary function that resolves references. |
| // Dependent (parent) nodes must already be resolved before attempting to |
| // resolve children references (guaranteed by calling this in a pre-order |
| // traversal). |
| static void ResolveReferenceComponentNode( |
| ReferenceComponentNode* node, const SymbolTableNode& context, |
| std::vector<absl::Status>* diagnostics) { |
| ReferenceComponent& component(node->Value()); |
| VLOG(2) << __FUNCTION__ << ": " << component; |
| if (component.resolved_symbol != nullptr) return; // already bound |
| |
| switch (component.ref_type) { |
| case ReferenceType::kUnqualified: { |
| // root node: lookup this symbol from its context upward |
| CHECK(node->Parent() == nullptr); |
| ResolveUnqualifiedName(&component, context, diagnostics); |
| break; |
| } |
| case ReferenceType::kImmediate: { |
| ResolveImmediateMember(&component, context, diagnostics); |
| break; |
| } |
| case ReferenceType::kDirectMember: { |
| // Use parent's scope (if resolved successfully) to resolve this node. |
| const ReferenceComponent& parent_component(node->Parent()->Value()); |
| |
| const SymbolTableNode* parent_scope = parent_component.resolved_symbol; |
| if (parent_scope == nullptr) return; // leave this subtree unresolved |
| |
| ResolveDirectMember(&component, *parent_scope, diagnostics); |
| break; |
| } |
| case ReferenceType::kMemberOfTypeOfParent: { |
| // Use parent's type's scope (if resolved successfully) to resolve this |
| // node. Get the type of the object from the parent component. |
| const ReferenceComponent& parent_component(node->Parent()->Value()); |
| const SymbolTableNode* parent_scope = parent_component.resolved_symbol; |
| if (parent_scope == nullptr) return; // leave this subtree unresolved |
| |
| const DeclarationTypeInfo& type_info = |
| parent_scope->Value().declared_type; |
| // Primitive types do not have members. |
| if (type_info.user_defined_type == nullptr) { |
| diagnostics->push_back(absl::InvalidArgumentError( |
| absl::StrCat("Type of parent reference ", |
| ReferenceNodeFullPathString(*node->Parent()), " (", |
| verible::StringSpanOfSymbol(*type_info.syntax_origin), |
| ") does not have any members."))); |
| return; |
| } |
| |
| // This referenced object's scope is not a parent of this node, and |
| // thus, not guaranteed to have been resolved first. |
| // TODO(fangism): resolve on-demand |
| const SymbolTableNode* type_scope = |
| type_info.user_defined_type->Value().resolved_symbol; |
| if (type_scope == nullptr) return; |
| |
| ResolveDirectMember(&component, *type_scope, diagnostics); |
| break; |
| } |
| } |
| VLOG(2) << "end of " << __FUNCTION__; |
| } |
| |
| ReferenceComponentMap ReferenceComponentNodeMapView( |
| const ReferenceComponentNode& node) { |
| ReferenceComponentMap map_view; |
| for (const auto& child : node.Children()) { |
| map_view.emplace(std::make_pair(child.Value().identifier, &child)); |
| } |
| return map_view; |
| } |
| |
| void DependentReferences::Resolve( |
| const SymbolTableNode& context, |
| std::vector<absl::Status>* diagnostics) const { |
| VLOG(1) << __FUNCTION__; |
| if (components == nullptr) return; |
| // References are arranged in dependency trees. |
| // Parent node references must be resolved before children nodes, |
| // hence a pre-order traversal. |
| ApplyPreOrder(*components, |
| [&context, diagnostics](ReferenceComponentNode& node) { |
| ResolveReferenceComponentNode(&node, context, diagnostics); |
| // TODO: minor optimization, when resolution for a node fails, |
| // skip checking that node's subtree; early terminate. |
| }); |
| VLOG(1) << "end of " << __FUNCTION__; |
| } |
| |
| void DependentReferences::ResolveLocally(const SymbolTableNode& context) const { |
| if (components == nullptr) return; |
| // Only attempt to resolve the reference root, and none of its subtrees. |
| ResolveReferenceComponentNodeLocal(components.get(), context); |
| } |
| |
| absl::StatusOr<SymbolTableNode*> DependentReferences::ResolveOnlyBaseLocally( |
| SymbolTableNode* context) { |
| // Similar lookup to ResolveReferenceComponentNodeLocal() but allows |
| // mutability of 'context' for injecting out-of-line definitions. |
| |
| ReferenceComponent& base(ABSL_DIE_IF_NULL(components)->Value()); |
| CHECK(base.ref_type == ReferenceType::kUnqualified || |
| base.ref_type == ReferenceType::kImmediate) |
| << "Inconsistent reference type: " << base.ref_type; |
| const absl::string_view key(base.identifier); |
| const auto found = context->Find(key); |
| if (found == context->end()) { |
| return DiagnoseMemberSymbolResolutionFailure(key, *context); |
| } |
| SymbolTableNode& resolved = found->second; |
| |
| // If metatype doesn't match what is expected, then fail. |
| const auto resolve_status = base.ResolveSymbol(resolved); |
| if (!resolve_status.ok()) { |
| return resolve_status; |
| } |
| return &resolved; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, ReferenceType ref_type) { |
| static const verible::EnumNameMap<ReferenceType> kReferenceTypeNames({ |
| // short-hand annotation for identifier reference type |
| {"@", ReferenceType::kUnqualified}, |
| {"!", ReferenceType::kImmediate}, |
| {"::", ReferenceType::kDirectMember}, |
| {".", ReferenceType::kMemberOfTypeOfParent}, |
| }); |
| return kReferenceTypeNames.Unparse(ref_type, stream); |
| } |
| |
| std::ostream& ReferenceComponent::PrintPathComponent( |
| std::ostream& stream) const { |
| stream << ref_type << identifier; |
| if (required_metatype != SymbolMetaType::kUnspecified) { |
| stream << '[' << required_metatype << ']'; |
| } |
| return stream; |
| } |
| |
| std::ostream& ReferenceComponent::PrintVerbose(std::ostream& stream) const { |
| PrintPathComponent(stream) << " -> "; |
| if (resolved_symbol == nullptr) { |
| return stream << "<unresolved>"; |
| } |
| return stream << ContextFullPath(*resolved_symbol); |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const ReferenceComponent& component) { |
| return component.PrintVerbose(stream); |
| } |
| |
| void DeclarationTypeInfo::VerifySymbolTableRoot( |
| const SymbolTableNode* root) const { |
| if (user_defined_type != nullptr) { |
| ApplyPreOrder(*user_defined_type, [=](const ReferenceComponent& component) { |
| component.VerifySymbolTableRoot(root); |
| }); |
| } |
| } |
| |
| absl::string_view SymbolInfo::CreateAnonymousScope(absl::string_view base) { |
| const size_t n = anonymous_scope_names.size(); |
| anonymous_scope_names.emplace_back(std::make_unique<const std::string>( |
| // Starting with a non-alpha character guarantees it cannot collide with |
| // any user-given identifier. |
| absl::StrCat("%", "anon-", base, "-", n))); |
| return *anonymous_scope_names.back(); |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const DeclarationTypeInfo& decl_type_info) { |
| stream << "type-info { "; |
| |
| stream << "source: "; |
| if (decl_type_info.syntax_origin != nullptr) { |
| stream << "\"" |
| << AutoTruncate{.text = StringSpanOfSymbol( |
| *decl_type_info.syntax_origin), |
| .max_chars = 25} |
| << "\""; |
| } else { |
| stream << "(unknown)"; |
| } |
| |
| stream << ", type ref: "; |
| if (decl_type_info.user_defined_type != nullptr) { |
| stream << *decl_type_info.user_defined_type; |
| } else { |
| stream << "(primitive)"; |
| } |
| |
| if (decl_type_info.implicit) { |
| stream << ", implicit"; |
| } |
| |
| return stream << " }"; |
| } |
| |
| void SymbolInfo::VerifySymbolTableRoot(const SymbolTableNode* root) const { |
| declared_type.VerifySymbolTableRoot(root); |
| for (const auto& local_ref : local_references_to_bind) { |
| local_ref.VerifySymbolTableRoot(root); |
| } |
| } |
| |
| void SymbolInfo::Resolve(const SymbolTableNode& context, |
| std::vector<absl::Status>* diagnostics) { |
| for (auto& local_ref : local_references_to_bind) { |
| local_ref.Resolve(context, diagnostics); |
| } |
| } |
| |
| void SymbolInfo::ResolveLocally(const SymbolTableNode& context) { |
| for (auto& local_ref : local_references_to_bind) { |
| local_ref.ResolveLocally(context); |
| } |
| } |
| |
| std::ostream& SymbolInfo::PrintDefinition(std::ostream& stream, |
| size_t indent) const { |
| // print everything except local_references_to_bind |
| const verible::Spacer wrap(indent); |
| stream << wrap << "metatype: " << metatype << std::endl; |
| if (file_origin != nullptr) { |
| stream << wrap << "file: " << file_origin->ResolvedPath() << std::endl; |
| } |
| // declared_type only makes sense for elements with potentially user-defined |
| // types, and not for language element declarations like modules and classes. |
| if (metatype == SymbolMetaType::kDataNetVariableInstance) { |
| stream << wrap << declared_type << std::endl; |
| } |
| return stream; |
| } |
| |
| std::ostream& SymbolInfo::PrintReferences(std::ostream& stream, |
| size_t indent) const { |
| // only print local_references_to_bind |
| // TODO: support indentation |
| std::string newline_wrap(indent + 1, ' '); |
| newline_wrap.front() = '\n'; |
| stream << "refs:"; |
| // When there's at most 1 reference, print more compactly. |
| if (local_references_to_bind.size() > 1) |
| stream << newline_wrap; |
| else |
| stream << ' '; |
| stream << absl::StrJoin(local_references_to_bind, newline_wrap, |
| absl::StreamFormatter()); |
| if (local_references_to_bind.size() > 1) stream << newline_wrap; |
| return stream; |
| } |
| |
| SymbolInfo::references_map_view_type |
| SymbolInfo::LocalReferencesMapViewForTesting() const { |
| references_map_view_type map_view; |
| for (const auto& local_ref : local_references_to_bind) { |
| CHECK(!local_ref.Empty()) << "Never add empty DependentReferences."; |
| map_view[local_ref.components->Value().identifier].emplace(&local_ref); |
| } |
| return map_view; |
| } |
| |
| void SymbolTable::CheckIntegrity() const { |
| const SymbolTableNode* root = &symbol_table_root_; |
| symbol_table_root_.ApplyPreOrder( |
| [=](const SymbolInfo& s) { s.VerifySymbolTableRoot(root); }); |
| } |
| |
| void SymbolTable::Resolve(std::vector<absl::Status>* diagnostics) { |
| symbol_table_root_.ApplyPreOrder( |
| [=](SymbolTableNode& node) { node.Value().Resolve(node, diagnostics); }); |
| } |
| |
| void SymbolTable::ResolveLocallyOnly() { |
| symbol_table_root_.ApplyPreOrder( |
| [=](SymbolTableNode& node) { node.Value().ResolveLocally(node); }); |
| } |
| |
| std::ostream& SymbolTable::PrintSymbolDefinitions(std::ostream& stream) const { |
| return symbol_table_root_.PrintTree( |
| stream, |
| [](std::ostream& s, const SymbolInfo& sym, |
| size_t indent) -> std::ostream& { |
| return sym.PrintDefinition(s << std::endl, indent + 4 /* wrap */) |
| << verible::Spacer(indent); |
| }); |
| } |
| |
| std::ostream& SymbolTable::PrintSymbolReferences(std::ostream& stream) const { |
| return symbol_table_root_.PrintTree(stream, |
| [](std::ostream& s, const SymbolInfo& sym, |
| size_t indent) -> std::ostream& { |
| return sym.PrintReferences( |
| s, indent + 4 /* wrap */); |
| }); |
| } |
| |
| static void ParseFileAndBuildSymbolTable( |
| VerilogSourceFile* source, SymbolTable* symbol_table, |
| VerilogProject* project, std::vector<absl::Status>* diagnostics) { |
| const auto parse_status = source->Parse(); |
| if (!parse_status.ok()) diagnostics->push_back(parse_status); |
| // Continue, in case syntax-error recovery left a partial syntax tree. |
| |
| // Amend symbol table by analyzing this translation unit. |
| const std::vector<absl::Status> statuses = |
| BuildSymbolTable(*source, symbol_table, project); |
| // Forward diagnostics. |
| diagnostics->insert(diagnostics->end(), statuses.begin(), statuses.end()); |
| } |
| |
| void SymbolTable::Build(std::vector<absl::Status>* diagnostics) { |
| for (auto& translation_unit : *project_) { |
| ParseFileAndBuildSymbolTable(translation_unit.second.get(), this, project_, |
| diagnostics); |
| } |
| } |
| |
| void SymbolTable::BuildSingleTranslationUnit( |
| absl::string_view referenced_file_name, |
| std::vector<absl::Status>* diagnostics) { |
| const auto translation_unit_or_status = |
| project_->OpenTranslationUnit(referenced_file_name); |
| if (!translation_unit_or_status.ok()) { |
| diagnostics->push_back(translation_unit_or_status.status()); |
| return; |
| } |
| VerilogSourceFile* translation_unit = *translation_unit_or_status; |
| |
| ParseFileAndBuildSymbolTable(translation_unit, this, project_, diagnostics); |
| } |
| |
| std::vector<absl::Status> BuildSymbolTable(const VerilogSourceFile& source, |
| SymbolTable* symbol_table, |
| VerilogProject* project) { |
| VLOG(1) << __FUNCTION__; |
| const auto* text_structure = source.GetTextStructure(); |
| if (text_structure == nullptr) return std::vector<absl::Status>(); |
| const auto& syntax_tree = text_structure->SyntaxTree(); |
| if (syntax_tree == nullptr) return std::vector<absl::Status>(); |
| |
| SymbolTable::Builder builder(source, symbol_table, project); |
| syntax_tree->Accept(&builder); |
| return builder.TakeDiagnostics(); // move |
| } |
| |
| } // namespace verilog |