diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index 59d3d09c2002..658b89260302 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -54,6 +54,8 @@ auto ToString(Operator op) -> std::string_view { switch (op) { case Operator::Add: return "+"; + case Operator::AddressOf: + return "&"; case Operator::Neg: case Operator::Sub: return "-"; diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index 60ec449c4c99..040322a18235 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -96,6 +96,7 @@ class FieldInitializer { enum class Operator { Add, + AddressOf, And, Deref, Eq, diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 594150e04ab6..7c92cbcdf7a1 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -154,7 +154,9 @@ auto Interpreter::EvalPrim(Operator op, case Operator::Ptr: return arena_->New(args[0]); case Operator::Deref: - FATAL() << "dereference not implemented yet"; + return heap_.Read(cast(*args[0]).address(), source_loc); + case Operator::AddressOf: + return arena_->New(cast(*args[0]).address()); } } @@ -313,13 +315,26 @@ void Interpreter::StepLvalue() { return todo_.FinishAction(arena_->New(field)); } } + case ExpressionKind::PrimitiveOperatorExpression: { + const PrimitiveOperatorExpression& op = cast(exp); + if (op.op() != Operator::Deref) { + FATAL() << "Can't treat primitive operator expression as lvalue: " << exp; + } + if (act.pos() == 0) { + return todo_.Spawn( + std::make_unique(op.arguments()[0])); + } else { + const PointerValue& res = cast(*act.results()[0]); + return todo_.FinishAction(arena_->New(res.address())); + } + break; + } case ExpressionKind::TupleLiteral: case ExpressionKind::StructLiteral: case ExpressionKind::StructTypeLiteral: case ExpressionKind::IntLiteral: case ExpressionKind::BoolLiteral: case ExpressionKind::CallExpression: - case ExpressionKind::PrimitiveOperatorExpression: case ExpressionKind::IntTypeLiteral: case ExpressionKind::BoolTypeLiteral: case ExpressionKind::TypeTypeLiteral: @@ -340,6 +355,7 @@ auto Interpreter::Convert(Nonnull value, switch (value->kind()) { case Value::Kind::IntValue: case Value::Kind::FunctionValue: + case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::BoolValue: case Value::Kind::NominalClassValue: @@ -509,7 +525,11 @@ void Interpreter::StepExp() { // { {v :: op(vs,[],e,es) :: C, E, F} :: S, H} // -> { {e :: op(vs,v,[],es) :: C, E, F} :: S, H} Nonnull arg = op.arguments()[act.pos()]; - return todo_.Spawn(std::make_unique(arg)); + if (op.op() == Operator::AddressOf) { + return todo_.Spawn(std::make_unique(arg)); + } else { + return todo_.Spawn(std::make_unique(arg)); + } } else { // { {v :: op(vs,[]) :: C, E, F} :: S, H} // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H} diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index da86664b7d60..a0ce25b538a4 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -74,6 +74,7 @@ static auto IsConcreteType(Nonnull value) -> bool { switch (value->kind()) { case Value::Kind::IntValue: case Value::Kind::FunctionValue: + case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::BoolValue: case Value::Kind::StructValue: @@ -313,6 +314,7 @@ void TypeChecker::ArgumentDeduction( case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: + case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::StructValue: case Value::Kind::NominalClassValue: @@ -378,6 +380,7 @@ auto TypeChecker::Substitute( case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: + case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::StructValue: case Value::Kind::NominalClassValue: @@ -618,6 +621,14 @@ void TypeChecker::TypeCheckExp(Nonnull e) { SetStaticType(&op, arena_->New()); op.set_value_category(ValueCategory::Let); return; + case Operator::AddressOf: + if (op.arguments()[0]->value_category() != ValueCategory::Var) { + FATAL_COMPILATION_ERROR(op.arguments()[0]->source_loc()) << + "Argument to " << ToString(op.op()) << " should be an lvalue."; + } + SetStaticType(&op, arena_->New(ts[0])); + op.set_value_category(ValueCategory::Let); + return; } break; } diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 5effada4841f..765e5b791456 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -179,8 +179,11 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::FunctionValue: out << "fun<" << cast(*this).declaration().name() << ">"; break; + case Value::Kind::PointerValue: + out << "ptr<" << cast(*this).address() << ">"; + break; case Value::Kind::LValue: - out << "ptr<" << cast(*this).address() << ">"; + out << "lval<" << cast(*this).address() << ">"; break; case Value::Kind::BoolType: out << "Bool"; @@ -434,6 +437,7 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: case Value::Kind::ContinuationValue: + case Value::Kind::PointerValue: case Value::Kind::LValue: // TODO: support pointer comparisons once we have a clearer distinction // between pointers and lvalues. diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index 636cdb9fafee..d550e7ca11ff 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -36,6 +36,7 @@ class Value { enum class Kind { IntValue, FunctionValue, + PointerValue, LValue, BoolValue, StructValue, @@ -150,6 +151,22 @@ class LValue : public Value { Address value_; }; +// A pointer value +class PointerValue : public Value { + public: + explicit PointerValue(Address value) + : Value(Kind::PointerValue), value_(std::move(value)) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::PointerValue; + } + + auto address() const -> const Address& { return value_; } + + private: + Address value_; +}; + // A bool value. class BoolValue : public Value { public: diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index 7bf0d39aec6d..ae658c5e7bf2 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -38,6 +38,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception %s AFTER_OPERAND /* table-begin */ +AMPERSAND "&" AND "and" API "api" ARROW "->" @@ -134,6 +135,7 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" %} /* table-begin */ +{AMPERSAND} { return SIMPLE_TOKEN(AMPERSAND); } {AND} { return SIMPLE_TOKEN(AND); } {API} { return SIMPLE_TOKEN(API); } {ARROW} { return SIMPLE_TOKEN(ARROW); } diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index ff85994f0917..6f283531b849 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -141,6 +141,7 @@ %token // Most tokens have their spelling defined in lexer.lpp. // table-begin + AMPERSAND AND API ARROW @@ -214,7 +215,7 @@ %nonassoc EQUAL_EQUAL %left PLUS MINUS %left BINARY_STAR -%precedence NOT UNARY_MINUS PREFIX_STAR +%precedence NOT UNARY_MINUS PREFIX_STAR AMPERSAND // We need to give the `UNARY_STAR` token a precedence, rather than overriding // the precedence of the `expression UNARY_STAR` rule below, because bison // compares the precedence of the final token (for a shift) to the precedence @@ -367,6 +368,12 @@ expression: context.source_loc(), Operator::Deref, std::vector>({$2})); } +| AMPERSAND expression + { + $$ = arena->New( + context.source_loc(), Operator::AddressOf, + std::vector>({$2})); + } | expression tuple { $$ = arena->New(context.source_loc(), $1, $2); } | expression POSTFIX_STAR diff --git a/executable_semantics/testdata/pointer/basic.carbon b/executable_semantics/testdata/pointer/basic.carbon new file mode 100644 index 000000000000..b291868fb52e --- /dev/null +++ b/executable_semantics/testdata/pointer/basic.carbon @@ -0,0 +1,24 @@ +// 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: 0 + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var x: i32 = 5; + x = 10; // changes x to 10 + var p: i32* = &x; + *p = 7; // changes x to 7 + var q: i32* = &*p; + *q = 0; // changes x to 0 + var y: i32 = *p; + + return y; +} diff --git a/executable_semantics/testdata/pointer/fail_rvalue_addressof.carbon b/executable_semantics/testdata/pointer/fail_rvalue_addressof.carbon new file mode 100644 index 000000000000..5e00d4a9761c --- /dev/null +++ b/executable_semantics/testdata/pointer/fail_rvalue_addressof.carbon @@ -0,0 +1,18 @@ +// 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: %{not} %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/pointer/fail_rvalue_addressof.carbon:16: Argument to & should be an lvalue. + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var x: i32 = 5; + var p: i32* = &5; + return 0; +} diff --git a/executable_semantics/testdata/pointer/struct_pointer.carbon b/executable_semantics/testdata/pointer/struct_pointer.carbon new file mode 100644 index 000000000000..f3895e9449b3 --- /dev/null +++ b/executable_semantics/testdata/pointer/struct_pointer.carbon @@ -0,0 +1,20 @@ +// 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: 0 + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var x: auto = {.x = 10, .y = 1}; + var p: i32* = &x.x; + *p = 0; + + return x.x; +}