From 2325c50b3b639f05a73ca8dfc3c4436f609ff8a6 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 3 Mar 2022 16:34:34 -0800 Subject: [PATCH] Support for `if ... then ... else` following #911. (#944) --- executable_semantics/ast/ast_rtti.txt | 1 + executable_semantics/ast/expression.cpp | 7 +++++ executable_semantics/ast/expression.h | 29 +++++++++++++++++++ .../interpreter/interpreter.cpp | 16 ++++++++++ .../interpreter/resolve_names.cpp | 7 +++++ .../interpreter/type_checker.cpp | 16 ++++++++++ executable_semantics/syntax/lexer.lpp | 2 ++ executable_semantics/syntax/parser.ypp | 17 +++++++++-- .../if_expression/if_then_else.carbon | 21 ++++++++++++++ 9 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 executable_semantics/testdata/if_expression/if_then_else.carbon diff --git a/executable_semantics/ast/ast_rtti.txt b/executable_semantics/ast/ast_rtti.txt index 71538e4c9401..a15c2964feae 100644 --- a/executable_semantics/ast/ast_rtti.txt +++ b/executable_semantics/ast/ast_rtti.txt @@ -52,4 +52,5 @@ abstract class Expression : AstNode; class TypeTypeLiteral : Expression; class IdentifierExpression : Expression; class IntrinsicExpression : Expression; + class IfExpression : Expression; class UnimplementedExpression : Expression; diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index 658b89260302..7595efb8b2b5 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -187,6 +187,13 @@ void Expression::Print(llvm::raw_ostream& out) const { } out << ")"; break; + case ExpressionKind::IfExpression: { + const auto& if_expr = cast(*this); + out << "if " << *if_expr.condition() << " then " + << *if_expr.then_expression() << " else " + << *if_expr.else_expression(); + break; + } case ExpressionKind::UnimplementedExpression: { const auto& unimplemented = cast(*this); out << "UnimplementedExpression<" << unimplemented.label() << ">("; diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index 8e3046060b32..b9cf7c65fc81 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -490,6 +490,35 @@ class IntrinsicExpression : public Expression { Nonnull args_; }; +class IfExpression : public Expression { + public: + explicit IfExpression(SourceLocation source_loc, + Nonnull condition, + Nonnull then_expression, + Nonnull else_expression) + : Expression(AstNodeKind::IfExpression, source_loc), + condition_(condition), + then_expression_(then_expression), + else_expression_(else_expression) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromIfExpression(node->kind()); + } + + auto condition() const -> Nonnull { return condition_; } + auto then_expression() const -> Nonnull { + return then_expression_; + } + auto else_expression() const -> Nonnull { + return else_expression_; + } + + private: + Nonnull condition_; + Nonnull then_expression_; + Nonnull else_expression_; +}; + // An expression whose semantics have not been implemented. This can be used // as a placeholder during development, in order to implement and test parsing // of a new expression syntax without having to implement its semantics. diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 468ce9cb579a..09aee46b31db 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -346,6 +346,7 @@ void Interpreter::StepLvalue() { case ExpressionKind::StringLiteral: case ExpressionKind::StringTypeLiteral: case ExpressionKind::IntrinsicExpression: + case ExpressionKind::IfExpression: FATAL() << "Can't treat expression as lvalue: " << exp; case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << exp; @@ -690,6 +691,21 @@ void Interpreter::StepExp() { CHECK(act.pos() == 0); return todo_.FinishAction(arena_->New()); } + case ExpressionKind::IfExpression: { + const auto& if_expr = cast(exp); + if (act.pos() == 0) { + return todo_.Spawn( + std::make_unique(if_expr.condition())); + } else if (act.pos() == 1) { + const BoolValue& condition = cast(*act.results()[0]); + return todo_.Spawn(std::make_unique( + condition.value() ? if_expr.then_expression() + : if_expr.else_expression())); + } else { + return todo_.FinishAction(act.results()[1]); + } + break; + } case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << exp; } // switch (exp->kind) diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index 5ed859ad6394..af854544943d 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -132,6 +132,13 @@ static void ResolveNames(Expression& expression, ResolveNames(cast(expression).args(), enclosing_scope); break; + case ExpressionKind::IfExpression: { + auto& if_expr = cast(expression); + ResolveNames(*if_expr.condition(), enclosing_scope); + ResolveNames(*if_expr.then_expression(), enclosing_scope); + ResolveNames(*if_expr.else_expression(), enclosing_scope); + break; + } case ExpressionKind::BoolTypeLiteral: case ExpressionKind::BoolLiteral: case ExpressionKind::IntTypeLiteral: diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index db04f872f709..9a3681f7f282 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -800,6 +800,22 @@ void TypeChecker::TypeCheckExp(Nonnull e, e->set_value_category(ValueCategory::Let); e->set_static_type(arena_->New()); return; + case ExpressionKind::IfExpression: { + auto& if_expr = cast(*e); + TypeCheckExp(if_expr.condition(), impl_scope); + ExpectType(if_expr.source_loc(), "condition of `if`", + arena_->New(), &if_expr.condition()->static_type()); + + // TODO: Compute the common type and convert both operands to it. + TypeCheckExp(if_expr.then_expression(), impl_scope); + TypeCheckExp(if_expr.else_expression(), impl_scope); + ExpectExactType(e->source_loc(), "expression of `if` expression", + &if_expr.then_expression()->static_type(), + &if_expr.else_expression()->static_type()); + e->set_static_type(&if_expr.then_expression()->static_type()); + e->set_value_category(ValueCategory::Let); + return; + } case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << *e; } diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index 668d93449006..e282e77fe76e 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -88,6 +88,7 @@ RUN "__run" SEMICOLON ";" SLASH "/" STRING "String" +THEN "then" TRUE "true" TYPE "Type" UNDERSCORE "_" @@ -185,6 +186,7 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" {SEMICOLON} { return SIMPLE_TOKEN(SEMICOLON); } {SLASH} { return SIMPLE_TOKEN(SLASH); } {STRING} { return SIMPLE_TOKEN(STRING); } +{THEN} { return SIMPLE_TOKEN(THEN); } {TRUE} { return SIMPLE_TOKEN(TRUE); } {TYPE} { return SIMPLE_TOKEN(TYPE); } {UNDERSCORE} { return SIMPLE_TOKEN(UNDERSCORE); } diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index 095cdef81fc9..9cd5b9fd2f35 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -134,6 +134,8 @@ %type > and_expression %type > or_lhs %type > or_expression +%type > statement_expression +%type > if_expression %type > expression %type > generic_binding %type >> deduced_params @@ -216,6 +218,7 @@ SEMICOLON SLASH STRING + THEN TRUE TYPE UNDERSCORE @@ -477,12 +480,20 @@ or_expression: std::vector>({$1, $3})); } ; -expression: +statement_expression: ref_deref_expression | predicate_expression | and_expression | or_expression ; +if_expression: + statement_expression +| IF expression THEN if_expression ELSE if_expression + { $$ = arena->New(context.source_loc(), $2, $4, $6); } +; +expression: + if_expression +; designator: PERIOD identifier { $$ = $2; } ; paren_expression: paren_expression_base @@ -645,11 +656,11 @@ clause_list: } ; statement: - expression EQUAL expression SEMICOLON + statement_expression EQUAL expression SEMICOLON { $$ = arena->New(context.source_loc(), $1, $3); } | VAR pattern EQUAL expression SEMICOLON { $$ = arena->New(context.source_loc(), $2, $4); } -| expression SEMICOLON +| statement_expression SEMICOLON { $$ = arena->New(context.source_loc(), $1); } | if_statement { $$ = $1; } diff --git a/executable_semantics/testdata/if_expression/if_then_else.carbon b/executable_semantics/testdata/if_expression/if_then_else.carbon new file mode 100644 index 000000000000..043b3954d41c --- /dev/null +++ b/executable_semantics/testdata/if_expression/if_then_else.carbon @@ -0,0 +1,21 @@ +// 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 +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 12 + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var cond: if true then Bool else i32 = true; + if (if cond then true else false) {} + while (if cond then false else true) {} + return if if cond then true or false else false and true + then if not cond then 1 + 2 else 3 * 4 + else if not cond then 5 + 6 else 7 * 8; +}