mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:30:12 +01:00
Adding some shape to toolchain semantic analysis (#1092)
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@
|
||||
# - performance-unnecessary-value-param is disabled because it duplicate
|
||||
# modernize-pass-by-value.
|
||||
Checks:
|
||||
-*, bugprone-*, -bugprone-easily-swappable-parameters,
|
||||
-*, bugprone-*, -bugprone-branch-clone, -bugprone-easily-swappable-parameters,
|
||||
-bugprone-narrowing-conversions, google-*, -google-readability-todo,
|
||||
misc-definitions-in-headers, misc-misplaced-const, misc-redundant-expression,
|
||||
misc-static-assert, misc-unconventional-assign-operator,
|
||||
|
||||
@@ -5,19 +5,45 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
cc_library(
|
||||
name = "semantics",
|
||||
srcs = ["semantics.cpp"],
|
||||
hdrs = ["semantics.h"],
|
||||
name = "function",
|
||||
hdrs = ["function.h"],
|
||||
deps = ["//toolchain/parser:parse_tree"],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "semantics_test",
|
||||
size = "small",
|
||||
srcs = ["semantics_test.cpp"],
|
||||
cc_library(
|
||||
name = "semantics_ir",
|
||||
srcs = ["semantics_ir.cpp"],
|
||||
hdrs = ["semantics_ir.h"],
|
||||
deps = [
|
||||
":semantics",
|
||||
"//toolchain/diagnostics:diagnostic_emitter",
|
||||
":function",
|
||||
"//common:check",
|
||||
"//toolchain/lexer:tokenized_buffer",
|
||||
"//toolchain/parser:parse_tree",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "semantics_ir_factory",
|
||||
srcs = ["semantics_ir_factory.cpp"],
|
||||
hdrs = ["semantics_ir_factory.h"],
|
||||
deps = [
|
||||
":semantics_ir",
|
||||
"//common:check",
|
||||
"//toolchain/lexer:tokenized_buffer",
|
||||
"//toolchain/parser:parse_node_kind",
|
||||
"//toolchain/parser:parse_tree",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "semantics_ir_factory_test",
|
||||
size = "small",
|
||||
srcs = ["semantics_ir_factory_test.cpp"],
|
||||
deps = [
|
||||
":semantics_ir_factory",
|
||||
"//toolchain/diagnostics:mocks",
|
||||
"//toolchain/lexer:tokenized_buffer",
|
||||
"//toolchain/parser:parse_tree",
|
||||
"//toolchain/source:source_buffer",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef TOOLCHAIN_SEMANTICS_FUNCTION_H_
|
||||
#define TOOLCHAIN_SEMANTICS_FUNCTION_H_
|
||||
|
||||
#include "toolchain/parser/parse_tree.h"
|
||||
|
||||
namespace Carbon::Semantics {
|
||||
|
||||
// Semantic information for a function.
|
||||
class Function {
|
||||
public:
|
||||
Function(ParseTree::Node decl_node, ParseTree::Node name_node)
|
||||
: decl_node_(decl_node), name_node_(name_node) {}
|
||||
|
||||
auto decl_node() const -> ParseTree::Node { return decl_node_; }
|
||||
auto name_node() const -> ParseTree::Node { return name_node_; }
|
||||
|
||||
private:
|
||||
// The FunctionDeclaration node.
|
||||
ParseTree::Node decl_node_;
|
||||
|
||||
// The function's DeclaredName node.
|
||||
ParseTree::Node name_node_;
|
||||
};
|
||||
|
||||
} // namespace Carbon::Semantics
|
||||
|
||||
#endif // TOOLCHAIN_SEMANTICS_FUNCTION_H_
|
||||
@@ -1,15 +0,0 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/semantics/semantics.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
Semantics Semantics::Analyze(const ParseTree& parse_tree,
|
||||
DiagnosticConsumer& consumer) {
|
||||
Semantics semantics;
|
||||
return semantics;
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
@@ -1,25 +0,0 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef TOOLCHAIN_SEMANTICS_SEMANTICS_H_
|
||||
#define TOOLCHAIN_SEMANTICS_SEMANTICS_H_
|
||||
|
||||
#include "toolchain/parser/parse_tree.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
// Provides semantic analysis on a ParseTree.
|
||||
class Semantics {
|
||||
public:
|
||||
// Analyzes a parse tree and returns the constructed semantic information.
|
||||
static auto Analyze(const ParseTree& parse_tree, DiagnosticConsumer& consumer)
|
||||
-> Semantics;
|
||||
|
||||
private:
|
||||
Semantics() = default;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // TOOLCHAIN_SEMANTICS_SEMANTICS_H_
|
||||
@@ -0,0 +1,28 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/semantics/semantics_ir.h"
|
||||
|
||||
#include "common/check.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "toolchain/lexer/tokenized_buffer.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
void SemanticsIR::Block::Add(llvm::StringRef name, Node named_entity) {
|
||||
ordering_.push_back(named_entity);
|
||||
name_lookup_.insert({name, named_entity});
|
||||
}
|
||||
|
||||
auto SemanticsIR::AddFunction(Block& block, ParseTree::Node decl_node,
|
||||
ParseTree::Node name_node)
|
||||
-> Semantics::Function& {
|
||||
int32_t index = functions_.size();
|
||||
functions_.push_back(Semantics::Function(decl_node, name_node));
|
||||
block.Add(parse_tree_->GetNodeText(name_node),
|
||||
Node(Node::Kind::Function, index));
|
||||
return functions_[index];
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
@@ -0,0 +1,78 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef TOOLCHAIN_SEMANTICS_SEMANTICS_IR_H_
|
||||
#define TOOLCHAIN_SEMANTICS_SEMANTICS_IR_H_
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "toolchain/parser/parse_tree.h"
|
||||
#include "toolchain/semantics/function.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
// Provides semantic analysis on a ParseTree.
|
||||
class SemanticsIR {
|
||||
public:
|
||||
// Provides a link back to a semantic node in a name scope.
|
||||
class Node {
|
||||
public:
|
||||
Node() : Node(Kind::Invalid, -1) {}
|
||||
|
||||
private:
|
||||
friend class SemanticsIR;
|
||||
|
||||
// The kind of token. These correspond to the lists on SemanticsIR which
|
||||
// will be indexed into.
|
||||
enum class Kind {
|
||||
Invalid,
|
||||
Function,
|
||||
};
|
||||
|
||||
Node(Kind kind, int32_t index) : kind_(kind), index_(index) {
|
||||
// TODO: kind_ and index_ are currently unused, this suppresses the
|
||||
// warning.
|
||||
kind_ = kind;
|
||||
index_ = index;
|
||||
}
|
||||
|
||||
Kind kind_;
|
||||
|
||||
// The index of the named entity within its list.
|
||||
int32_t index_;
|
||||
};
|
||||
|
||||
struct Block {
|
||||
public:
|
||||
void Add(llvm::StringRef name, Node named_entity);
|
||||
|
||||
private:
|
||||
llvm::SmallVector<Node> ordering_;
|
||||
llvm::StringMap<Node> name_lookup_;
|
||||
};
|
||||
|
||||
private:
|
||||
friend class SemanticsIRFactory;
|
||||
|
||||
explicit SemanticsIR(const ParseTree& parse_tree)
|
||||
: parse_tree_(&parse_tree) {}
|
||||
|
||||
// Creates a function, adds it to the enclosing scope, and returns a reference
|
||||
// for further mutations. On a name collision, it will not be added to the
|
||||
// scope, but will still be returned.
|
||||
auto AddFunction(Block& block, ParseTree::Node decl_node,
|
||||
ParseTree::Node name_node) -> Semantics::Function&;
|
||||
|
||||
// Indexed by Token::Function.
|
||||
llvm::SmallVector<Semantics::Function, 0> functions_;
|
||||
|
||||
// The file-level block.
|
||||
Block root_block_;
|
||||
|
||||
const ParseTree* parse_tree_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // TOOLCHAIN_SEMANTICS_SEMANTICS_IR_H_
|
||||
@@ -0,0 +1,58 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/semantics/semantics_ir_factory.h"
|
||||
|
||||
#include "common/check.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "toolchain/lexer/tokenized_buffer.h"
|
||||
#include "toolchain/parser/parse_node_kind.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
auto SemanticsIRFactory::Build(const ParseTree& parse_tree) -> SemanticsIR {
|
||||
SemanticsIRFactory builder(parse_tree);
|
||||
builder.ProcessRoots();
|
||||
return builder.semantics_;
|
||||
}
|
||||
|
||||
void SemanticsIRFactory::ProcessRoots() {
|
||||
for (ParseTree::Node node : semantics_.parse_tree_->roots()) {
|
||||
switch (semantics_.parse_tree_->node_kind(node)) {
|
||||
case ParseNodeKind::FunctionDeclaration():
|
||||
ProcessFunctionNode(semantics_.root_block_, node);
|
||||
break;
|
||||
case ParseNodeKind::FileEnd():
|
||||
// No action needed.
|
||||
break;
|
||||
default:
|
||||
FATAL() << "Unhandled node kind: "
|
||||
<< semantics_.parse_tree_->node_kind(node).name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SemanticsIRFactory::ProcessFunctionNode(SemanticsIR::Block& block,
|
||||
ParseTree::Node decl_node) {
|
||||
llvm::Optional<Semantics::Function> fn;
|
||||
for (ParseTree::Node node : semantics_.parse_tree_->children(decl_node)) {
|
||||
switch (semantics_.parse_tree_->node_kind(node)) {
|
||||
case ParseNodeKind::DeclaredName():
|
||||
fn = semantics_.AddFunction(block, decl_node, node);
|
||||
break;
|
||||
case ParseNodeKind::ParameterList():
|
||||
// TODO: Maybe something like Semantics::AddVariable passed to
|
||||
// Function::AddParameter.
|
||||
break;
|
||||
case ParseNodeKind::CodeBlock():
|
||||
// TODO: Should accumulate the definition into the code block.
|
||||
break;
|
||||
default:
|
||||
FATAL() << "Unhandled node kind: "
|
||||
<< semantics_.parse_tree_->node_kind(node).name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
@@ -0,0 +1,40 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FACTORY_H_
|
||||
#define TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FACTORY_H_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "toolchain/parser/parse_tree.h"
|
||||
#include "toolchain/semantics/semantics_ir.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
// The main semantic analysis entry.
|
||||
class SemanticsIRFactory {
|
||||
public:
|
||||
// Builds the SemanticsIR without doing any substantial semantic analysis.
|
||||
static auto Build(const ParseTree& parse_tree) -> SemanticsIR;
|
||||
|
||||
private:
|
||||
explicit SemanticsIRFactory(const ParseTree& parse_tree)
|
||||
: semantics_(parse_tree) {}
|
||||
|
||||
// Processes the roots of the ParseTree into semantics_, transitively
|
||||
// handling children.
|
||||
void ProcessRoots();
|
||||
|
||||
// Turns a function node from the parse tree into a semantic function node,
|
||||
// adding it to the containing scope.
|
||||
void ProcessFunctionNode(SemanticsIR::Block& block,
|
||||
ParseTree::Node decl_node);
|
||||
|
||||
SemanticsIR semantics_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FACTORY_H_
|
||||
@@ -0,0 +1,56 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/semantics/semantics_ir_factory.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "toolchain/diagnostics/mocks.h"
|
||||
#include "toolchain/lexer/tokenized_buffer.h"
|
||||
#include "toolchain/parser/parse_tree.h"
|
||||
#include "toolchain/source/source_buffer.h"
|
||||
|
||||
namespace Carbon::Testing {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
|
||||
class SemanticsIRFactoryTest : public ::testing::Test {
|
||||
protected:
|
||||
auto Analyze(llvm::Twine t) -> SemanticsIR {
|
||||
source_buffer.emplace(std::move(*SourceBuffer::CreateFromText(t.str())));
|
||||
tokenized_buffer = TokenizedBuffer::Lex(*source_buffer, consumer);
|
||||
EXPECT_FALSE(tokenized_buffer->has_errors());
|
||||
parse_tree = ParseTree::Parse(*tokenized_buffer, consumer);
|
||||
EXPECT_FALSE(parse_tree->has_errors());
|
||||
return SemanticsIRFactory::Build(*parse_tree);
|
||||
}
|
||||
|
||||
std::optional<SourceBuffer> source_buffer;
|
||||
std::optional<TokenizedBuffer> tokenized_buffer;
|
||||
std::optional<ParseTree> parse_tree;
|
||||
MockDiagnosticConsumer consumer;
|
||||
};
|
||||
|
||||
TEST_F(SemanticsIRFactoryTest, Empty) {
|
||||
EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0);
|
||||
Analyze("");
|
||||
}
|
||||
|
||||
TEST_F(SemanticsIRFactoryTest, FunctionBasic) {
|
||||
EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0);
|
||||
Analyze("fn Foo() {}");
|
||||
}
|
||||
|
||||
TEST_F(SemanticsIRFactoryTest, FunctionDuplicate) {
|
||||
Analyze(R"(fn Foo() {}
|
||||
fn Foo() {}
|
||||
)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace Carbon::Testing
|
||||
Reference in New Issue
Block a user