Skip to content

Commit

Permalink
Split out and/or operator handling from infix. (carbon-language#3480)
Browse files Browse the repository at this point in the history
This is also doing the parse node split, allowing lower reliance in
formatter on the tokenized buffer (something that I may be touching more
due to import handling).
  • Loading branch information
jonmeow authored Dec 9, 2023
1 parent b7d129b commit c4864aa
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 104 deletions.
103 changes: 58 additions & 45 deletions toolchain/check/handle_operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,6 @@ auto HandleInfixOperator(Context& context, Parse::NodeId parse_node) -> bool {
// Figure out the operator for the token.
auto token = context.parse_tree().node_token(parse_node);
switch (auto token_kind = context.tokens().GetKind(token)) {
case Lex::TokenKind::And:
case Lex::TokenKind::Or: {
// The first operand is wrapped in a ShortCircuitOperand, which we
// already handled by creating a RHS block and a resumption block, which
// are the current block and its enclosing block.
rhs_id = ConvertToBoolValue(context, parse_node, rhs_id);

// When the second operand is evaluated, the result of `and` and `or` is
// its value.
auto resume_block_id = context.inst_block_stack().PeekOrAdd(/*depth=*/1);
context.AddInst(
SemIR::BranchWithArg{parse_node, resume_block_id, rhs_id});
context.inst_block_stack().Pop();
context.AddCurrentCodeBlockToFunction();

// Collect the result from either the first or second operand.
context.AddInstAndPush(
parse_node,
SemIR::BlockArg{parse_node, context.insts().Get(rhs_id).type_id(),
resume_block_id});
return true;
}
case Lex::TokenKind::As: {
auto rhs_type_id = ExprAsType(context, rhs_node, rhs_id);
context.node_stack().Push(
Expand Down Expand Up @@ -181,34 +159,23 @@ auto HandlePrefixOperator(Context& context, Parse::NodeId parse_node) -> bool {
}
}

auto HandleShortCircuitOperand(Context& context, Parse::NodeId parse_node)
// Adds the branch for a short circuit operand.
static auto HandleShortCircuitOperand(Context& context,
Parse::NodeId parse_node, bool is_or)
-> bool {
// Convert the condition to `bool`.
auto cond_value_id = context.node_stack().PopExpr();
cond_value_id = ConvertToBoolValue(context, parse_node, cond_value_id);
auto bool_type_id = context.insts().Get(cond_value_id).type_id();

// Compute the branch value: the condition for `and`, inverted for `or`.
auto token = context.parse_tree().node_token(parse_node);
SemIR::InstId branch_value_id = SemIR::InstId::Invalid;
auto short_circuit_result_id = SemIR::InstId::Invalid;
switch (auto token_kind = context.tokens().GetKind(token)) {
case Lex::TokenKind::And:
branch_value_id = cond_value_id;
short_circuit_result_id = context.AddInst(SemIR::BoolLiteral{
parse_node, bool_type_id, SemIR::BoolValue::False});
break;

case Lex::TokenKind::Or:
branch_value_id = context.AddInst(
SemIR::UnaryOperatorNot{parse_node, bool_type_id, cond_value_id});
short_circuit_result_id = context.AddInst(
SemIR::BoolLiteral{parse_node, bool_type_id, SemIR::BoolValue::True});
break;

default:
CARBON_FATAL() << "Unexpected short-circuiting operator " << token_kind;
}
SemIR::InstId branch_value_id =
is_or ? context.AddInst(SemIR::UnaryOperatorNot{parse_node, bool_type_id,
cond_value_id})
: cond_value_id;
auto short_circuit_result_id = context.AddInst(SemIR::BoolLiteral{
parse_node, bool_type_id,
is_or ? SemIR::BoolValue::True : SemIR::BoolValue::False});

// Create a block for the right-hand side and for the continuation.
auto rhs_block_id =
Expand All @@ -223,9 +190,55 @@ auto HandleShortCircuitOperand(Context& context, Parse::NodeId parse_node)
context.inst_block_stack().Push(rhs_block_id);
context.AddCurrentCodeBlockToFunction();

// Put the condition back on the stack for HandleInfixOperator.
context.node_stack().Push(parse_node, cond_value_id);
// HandleShortCircuitOperator will follow, and doesn't need the operand on the
// node stack.
return true;
}

auto HandleShortCircuitOperandAnd(Context& context, Parse::NodeId parse_node)
-> bool {
return HandleShortCircuitOperand(context, parse_node, /*is_or=*/false);
}

auto HandleShortCircuitOperandOr(Context& context, Parse::NodeId parse_node)
-> bool {
return HandleShortCircuitOperand(context, parse_node, /*is_or=*/true);
}

// Short circuit operator handling is uniform because the branching logic
// occurs during operand handling.
static auto HandleShortCircuitOperator(Context& context,
Parse::NodeId parse_node) -> bool {
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithParseNode();

// The first operand is wrapped in a ShortCircuitOperand, which we
// already handled by creating a RHS block and a resumption block, which
// are the current block and its enclosing block.
rhs_id = ConvertToBoolValue(context, parse_node, rhs_id);

// When the second operand is evaluated, the result of `and` and `or` is
// its value.
auto resume_block_id = context.inst_block_stack().PeekOrAdd(/*depth=*/1);
context.AddInst(SemIR::BranchWithArg{parse_node, resume_block_id, rhs_id});
context.inst_block_stack().Pop();
context.AddCurrentCodeBlockToFunction();

// Collect the result from either the first or second operand.
context.AddInstAndPush(
parse_node,
SemIR::BlockArg{parse_node, context.insts().Get(rhs_id).type_id(),
resume_block_id});
return true;
}

auto HandleShortCircuitOperatorAnd(Context& context, Parse::NodeId parse_node)
-> bool {
return HandleShortCircuitOperator(context, parse_node);
}

auto HandleShortCircuitOperatorOr(Context& context, Parse::NodeId parse_node)
-> bool {
return HandleShortCircuitOperator(context, parse_node);
}

} // namespace Carbon::Check
5 changes: 4 additions & 1 deletion toolchain/check/node_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,10 @@ class NodeStack {
case Parse::NodeKind::ReturnType:
case Parse::NodeKind::SelfTypeNameExpr:
case Parse::NodeKind::SelfValueNameExpr:
case Parse::NodeKind::ShortCircuitOperand:
case Parse::NodeKind::ShortCircuitOperandAnd:
case Parse::NodeKind::ShortCircuitOperandOr:
case Parse::NodeKind::ShortCircuitOperatorAnd:
case Parse::NodeKind::ShortCircuitOperatorOr:
case Parse::NodeKind::StructFieldValue:
case Parse::NodeKind::StructLiteral:
case Parse::NodeKind::StructFieldType:
Expand Down
47 changes: 33 additions & 14 deletions toolchain/parse/handle_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,26 @@ auto HandleExprLoop(Context& context) -> void {
state.lhs_precedence = operator_precedence;

if (is_binary) {
if (operator_kind == Lex::TokenKind::And ||
operator_kind == Lex::TokenKind::Or) {
switch (operator_kind) {
// For `and` and `or`, wrap the first operand in a virtual parse tree
// node so that semantics can insert control flow here.
context.AddNode(NodeKind::ShortCircuitOperand, state.token,
state.subtree_start, state.has_error);
// node so that checking can insert control flow here.
case Lex::TokenKind::And:
context.AddNode(NodeKind::ShortCircuitOperandAnd, state.token,
state.subtree_start, state.has_error);
state.state = State::ExprLoopForShortCircuitOperatorAsAnd;
break;
case Lex::TokenKind::Or:
context.AddNode(NodeKind::ShortCircuitOperandOr, state.token,
state.subtree_start, state.has_error);
state.state = State::ExprLoopForShortCircuitOperatorAsOr;
break;

default:
state.state = State::ExprLoopForBinary;
break;
}

context.PushState(state, State::ExprLoopForBinary);
context.PushState(state);
context.PushStateForExpr(operator_precedence);
} else {
context.AddNode(NodeKind::PostfixOperator, state.token, state.subtree_start,
Expand All @@ -282,22 +293,30 @@ auto HandleExprLoop(Context& context) -> void {
}
}

auto HandleExprLoopForBinary(Context& context) -> void {
// Adds the operator node and returns the the main expression loop.
static auto HandleExprLoopForOperator(Context& context, NodeKind node_kind)
-> void {
auto state = context.PopState();

context.AddNode(NodeKind::InfixOperator, state.token, state.subtree_start,
state.has_error);
context.AddNode(node_kind, state.token, state.subtree_start, state.has_error);
state.has_error = false;
context.PushState(state, State::ExprLoop);
}

auto HandleExprLoopForBinary(Context& context) -> void {
HandleExprLoopForOperator(context, NodeKind::InfixOperator);
}

auto HandleExprLoopForPrefix(Context& context) -> void {
auto state = context.PopState();
HandleExprLoopForOperator(context, NodeKind::PrefixOperator);
}

context.AddNode(NodeKind::PrefixOperator, state.token, state.subtree_start,
state.has_error);
state.has_error = false;
context.PushState(state, State::ExprLoop);
auto HandleExprLoopForShortCircuitOperatorAsAnd(Context& context) -> void {
HandleExprLoopForOperator(context, NodeKind::ShortCircuitOperatorAnd);
}

auto HandleExprLoopForShortCircuitOperatorAsOr(Context& context) -> void {
HandleExprLoopForOperator(context, NodeKind::ShortCircuitOperatorOr);
}

auto HandleIfExprFinishCondition(Context& context) -> void {
Expand Down
14 changes: 7 additions & 7 deletions toolchain/parse/node_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,6 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(PrefixOperator, 1,
CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2,
CARBON_TOKEN(Amp)
CARBON_TOKEN(AmpEqual)
CARBON_TOKEN(And)
CARBON_TOKEN(As)
CARBON_TOKEN(Caret)
CARBON_TOKEN(CaretEqual)
Expand All @@ -547,7 +546,6 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2,
CARBON_TOKEN(LessLessEqual)
CARBON_TOKEN(Minus)
CARBON_TOKEN(MinusEqual)
CARBON_TOKEN(Or)
CARBON_TOKEN(Percent)
CARBON_TOKEN(PercentEqual)
CARBON_TOKEN(Pipe)
Expand All @@ -561,13 +559,15 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2,

// clang-format on

// The first operand of a short-circuiting infix operator:
// A short-circuiting infix operator:
// _external_: expression
// ShortCircuitOperand
// ShortCircuitOperand(And|Or)
// _external_: expression
// _external_: InfixOperator
CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperand, 1,
CARBON_TOKEN(And) CARBON_TOKEN(Or))
// ShortCircuitOperand(And|Or)
CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperandAnd, 1, CARBON_TOKEN(And))
CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperandOr, 1, CARBON_TOKEN(Or))
CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperatorAnd, 2, CARBON_TOKEN(And))
CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperatorOr, 2, CARBON_TOKEN(Or))

// A postfix operator:
// _external_: expression
Expand Down
21 changes: 17 additions & 4 deletions toolchain/parse/state.def
Original file line number Diff line number Diff line change
Expand Up @@ -471,15 +471,20 @@ CARBON_PARSE_STATE(ExprInPostfixLoop)

// Handles processing of an expression.
//
// expr <binary operator> ...
// ^~~~~~~~~~~~~~~~~
// expr <infix operator> ...
// ^~~~~~~~~~~~~~~~
// 1. Expr
// 2. ExprLoopForBinary
//
// expr <postfix operator>
// ^~~~~~~~~~~~~~~~~~
// 1. ExprLoop
//
// expr <short circuit operator> ...
// ^~~~~~~~~~~~~~~~~~~~~~~~
// 1. Expr
// 2. ExprLoopForShortCircuitOperator
//
// expr ...
// ^
// (state done)
Expand All @@ -488,8 +493,8 @@ CARBON_PARSE_STATE(ExprLoop)
// Completes an ExprLoop pass by adding an infix operator, then goes back
// to ExprLoop.
//
// expr <binary operator> expr ...
// ^
// expr <infix operator> expr ...
// ^
// 1. ExprLoop
CARBON_PARSE_STATE(ExprLoopForBinary)

Expand All @@ -501,6 +506,14 @@ CARBON_PARSE_STATE(ExprLoopForBinary)
// 1. ExprLoop
CARBON_PARSE_STATE(ExprLoopForPrefix)

// Completes an ExprLoop pass by adding a short circuit operator, then goes back
// to ExprLoop.
//
// expr <short circuit operator> expr ...
// ^
// 1. ExprLoop
CARBON_PARSE_STATE_VARIANTS2(ExprLoopForShortCircuitOperator, And, Or)

// Completes the condition of an `if` expression and handles the `then` token.
//
// if expr then ...
Expand Down
12 changes: 6 additions & 6 deletions toolchain/parse/testdata/if_expr/precedence.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ fn F(b: bool) -> bool {
// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 10},
// CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'IfExprIf', text: 'if', subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'IfExprThen', text: 'then', subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'or', subtree_size: 2},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandOr', text: 'or', subtree_size: 2},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'or', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorOr', text: 'or', subtree_size: 4},
// CHECK:STDOUT: {kind: 'IfExprElse', text: 'else', subtree_size: 15},
// CHECK:STDOUT: {kind: 'ReturnStatement', text: ';', subtree_size: 17},
// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 28},
Expand Down
8 changes: 4 additions & 4 deletions toolchain/parse/testdata/operators/associative.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ fn F() {
// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2},
// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 5},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 7},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 7},
// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 8},
// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ fn F() {
// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2},
// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 2},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'or', has_error: yes, subtree_size: 5},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandOr', text: 'or', has_error: yes, subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'or', has_error: yes, subtree_size: 7},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorOr', text: 'or', has_error: yes, subtree_size: 7},
// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 8},
// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ fn F() {
// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2},
// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'or', subtree_size: 2},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandOr', text: 'or', subtree_size: 2},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'or', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', has_error: yes, subtree_size: 5},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorOr', text: 'or', subtree_size: 4},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', has_error: yes, subtree_size: 5},
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', has_error: yes, subtree_size: 7},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', has_error: yes, subtree_size: 7},
// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 8},
// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
Expand Down
8 changes: 4 additions & 4 deletions toolchain/parse/testdata/operators/precedence_as.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ fn F(n: i32) {
// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'as', subtree_size: 3},
// CHECK:STDOUT: {kind: 'InfixOperator', text: '<', subtree_size: 7},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 8},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 8},
// CHECK:STDOUT: {kind: 'BoolLiteralTrue', text: 'true'},
// CHECK:STDOUT: {kind: 'BoolTypeLiteral', text: 'bool'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'as', subtree_size: 3},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 12},
// CHECK:STDOUT: {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 13},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 12},
// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 13},
// CHECK:STDOUT: {kind: 'BoolLiteralFalse', text: 'false'},
// CHECK:STDOUT: {kind: 'BoolTypeLiteral', text: 'bool'},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'as', subtree_size: 3},
// CHECK:STDOUT: {kind: 'InfixOperator', text: 'and', subtree_size: 17},
// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 17},
// CHECK:STDOUT: {kind: 'IfCondition', text: ')', subtree_size: 19},
// CHECK:STDOUT: {kind: 'CodeBlockStart', text: '{'},
// CHECK:STDOUT: {kind: 'CodeBlock', text: '}', subtree_size: 2},
Expand Down
Loading

0 comments on commit c4864aa

Please sign in to comment.