mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 07:21:04 +01:00
Rewrite semantics towards a more pure instruction model (#2320)
This rewrites semantics towards a more pure instruction model, in pursuit of the simple instruction-style output. I think I can get this approach to type-check as it goes along, but obviously this change doesn't prove that yet. I'm separating it out because it's a large rewrite of the semantics structure, tossing out a lot of what was there before. But I think it does help towards several requests, like setting up a clear path for consolidating duplicate identifiers and making the node style more standardized. I expect to need to pass multiple args to function calls, that'd probably be storing vectors of args similar to how I'm showing identifiers and integer literals stored. This removes the semantics namespace because (a) it was getting annoying writing the `::` everywhere, and (b) I think the leaning with Carbon is to avoid namespaces (@chandlerc asked not to put SemanticsIR/SemanticsFactory in a namespace, which is the crux of the issue). But, it's still necessary to avoid name conflicts so I just prefix everything with "Semantics" (still a lot of typing, but no `::`).
This commit is contained in:
@@ -6,13 +6,10 @@
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "common/check.h"
|
||||
#include "toolchain/lexer/token_kind.h"
|
||||
#include "toolchain/lexer/tokenized_buffer.h"
|
||||
#include "toolchain/parser/parse_node_kind.h"
|
||||
#include "toolchain/semantics/node_ref.h"
|
||||
#include "toolchain/semantics/nodes/binary_operator.h"
|
||||
#include "toolchain/semantics/nodes/integer_literal.h"
|
||||
#include "toolchain/semantics/semantics_node.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
@@ -23,107 +20,36 @@ auto SemanticsIRFactory::Build(const TokenizedBuffer& tokens,
|
||||
return builder.semantics_;
|
||||
}
|
||||
|
||||
// An entry in the stack for traversing the ParseTree.
|
||||
// TODO: This is badly structured, and a redesign may be able to get rid of
|
||||
// the need for `sem_ir`. Need to keep thinking about this, but for now
|
||||
// this setup allows for test consistency.
|
||||
// Alternately, maybe think about if we can group semantics for multiple nodes
|
||||
// so that we aren't constantly copying/reallocating vectors of NodeRefs.
|
||||
struct TraversalStackEntry {
|
||||
explicit TraversalStackEntry(ParseTree::Node parse_node)
|
||||
: parse_node(parse_node) {}
|
||||
|
||||
TraversalStackEntry(ParseTree::Node parse_node,
|
||||
llvm::SmallVector<Semantics::NodeRef, 0> sem_ir)
|
||||
: parse_node(parse_node), sem_ir(std::move(sem_ir)) {}
|
||||
|
||||
TraversalStackEntry(ParseTree::Node parse_node,
|
||||
llvm::SmallVector<Semantics::NodeRef, 0> sem_ir,
|
||||
Semantics::NodeId result_id)
|
||||
: parse_node(parse_node),
|
||||
sem_ir(std::move(sem_ir)),
|
||||
result_id(result_id) {}
|
||||
|
||||
ParseTree::Node parse_node;
|
||||
llvm::SmallVector<Semantics::NodeRef> sem_ir;
|
||||
llvm::Optional<Semantics::NodeId> result_id;
|
||||
};
|
||||
|
||||
// Converts a TokenKind to a BinaryOperator operator.
|
||||
static auto GetBinaryOp(TokenKind kind) -> Semantics::BinaryOperator::Op {
|
||||
switch (kind) {
|
||||
case TokenKind::Plus():
|
||||
return Semantics::BinaryOperator::Op::Add;
|
||||
default:
|
||||
CARBON_FATAL() << "Unrecognized token kind: " << kind.Name();
|
||||
}
|
||||
}
|
||||
|
||||
void SemanticsIRFactory::Build() {
|
||||
llvm::SmallVector<TraversalStackEntry> node_stack;
|
||||
auto range = parse_tree().postorder();
|
||||
for (auto it = range.begin();; ++it) {
|
||||
auto parse_node = *it;
|
||||
switch (auto parse_kind = parse_tree().node_kind(parse_node)) {
|
||||
case ParseNodeKind::DeclaredName(): {
|
||||
// DeclaredNames will be acted upon as part of the declaring construct.
|
||||
node_stack.push_back(TraversalStackEntry(parse_node));
|
||||
auto text = parse_tree().GetNodeText(parse_node);
|
||||
auto identifier_id = semantics_.AddIdentifier(text);
|
||||
Push(parse_node, SemanticsNode::MakeIdentifier(identifier_id));
|
||||
break;
|
||||
}
|
||||
case ParseNodeKind::FunctionDefinition(): {
|
||||
// Merges code block children up under the FunctionDefinitionStart.
|
||||
llvm::SmallVector<Semantics::NodeRef> body;
|
||||
while (parse_tree().node_kind(node_stack.back().parse_node) !=
|
||||
while (parse_tree().node_kind(node_stack_.back().parse_node) !=
|
||||
ParseNodeKind::FunctionDefinitionStart()) {
|
||||
body.insert(body.begin(), node_stack.back().sem_ir.begin(),
|
||||
node_stack.back().sem_ir.end());
|
||||
node_stack.pop_back();
|
||||
node_stack_.pop_back();
|
||||
}
|
||||
|
||||
// Next is the FunctionDefinitionStart.
|
||||
llvm::SmallVector<Semantics::NodeRef> sig =
|
||||
std::move(node_stack.back().sem_ir);
|
||||
node_stack.pop_back();
|
||||
|
||||
// TODO: This replacement is in particular why I want to change
|
||||
// the IR setup now, but for now I want to just produce output that
|
||||
// satisfies tests without changes.
|
||||
auto orig_function = semantics_.nodes_.Get<Semantics::Function>(sig[0]);
|
||||
auto orig_set_name = semantics_.nodes_.Get<Semantics::SetName>(sig[1]);
|
||||
llvm::SmallVector<Semantics::NodeRef> function_sem_ir;
|
||||
auto function_id = next_id();
|
||||
function_sem_ir.push_back(semantics_.nodes_.Store(
|
||||
Semantics::Function(orig_function.node(), function_id, body)));
|
||||
function_sem_ir.push_back(semantics_.nodes_.Store(Semantics::SetName(
|
||||
orig_set_name.node(), orig_set_name.name(), function_id)));
|
||||
|
||||
node_stack.push_back(
|
||||
TraversalStackEntry(parse_node, std::move(function_sem_ir)));
|
||||
Pop(ParseNodeKind::FunctionDefinitionStart());
|
||||
semantics_.AddNode(SemanticsNode::MakeFunctionDefinitionEnd());
|
||||
Push(parse_node);
|
||||
break;
|
||||
}
|
||||
case ParseNodeKind::FunctionDefinitionStart(): {
|
||||
// TODO: Skip over the parameter list for now.
|
||||
node_stack.pop_back();
|
||||
|
||||
// TODO: At this point, it should be possible to forward-declare the
|
||||
// function so that it can be called from its code block. For now, we
|
||||
// just assemble the semantic function to associate the body.
|
||||
llvm::SmallVector<Semantics::NodeRef> sem_ir;
|
||||
|
||||
auto function_id = next_id();
|
||||
sem_ir.push_back(semantics_.nodes_.Store(
|
||||
Semantics::Function(parse_node, function_id, {})));
|
||||
|
||||
auto name_node = node_stack.back().parse_node;
|
||||
sem_ir.push_back(semantics_.nodes_.Store(Semantics::SetName(
|
||||
name_node, parse_tree().GetNodeText(name_node), function_id)));
|
||||
node_stack.pop_back();
|
||||
|
||||
// Do nothing with the `fn`.
|
||||
node_stack.pop_back();
|
||||
|
||||
node_stack.push_back(
|
||||
TraversalStackEntry(parse_node, std::move(sem_ir)));
|
||||
Pop(ParseNodeKind::ParameterList());
|
||||
auto name_node_id = PopWithResult(ParseNodeKind::DeclaredName());
|
||||
Pop(ParseNodeKind::FunctionIntroducer());
|
||||
auto decl_id = semantics_.AddNode(
|
||||
SemanticsNode::MakeFunctionDeclaration(name_node_id));
|
||||
semantics_.AddNode(SemanticsNode::MakeFunctionDefinitionStart(decl_id));
|
||||
Push(parse_node);
|
||||
break;
|
||||
}
|
||||
case ParseNodeKind::FileEnd(): {
|
||||
@@ -131,77 +57,48 @@ void SemanticsIRFactory::Build() {
|
||||
CARBON_CHECK(it == range.end())
|
||||
<< "FileEnd should always be last, found "
|
||||
<< parse_tree().node_kind(*it);
|
||||
|
||||
for (const auto& entry : node_stack) {
|
||||
semantics_.root_block_.append(entry.sem_ir.begin(),
|
||||
entry.sem_ir.end());
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ParseNodeKind::InfixOperator(): {
|
||||
llvm::SmallVector<Semantics::NodeRef> sem_ir;
|
||||
|
||||
sem_ir.insert(sem_ir.begin(), node_stack.back().sem_ir.begin(),
|
||||
node_stack.back().sem_ir.end());
|
||||
auto rhs_id = *node_stack.back().result_id;
|
||||
node_stack.pop_back();
|
||||
|
||||
sem_ir.insert(sem_ir.begin(), node_stack.back().sem_ir.begin(),
|
||||
node_stack.back().sem_ir.end());
|
||||
auto lhs_id = *node_stack.back().result_id;
|
||||
node_stack.pop_back();
|
||||
auto rhs_id = PopWithResult();
|
||||
auto lhs_id = PopWithResult();
|
||||
|
||||
// Figure out the operator for the token.
|
||||
auto token = parse_tree().node_token(parse_node);
|
||||
auto token_kind = tokens_->GetKind(token);
|
||||
auto op = GetBinaryOp(token_kind);
|
||||
|
||||
auto literal_id = next_id();
|
||||
sem_ir.push_back(semantics_.nodes_.Store(Semantics::BinaryOperator(
|
||||
parse_node, literal_id, op, lhs_id, rhs_id)));
|
||||
node_stack.push_back(
|
||||
TraversalStackEntry(parse_node, std::move(sem_ir), literal_id));
|
||||
switch (auto token_kind = tokens_->GetKind(token)) {
|
||||
case TokenKind::Plus():
|
||||
Push(parse_node,
|
||||
SemanticsNode::MakeBinaryOperatorAdd(lhs_id, rhs_id));
|
||||
break;
|
||||
default:
|
||||
CARBON_FATAL() << "Unrecognized token kind: " << token_kind.Name();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ParseNodeKind::Literal(): {
|
||||
auto literal_id = next_id();
|
||||
|
||||
llvm::SmallVector<Semantics::NodeRef> sem_ir;
|
||||
auto token = parse_tree().node_token(parse_node);
|
||||
switch (auto token_kind = tokens_->GetKind(token)) {
|
||||
case TokenKind::IntegerLiteral(): {
|
||||
sem_ir.push_back(semantics_.nodes_.Store(Semantics::IntegerLiteral(
|
||||
parse_node, literal_id, tokens_->GetIntegerLiteral(token))));
|
||||
auto id =
|
||||
semantics_.AddIntegerLiteral(tokens_->GetIntegerLiteral(token));
|
||||
Push(parse_node, SemanticsNode::MakeIntegerLiteral(id));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CARBON_FATAL() << "Unhandled kind: " << token_kind.Name();
|
||||
}
|
||||
node_stack.push_back(
|
||||
TraversalStackEntry(parse_node, std::move(sem_ir), literal_id));
|
||||
break;
|
||||
}
|
||||
case ParseNodeKind::ReturnStatement(): {
|
||||
CARBON_CHECK(parse_tree().node_kind(node_stack.back().parse_node) ==
|
||||
ParseNodeKind::StatementEnd());
|
||||
node_stack.pop_back();
|
||||
Pop(ParseNodeKind::StatementEnd());
|
||||
|
||||
// TODO: Restructure ReturnStatement so that we can do this without
|
||||
// looking at the subtree size.
|
||||
if (parse_tree().node_subtree_size(parse_node) == 2) {
|
||||
node_stack.push_back(TraversalStackEntry(
|
||||
parse_node, {semantics_.nodes_.Store(
|
||||
Semantics::Return(parse_node, llvm::None))}));
|
||||
Push(parse_node, SemanticsNode::MakeReturn());
|
||||
} else {
|
||||
// Return should only ever have one expression child.
|
||||
llvm::SmallVector<Semantics::NodeRef> sem_ir =
|
||||
std::move(node_stack.back().sem_ir);
|
||||
Semantics::NodeId result_id = *node_stack.back().result_id;
|
||||
node_stack.pop_back();
|
||||
sem_ir.push_back(semantics_.nodes_.Store(
|
||||
Semantics::Return(parse_node, result_id)));
|
||||
node_stack.push_back(
|
||||
TraversalStackEntry(parse_node, std::move(sem_ir)));
|
||||
auto arg = PopWithResult();
|
||||
Push(parse_node, SemanticsNode::MakeReturnExpression(arg));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -210,17 +107,15 @@ void SemanticsIRFactory::Build() {
|
||||
// it's unused and only stored so that node counts match.
|
||||
// TODO: Reorder with ParameterListStart so that we can traverse without
|
||||
// subtree_size.
|
||||
CARBON_CHECK(parse_tree().node_kind(node_stack.back().parse_node) ==
|
||||
ParseNodeKind::ParameterListEnd());
|
||||
node_stack.pop_back();
|
||||
node_stack.push_back(TraversalStackEntry(parse_node));
|
||||
Pop(ParseNodeKind::ParameterListEnd());
|
||||
Push(parse_node);
|
||||
break;
|
||||
}
|
||||
case ParseNodeKind::FunctionIntroducer():
|
||||
case ParseNodeKind::ParameterListEnd():
|
||||
case ParseNodeKind::StatementEnd(): {
|
||||
// The token has no action, but we still track it for the stack.
|
||||
node_stack.push_back(TraversalStackEntry(parse_node));
|
||||
Push(parse_node);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
Reference in New Issue
Block a user