From 250ce4ab000c273fb8abcdf704402071472719c2 Mon Sep 17 00:00:00 2001 From: Jon Meow <46229924+jonmeow@users.noreply.github.com> Date: Wed, 11 Aug 2021 13:14:05 -0700 Subject: [PATCH] Add string parsing and a print builtin (#721) It was in my mind to add String in order to support libraries in `package`. `print` is added in order to have a String go to stdout. I've tried to do `print` in a way that won't be too hard to add other printable types, but it's probably also somewhat optional here -- that is, if desired, I could remove it. But it was a lot easier to doublecheck `\n` behavior with it, and I suspect it'll be helpful in other tests if it supports more value types. On the side, this also fixes dereferencing in Pattern/Expression Print() calls, which I was noticing printing pointers instead of values. This may be another argument for moving away from passing pointers, since this seems to be a difficult-to-catch error. Co-authored-by: Geoff Romer --- common/BUILD | 20 +++++ common/string_helpers.cpp | 88 +++++++++++++++++++ common/string_helpers.h | 25 ++++++ common/string_helpers_test.cpp | 54 ++++++++++++ executable_semantics/ast/expression.cpp | 17 +++- executable_semantics/ast/expression.h | 47 ++++++++++ executable_semantics/ast/pattern.cpp | 8 +- .../interpreter/interpreter.cpp | 64 ++++++++++---- .../interpreter/typecheck.cpp | 32 +++++-- executable_semantics/interpreter/value.cpp | 16 ++++ executable_semantics/interpreter/value.h | 32 ++++++- executable_semantics/syntax/BUILD | 1 + executable_semantics/syntax/lexer.lpp | 21 +++++ .../syntax/parse_and_lex_context.cpp | 8 +- executable_semantics/syntax/parser.ypp | 6 ++ .../syntax/syntax_helpers.cpp | 25 +++++- executable_semantics/syntax/syntax_helpers.h | 2 +- executable_semantics/test_list.bzl | 9 ++ executable_semantics/testdata/print1.carbon | 9 ++ executable_semantics/testdata/print1.golden | 2 + executable_semantics/testdata/string1.carbon | 14 +++ executable_semantics/testdata/string1.golden | 1 + executable_semantics/testdata/string2.carbon | 14 +++ executable_semantics/testdata/string2.golden | 1 + executable_semantics/testdata/string3.carbon | 14 +++ executable_semantics/testdata/string3.golden | 1 + .../testdata/string_fail1.carbon | 8 ++ .../testdata/string_fail1.golden | 2 + .../testdata/string_fail2.carbon | 8 ++ .../testdata/string_fail2.golden | 2 + .../testdata/string_fail3.carbon | 8 ++ .../testdata/string_fail3.golden | 2 + .../testdata/string_fail4.carbon | 8 ++ .../testdata/string_fail4.golden | 2 + .../testdata/string_fail5.carbon | 9 ++ .../testdata/string_fail5.golden | 2 + 36 files changed, 549 insertions(+), 33 deletions(-) create mode 100644 common/string_helpers.cpp create mode 100644 common/string_helpers.h create mode 100644 common/string_helpers_test.cpp create mode 100644 executable_semantics/testdata/print1.carbon create mode 100644 executable_semantics/testdata/print1.golden create mode 100644 executable_semantics/testdata/string1.carbon create mode 100644 executable_semantics/testdata/string1.golden create mode 100644 executable_semantics/testdata/string2.carbon create mode 100644 executable_semantics/testdata/string2.golden create mode 100644 executable_semantics/testdata/string3.carbon create mode 100644 executable_semantics/testdata/string3.golden create mode 100644 executable_semantics/testdata/string_fail1.carbon create mode 100644 executable_semantics/testdata/string_fail1.golden create mode 100644 executable_semantics/testdata/string_fail2.carbon create mode 100644 executable_semantics/testdata/string_fail2.golden create mode 100644 executable_semantics/testdata/string_fail3.carbon create mode 100644 executable_semantics/testdata/string_fail3.golden create mode 100644 executable_semantics/testdata/string_fail4.carbon create mode 100644 executable_semantics/testdata/string_fail4.golden create mode 100644 executable_semantics/testdata/string_fail5.carbon create mode 100644 executable_semantics/testdata/string_fail5.golden diff --git a/common/BUILD b/common/BUILD index 0b29a90724df..309c541fed0e 100644 --- a/common/BUILD +++ b/common/BUILD @@ -44,3 +44,23 @@ cc_library( "@llvm-project//llvm:Support", ], ) + +cc_library( + name = "string_helpers", + srcs = ["string_helpers.cpp"], + hdrs = ["string_helpers.h"], + deps = [ + ":check", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "string_helpers_test", + srcs = ["string_helpers_test.cpp"], + deps = [ + ":string_helpers", + "@llvm-project//llvm:gtest", + "@llvm-project//llvm:gtest_main", + ], +) diff --git a/common/string_helpers.cpp b/common/string_helpers.cpp new file mode 100644 index 000000000000..b5f48d8d060b --- /dev/null +++ b/common/string_helpers.cpp @@ -0,0 +1,88 @@ +// 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 "common/string_helpers.h" + +#include "common/check.h" +#include "llvm/ADT/StringExtras.h" + +namespace Carbon { + +// Carbon only takes uppercase hex input. +static auto FromHex(char c) -> std::optional { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return 10 + c - 'A'; + } + return std::nullopt; +} + +auto UnescapeStringLiteral(llvm::StringRef source) + -> std::optional { + std::string ret; + ret.reserve(source.size()); + size_t i = 0; + while (i < source.size()) { + char c = source[i]; + if (c == '\\') { + ++i; + if (i == source.size()) { + return std::nullopt; + } + switch (source[i]) { + case 'n': + ret.push_back('\n'); + break; + case 'r': + ret.push_back('\r'); + break; + case 't': + ret.push_back('\t'); + break; + case '0': + if (i + 1 < source.size() && llvm::isDigit(source[i + 1])) { + // \0[0-9] is reserved. + return std::nullopt; + } + ret.push_back('\0'); + break; + case '"': + ret.push_back('"'); + break; + case '\'': + ret.push_back('\''); + break; + case '\\': + ret.push_back('\\'); + break; + case 'x': { + i += 2; + if (i >= source.size()) { + return std::nullopt; + } + std::optional c1 = FromHex(source[i - 1]); + std::optional c2 = FromHex(source[i]); + if (c1 == std::nullopt || c2 == std::nullopt) { + return std::nullopt; + } + ret.push_back(16 * *c1 + *c2); + break; + } + case 'u': + FATAL() << "\\u is not yet supported in string literals"; + default: + // Unsupported. + return std::nullopt; + } + } else { + ret.push_back(c); + } + ++i; + } + return ret; +} + +} // namespace Carbon diff --git a/common/string_helpers.h b/common/string_helpers.h new file mode 100644 index 000000000000..01682b224f7d --- /dev/null +++ b/common/string_helpers.h @@ -0,0 +1,25 @@ +// 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 COMMON_STRING_HELPERS_H_ +#define COMMON_STRING_HELPERS_H_ + +#include +#include + +#include "llvm/ADT/StringRef.h" + +namespace Carbon { + +// Note llvm StringExtras has significant functionality which is intended to be +// complementary to this. + +// Unescapes Carbon escape sequences in the source string. Returns std::nullopt +// on bad input. +auto UnescapeStringLiteral(llvm::StringRef source) + -> std::optional; + +} // namespace Carbon + +#endif // COMMON_STRING_HELPERS_H_ diff --git a/common/string_helpers_test.cpp b/common/string_helpers_test.cpp new file mode 100644 index 000000000000..f434f0a8eb99 --- /dev/null +++ b/common/string_helpers_test.cpp @@ -0,0 +1,54 @@ +// 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 "common/string_helpers.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::Eq; +using ::testing::Optional; + +namespace Carbon { +namespace { + +TEST(UnescapeStringLiteral, Valid) { + EXPECT_THAT(UnescapeStringLiteral("test"), Optional(Eq("test"))); + EXPECT_THAT(UnescapeStringLiteral("test\n"), Optional(Eq("test\n"))); + EXPECT_THAT(UnescapeStringLiteral("test\\n"), Optional(Eq("test\n"))); + EXPECT_THAT(UnescapeStringLiteral("abc\\ndef"), Optional(Eq("abc\ndef"))); + EXPECT_THAT(UnescapeStringLiteral("test\\\\n"), Optional(Eq("test\\n"))); + EXPECT_THAT(UnescapeStringLiteral("\\xAA"), Optional(Eq("\xAA"))); + EXPECT_THAT(UnescapeStringLiteral("\\x12"), Optional(Eq("\x12"))); +} + +TEST(UnescapeStringLiteral, Invalid) { + // Missing char after `\`. + EXPECT_THAT(UnescapeStringLiteral("a\\"), Eq(std::nullopt)); + // Not a supported escape. + EXPECT_THAT(UnescapeStringLiteral("\\e"), Eq(std::nullopt)); + // Needs 2 hex chars. + EXPECT_THAT(UnescapeStringLiteral("\\x"), Eq(std::nullopt)); + // Needs 2 hex chars. + EXPECT_THAT(UnescapeStringLiteral("\\xA"), Eq(std::nullopt)); + // Needs uppercase hex. + EXPECT_THAT(UnescapeStringLiteral("\\xaa"), Eq(std::nullopt)); + // Reserved. + EXPECT_THAT(UnescapeStringLiteral("\\00"), Eq(std::nullopt)); +} + +TEST(UnescapeStringLiteral, Nul) { + std::optional str = UnescapeStringLiteral("a\\0b"); + ASSERT_NE(str, std::nullopt); + EXPECT_THAT(str->size(), Eq(3)); + EXPECT_THAT(strlen(str->c_str()), Eq(1)); + EXPECT_THAT((*str)[0], Eq('a')); + EXPECT_THAT((*str)[1], Eq('\0')); + EXPECT_THAT((*str)[2], Eq('b')); +} + +} // namespace +} // namespace Carbon diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index fb6dafb6b68e..f5e3fb4430f5 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -67,7 +67,7 @@ static void PrintFields(llvm::raw_ostream& out, const std::vector& fields) { llvm::ListSeparator sep; for (const auto& field : fields) { - out << sep << field.name << " = " << field.expression; + out << sep << field.name << " = " << *field.expression; } } @@ -129,6 +129,14 @@ void Expression::Print(llvm::raw_ostream& out) const { case Expression::Kind::IntTypeLiteral: out << "i32"; break; + case Expression::Kind::StringLiteral: + out << "\""; + out.write_escaped(cast(*this).Val()); + out << "\""; + break; + case Expression::Kind::StringTypeLiteral: + out << "String"; + break; case Expression::Kind::TypeTypeLiteral: out << "Type"; break; @@ -140,6 +148,13 @@ void Expression::Print(llvm::raw_ostream& out) const { out << "fn " << *fn.Parameter() << " -> " << *fn.ReturnType(); break; } + case Expression::Kind::IntrinsicExpression: + out << "intrinsic_expression("; + switch (cast(*this).Intrinsic()) { + case IntrinsicExpression::IntrinsicKind::Print: + out << "print"; + } + out << ")"; } } diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index e562265ad758..e35d671e9793 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -29,9 +29,12 @@ class Expression { ContinuationTypeLiteral, // The type of a continuation value. IntLiteral, PrimitiveOperatorExpression, + StringLiteral, + StringTypeLiteral, TupleLiteral, TypeTypeLiteral, IdentifierExpression, + IntrinsicExpression, }; // Returns the enumerator corresponding to the most-derived type of this @@ -178,6 +181,31 @@ class BoolLiteral : public Expression { bool val; }; +class StringLiteral : public Expression { + public: + explicit StringLiteral(int line_num, std::string val) + : Expression(Kind::StringLiteral, line_num), val(std::move(val)) {} + + static auto classof(const Expression* exp) -> bool { + return exp->Tag() == Kind::StringLiteral; + } + + auto Val() const -> const std::string& { return val; } + + private: + std::string val; +}; + +class StringTypeLiteral : public Expression { + public: + explicit StringTypeLiteral(int line_num) + : Expression(Kind::StringTypeLiteral, line_num) {} + + static auto classof(const Expression* exp) -> bool { + return exp->Tag() == Kind::StringTypeLiteral; + } +}; + class TupleLiteral : public Expression { public: explicit TupleLiteral(int line_num) : TupleLiteral(line_num, {}) {} @@ -301,6 +329,25 @@ class TypeTypeLiteral : public Expression { } }; +class IntrinsicExpression : public Expression { + public: + enum class IntrinsicKind { + Print, + }; + + explicit IntrinsicExpression(IntrinsicKind intrinsic) + : Expression(Kind::IntrinsicExpression, -1), intrinsic(intrinsic) {} + + static auto classof(const Expression* exp) -> bool { + return exp->Tag() == Kind::IntrinsicExpression; + } + + auto Intrinsic() const -> IntrinsicKind { return intrinsic; } + + private: + IntrinsicKind intrinsic; +}; + } // namespace Carbon #endif // EXECUTABLE_SEMANTICS_AST_EXPRESSION_H_ diff --git a/executable_semantics/ast/pattern.cpp b/executable_semantics/ast/pattern.cpp index 8ebdae1df7da..23d7c06b3306 100644 --- a/executable_semantics/ast/pattern.cpp +++ b/executable_semantics/ast/pattern.cpp @@ -37,19 +37,19 @@ void Pattern::Print(llvm::raw_ostream& out) const { out << "("; llvm::ListSeparator sep; for (const TuplePattern::Field& field : tuple.Fields()) { - out << sep << field.name << " = " << field.pattern; + out << sep << field.name << " = " << *field.pattern; } out << ")"; break; } case Kind::AlternativePattern: { const auto& alternative = cast(*this); - out << alternative.ChoiceType() << "." << alternative.AlternativeName() - << alternative.Arguments(); + out << *alternative.ChoiceType() << "." << alternative.AlternativeName() + << *alternative.Arguments(); break; } case Kind::ExpressionPattern: - out << cast(*this).Expression(); + out << *cast(*this).Expression(); break; } } diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index ed0ce8b4e6b2..33b34324ef85 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -60,6 +60,15 @@ auto CurrentEnv(State* state) -> Env { return frame->scopes.Top()->values; } +// Returns the given name from the environment, printing an error if not found. +static auto GetFromEnv(int line_num, const std::string& name) -> Address { + std::optional
pointer = CurrentEnv(state).Get(name); + if (!pointer) { + FATAL_RUNTIME_ERROR(line_num) << "could not find `" << name << "`"; + } + return *pointer; +} + void PrintState(llvm::raw_ostream& out) { out << "{\nstack: "; PrintStack(state->stack, out); @@ -411,14 +420,9 @@ void StepLvalue() { case Expression::Kind::IdentifierExpression: { // { {x :: C, E, F} :: S, H} // -> { {E(x) :: C, E, F} :: S, H} - std::optional
pointer = - CurrentEnv(state).Get(cast(*exp).Name()); - if (!pointer) { - FATAL_RUNTIME_ERROR(exp->LineNumber()) - << "could not find `" << cast(*exp).Name() - << "`"; - } - const Value* v = global_arena->New(*pointer); + Address pointer = GetFromEnv(exp->LineNumber(), + cast(*exp).Name()); + const Value* v = global_arena->New(pointer); frame->todo.Pop(); frame->todo.Push(global_arena->New(v)); break; @@ -496,10 +500,12 @@ void StepLvalue() { case Expression::Kind::BoolTypeLiteral: case Expression::Kind::TypeTypeLiteral: case Expression::Kind::FunctionTypeLiteral: - case Expression::Kind::ContinuationTypeLiteral: { + case Expression::Kind::ContinuationTypeLiteral: + case Expression::Kind::StringLiteral: + case Expression::Kind::StringTypeLiteral: + case Expression::Kind::IntrinsicExpression: FATAL_RUNTIME_ERROR_NO_LINE() << "Can't treat expression as lvalue: " << *exp; - } } } @@ -597,12 +603,8 @@ void StepExp() { CHECK(act->Pos() == 0); const auto& ident = cast(*exp); // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} - std::optional
pointer = CurrentEnv(state).Get(ident.Name()); - if (!pointer) { - FATAL_RUNTIME_ERROR(exp->LineNumber()) - << "could not find `" << ident.Name() << "`"; - } - const Value* pointee = state->heap.Read(*pointer, exp->LineNumber()); + Address pointer = GetFromEnv(exp->LineNumber(), ident.Name()); + const Value* pointee = state->heap.Read(pointer, exp->LineNumber()); frame->todo.Pop(1); frame->todo.Push(global_arena->New(pointee)); break; @@ -660,6 +662,22 @@ void StepExp() { FATAL() << "in handle_value with Call pos " << act->Pos(); } break; + case Expression::Kind::IntrinsicExpression: + CHECK(act->Pos() == 0); + // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} + frame->todo.Pop(1); + switch (cast(*exp).Intrinsic()) { + case IntrinsicExpression::IntrinsicKind::Print: + Address pointer = GetFromEnv(exp->LineNumber(), "format_str"); + const Value* pointee = state->heap.Read(pointer, exp->LineNumber()); + CHECK(pointee->Tag() == Value::Kind::StringValue); + // TODO: This could eventually use something like llvm::formatv. + llvm::outs() << cast(*pointee).Val(); + frame->todo.Push(global_arena->New(&TupleValue::Empty())); + break; + } + break; + case Expression::Kind::IntTypeLiteral: { CHECK(act->Pos() == 0); const Value* v = global_arena->New(); @@ -710,6 +728,20 @@ void StepExp() { frame->todo.Push(global_arena->New(v)); break; } + case Expression::Kind::StringLiteral: + CHECK(act->Pos() == 0); + // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} + frame->todo.Pop(1); + frame->todo.Push(global_arena->New( + global_arena->New(cast(*exp).Val()))); + break; + case Expression::Kind::StringTypeLiteral: { + CHECK(act->Pos() == 0); + const Value* v = global_arena->New(); + frame->todo.Pop(1); + frame->todo.Push(global_arena->New(v)); + break; + } } // switch (exp->Tag) } diff --git a/executable_semantics/interpreter/typecheck.cpp b/executable_semantics/interpreter/typecheck.cpp index 1773ef50affe..46caa780696e 100644 --- a/executable_semantics/interpreter/typecheck.cpp +++ b/executable_semantics/interpreter/typecheck.cpp @@ -82,7 +82,19 @@ static auto ReifyType(const Value* t, int line_num) -> const Expression* { case Value::Kind::VariableType: return global_arena->New( 0, cast(*t).Name()); - default: + case Value::Kind::StringType: + return global_arena->New(0); + case Value::Kind::AlternativeConstructorValue: + case Value::Kind::AlternativeValue: + case Value::Kind::AutoType: + case Value::Kind::BindingPlaceholderValue: + case Value::Kind::BoolValue: + case Value::Kind::ContinuationValue: + case Value::Kind::FunctionValue: + case Value::Kind::IntValue: + case Value::Kind::PointerValue: + case Value::Kind::StringValue: + case Value::Kind::StructValue: FATAL() << "expected a type, not " << *t; } } @@ -158,10 +170,10 @@ static auto ArgumentDeduction(int line_num, TypeEnv deduced, const Value* param, case Value::Kind::ChoiceType: case Value::Kind::IntType: case Value::Kind::BoolType: - case Value::Kind::TypeType: { + case Value::Kind::TypeType: + case Value::Kind::StringType: ExpectType(line_num, "argument deduction", param, arg); return deduced; - } // The rest of these cases should never happen. case Value::Kind::IntValue: case Value::Kind::BoolValue: @@ -172,6 +184,7 @@ static auto ArgumentDeduction(int line_num, TypeEnv deduced, const Value* param, case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: case Value::Kind::ContinuationValue: + case Value::Kind::StringValue: FATAL() << "In ArgumentDeduction: expected type, not value " << *param; } } @@ -213,6 +226,7 @@ static auto Substitute(TypeEnv dict, const Value* type) -> const Value* { case Value::Kind::StructType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: + case Value::Kind::StringType: return type; // The rest of these cases should never happen. case Value::Kind::IntValue: @@ -224,6 +238,7 @@ static auto Substitute(TypeEnv dict, const Value* type) -> const Value* { case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: case Value::Kind::ContinuationValue: + case Value::Kind::StringValue: FATAL() << "In Substitute: expected type, not value " << *type; } } @@ -475,12 +490,17 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values) /*is_omitted_return_type=*/false); return TCExpression(new_e, global_arena->New(), types); } + case Expression::Kind::StringLiteral: + return TCExpression(e, global_arena->New(), types); + case Expression::Kind::IntrinsicExpression: + switch (cast(*e).Intrinsic()) { + case IntrinsicExpression::IntrinsicKind::Print: + return TCExpression(e, &TupleValue::Empty(), types); + } case Expression::Kind::IntTypeLiteral: - return TCExpression(e, global_arena->New(), types); case Expression::Kind::BoolTypeLiteral: - return TCExpression(e, global_arena->New(), types); + case Expression::Kind::StringTypeLiteral: case Expression::Kind::TypeTypeLiteral: - return TCExpression(e, global_arena->New(), types); case Expression::Kind::ContinuationTypeLiteral: return TCExpression(e, global_arena->New(), types); } diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 25f117ec7e9b..caab69b3e6e8 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -236,6 +236,14 @@ void Value::Print(llvm::raw_ostream& out) const { // TODO: Find a way to print useful information about the continuation // without creating a dependency cycle. break; + case Value::Kind::StringType: + out << "String"; + break; + case Value::Kind::StringValue: + out << "\""; + out.write_escaped(cast(*this).Val()); + out << "\""; + break; } } @@ -293,6 +301,10 @@ auto CopyVal(const Value* val, int line_num) -> const Value* { return global_arena->New(); case Value::Kind::ContinuationType: return global_arena->New(); + case Value::Kind::StringType: + return global_arena->New(); + case Value::Kind::StringValue: + return global_arena->New(cast(*val).Val()); case Value::Kind::VariableType: case Value::Kind::StructType: case Value::Kind::ChoiceType: @@ -339,6 +351,7 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool { case Value::Kind::BoolType: case Value::Kind::ContinuationType: case Value::Kind::TypeType: + case Value::Kind::StringType: return true; case Value::Kind::VariableType: return cast(*t1).Name() == cast(*t2).Name(); @@ -390,6 +403,8 @@ auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool { case Value::Kind::TupleValue: return FieldsValueEqual(cast(*v1).Elements(), cast(*v2).Elements(), line_num); + case Value::Kind::StringValue: + return cast(*v1).Val() == cast(*v2).Val(); case Value::Kind::IntType: case Value::Kind::BoolType: case Value::Kind::TypeType: @@ -400,6 +415,7 @@ auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool { case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: + case Value::Kind::StringType: return TypeEqual(v1, v2); case Value::Kind::StructValue: case Value::Kind::AlternativeValue: diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index 8598e4c5da00..b2c98392fe7c 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -48,10 +48,12 @@ class Value { StructType, ChoiceType, ContinuationType, // The type of a continuation. - VariableType, // e.g. generic type parameters + VariableType, // e.g., generic type parameters. BindingPlaceholderValue, AlternativeConstructorValue, - ContinuationValue // A first-class continuation value. + ContinuationValue, // A first-class continuation value. + StringType, + StringValue, }; Value(const Value&) = delete; @@ -442,6 +444,32 @@ class ContinuationValue : public Value { std::vector stack; }; +// The String type. +class StringType : public Value { + public: + StringType() : Value(Kind::StringType) {} + + static auto classof(const Value* value) -> bool { + return value->Tag() == Kind::StringType; + } +}; + +// A string value. +class StringValue : public Value { + public: + explicit StringValue(std::string val) + : Value(Kind::StringValue), val(std::move(val)) {} + + static auto classof(const Value* value) -> bool { + return value->Tag() == Kind::StringValue; + } + + auto Val() const -> const std::string& { return val; } + + private: + std::string val; +}; + auto CopyVal(const Value* val, int line_num) -> const Value*; auto TypeEqual(const Value* t1, const Value* t2) -> bool; diff --git a/executable_semantics/syntax/BUILD b/executable_semantics/syntax/BUILD index e48972ea286d..60dec0b54810 100644 --- a/executable_semantics/syntax/BUILD +++ b/executable_semantics/syntax/BUILD @@ -31,6 +31,7 @@ cc_library( ":paren_contents", "//common:check", "//common:ostream", + "//common:string_helpers", "//executable_semantics/ast:declaration", "//executable_semantics/ast:expression", "//executable_semantics/common:arena", diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index 664e71fd5168..c302d46859ac 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -8,6 +8,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include #include "common/check.h" +#include "common/string_helpers.h" #include "executable_semantics/common/tracing_flag.h" #include "executable_semantics/syntax/parse_and_lex_context.h" #include "llvm/ADT/StringExtras.h" @@ -49,6 +50,7 @@ MATCH "match" NOT "not" OR "or" RETURN "return" +STRING "String" STRUCT "struct" TRUE "true" TYPE "Type" @@ -63,6 +65,7 @@ UNDERSCORE "_" identifier [A-Za-z_][A-Za-z0-9_]* sized_type_literal [iuf][1-9][0-9]* integer_literal [0-9]+ +string_literal \"([^\\\"\n]|\\.)*\" horizontal_whitespace [ \t\r] whitespace [ \t\r\n] operand_start [(A-Za-z0-9_"] @@ -120,6 +123,7 @@ operand_start [(A-Za-z0-9_"] {RUN} { return Carbon::Parser::make_RUN(context.current_token_position); } {AWAIT} { return Carbon::Parser::make_AWAIT(context.current_token_position); } {UNDERSCORE} { return Carbon::Parser::make_UNDERSCORE(context.current_token_position); } +{STRING} { return Carbon::Parser::make_STRING(context.current_token_position); } {sized_type_literal} { return Carbon::Parser::make_sized_type_literal(yytext, context.current_token_position); } @@ -189,6 +193,23 @@ operator and its operand, leading to three more cases: return Carbon::Parser::make_integer_literal(val, context.current_token_position); } +{string_literal} { + llvm::StringRef str(yytext); + CHECK(str.consume_front("\"") && str.consume_back("\"")); + std::optional unescaped = Carbon::UnescapeStringLiteral(str); + if (unescaped == std::nullopt) { + if (Carbon::tracing_output) { + // Print a newline because tracing prints an incomplete line + // "Reading a token: ". + llvm::errs() << "\n"; + } + FATAL_COMPILATION_ERROR(yylineno) + << "Invalid escaping in string: " << yytext; + } + return Carbon::Parser::make_string_literal( + *unescaped, context.current_token_position); +} + {ONE_LINE_COMMENT} { // Advance end by 1 line, resetting the column to zero. context.current_token_position.lines(1); diff --git a/executable_semantics/syntax/parse_and_lex_context.cpp b/executable_semantics/syntax/parse_and_lex_context.cpp index 33f05331b304..dd0c61346fc8 100644 --- a/executable_semantics/syntax/parse_and_lex_context.cpp +++ b/executable_semantics/syntax/parse_and_lex_context.cpp @@ -4,12 +4,16 @@ #include "executable_semantics/syntax/parse_and_lex_context.h" +namespace Carbon { + // Writes a syntax error diagnostic, containing message, for the input file at // the given line, to standard error. -auto Carbon::ParseAndLexContext::PrintDiagnostic(const std::string& message, - int line_num) -> void { +auto ParseAndLexContext::PrintDiagnostic(const std::string& message, + int line_num) -> void { // TODO: Do we really want this to be fatal? It makes the comment and the // name a lie, and renders some of the other yyparse() result propagation code // moot. FATAL_COMPILATION_ERROR(line_num) << message; } + +} // namespace Carbon diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index 0f147230a93b..4d6a2be6fdac 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -90,6 +90,7 @@ void Carbon::Parser::error(const location_type&, const std::string& message) { %token integer_literal %token identifier %token sized_type_literal +%token string_literal %type designator %type declaration %type function_declaration @@ -131,6 +132,7 @@ void Carbon::Parser::error(const location_type&, const std::string& message) { %token AND %token OR %token NOT +%token STRING %token BOOL %token TYPE %token FN @@ -218,6 +220,8 @@ expression: { $$ = global_arena->New(yylineno, $1, $3); } | integer_literal { $$ = global_arena->New(yylineno, $1); } +| string_literal + { $$ = global_arena->New(yylineno, $1); } | TRUE { $$ = global_arena->New(yylineno, true); } | FALSE @@ -229,6 +233,8 @@ expression: CHECK($1[0] == 'i' && val == 32) << "Only i32 is supported for now: " << $1; $$ = global_arena->New(yylineno); } +| STRING + { $$ = global_arena->New(yylineno); } | BOOL { $$ = global_arena->New(yylineno); } | TYPE diff --git a/executable_semantics/syntax/syntax_helpers.cpp b/executable_semantics/syntax/syntax_helpers.cpp index d4d084dfdf56..8d8d657aea6b 100644 --- a/executable_semantics/syntax/syntax_helpers.cpp +++ b/executable_semantics/syntax/syntax_helpers.cpp @@ -4,6 +4,7 @@ #include "executable_semantics/syntax/syntax_helpers.h" +#include "common/check.h" #include "common/ostream.h" #include "executable_semantics/common/arena.h" #include "executable_semantics/common/tracing_flag.h" @@ -12,7 +13,29 @@ namespace Carbon { -void ExecProgram(const std::list& fs) { +// Adds builtins, currently only Print(). Note Print() is experimental, not +// standardized, but is made available for printing state in tests. +static void AddIntrinsics(std::list* fs) { + std::vector print_fields = {TuplePattern::Field( + "0", global_arena->New( + -1, "format_str", + global_arena->New( + global_arena->New(-1))))}; + auto* print_return = + global_arena->New(-1, + global_arena->New( + IntrinsicExpression::IntrinsicKind::Print), + false); + auto* print = global_arena->New(FunctionDefinition( + -1, "Print", std::vector(), + global_arena->New(-1, print_fields), + global_arena->New(global_arena->New(-1)), + /*is_omitted_return_type=*/false, print_return)); + fs->insert(fs->begin(), print); +} + +void ExecProgram(std::list fs) { + AddIntrinsics(&fs); if (tracing_output) { llvm::outs() << "********** source program **********\n"; for (const auto* decl : fs) { diff --git a/executable_semantics/syntax/syntax_helpers.h b/executable_semantics/syntax/syntax_helpers.h index 2e6c8284a345..b0a18f61fe8a 100644 --- a/executable_semantics/syntax/syntax_helpers.h +++ b/executable_semantics/syntax/syntax_helpers.h @@ -16,7 +16,7 @@ namespace Carbon { // Runs the top-level declaration list. -void ExecProgram(const std::list& fs); +void ExecProgram(std::list fs); } // namespace Carbon diff --git a/executable_semantics/test_list.bzl b/executable_semantics/test_list.bzl index e1a0daff26b8..6feff1268ca0 100644 --- a/executable_semantics/test_list.bzl +++ b/executable_semantics/test_list.bzl @@ -65,6 +65,7 @@ TEST_LIST = [ "pattern_init", "pattern_variable_fail", "placeholder_variable", + "print1", "record1", "return_auto", "return_empty_explicit", @@ -74,6 +75,14 @@ TEST_LIST = [ "return_empty_implicit_fail1", "return_empty_implicit_fail2", "star", + "string1", + "string2", + "string3", + "string_fail1", + "string_fail2", + "string_fail3", + "string_fail4", + "string_fail5", "struct1", "struct2", "struct3", diff --git a/executable_semantics/testdata/print1.carbon b/executable_semantics/testdata/print1.carbon new file mode 100644 index 000000000000..36a518fb10a0 --- /dev/null +++ b/executable_semantics/testdata/print1.carbon @@ -0,0 +1,9 @@ +// 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 + +fn main() -> i32 { + var s: auto = "Hello world!\n"; + Print(s); + return 0; +} diff --git a/executable_semantics/testdata/print1.golden b/executable_semantics/testdata/print1.golden new file mode 100644 index 000000000000..f701bed11cfa --- /dev/null +++ b/executable_semantics/testdata/print1.golden @@ -0,0 +1,2 @@ +Hello world! +result: 0 diff --git a/executable_semantics/testdata/string1.carbon b/executable_semantics/testdata/string1.carbon new file mode 100644 index 000000000000..6a37547e96d0 --- /dev/null +++ b/executable_semantics/testdata/string1.carbon @@ -0,0 +1,14 @@ +// 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 + +fn CompareStr(s: String) -> i32 { + if (s == "str") { + return 0; + } + return 1; +} + +fn main() -> i32 { + return CompareStr("str"); +} diff --git a/executable_semantics/testdata/string1.golden b/executable_semantics/testdata/string1.golden new file mode 100644 index 000000000000..2db2755da426 --- /dev/null +++ b/executable_semantics/testdata/string1.golden @@ -0,0 +1 @@ +result: 0 diff --git a/executable_semantics/testdata/string2.carbon b/executable_semantics/testdata/string2.carbon new file mode 100644 index 000000000000..9741a975a71d --- /dev/null +++ b/executable_semantics/testdata/string2.carbon @@ -0,0 +1,14 @@ +// 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 + +fn CompareStr(s: String) -> i32 { + if (s == "str\n") { + return 0; + } + return 1; +} + +fn main() -> i32 { + return CompareStr("str\n"); +} diff --git a/executable_semantics/testdata/string2.golden b/executable_semantics/testdata/string2.golden new file mode 100644 index 000000000000..2db2755da426 --- /dev/null +++ b/executable_semantics/testdata/string2.golden @@ -0,0 +1 @@ +result: 0 diff --git a/executable_semantics/testdata/string3.carbon b/executable_semantics/testdata/string3.carbon new file mode 100644 index 000000000000..e2642919ab46 --- /dev/null +++ b/executable_semantics/testdata/string3.carbon @@ -0,0 +1,14 @@ +// 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 + +fn CompareStr(s: String) -> i32 { + if (s == "str;") { + return 0; + } + return 1; +} + +fn main() -> i32 { + return CompareStr("\x73\x74\x72\x3B"); +} diff --git a/executable_semantics/testdata/string3.golden b/executable_semantics/testdata/string3.golden new file mode 100644 index 000000000000..2db2755da426 --- /dev/null +++ b/executable_semantics/testdata/string3.golden @@ -0,0 +1 @@ +result: 0 diff --git a/executable_semantics/testdata/string_fail1.carbon b/executable_semantics/testdata/string_fail1.carbon new file mode 100644 index 000000000000..6a9e4f3b8918 --- /dev/null +++ b/executable_semantics/testdata/string_fail1.carbon @@ -0,0 +1,8 @@ +// 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 + +fn main() -> i32 { + Print("str\e"); + return 0; +} diff --git a/executable_semantics/testdata/string_fail1.golden b/executable_semantics/testdata/string_fail1.golden new file mode 100644 index 000000000000..e5cccb823fb2 --- /dev/null +++ b/executable_semantics/testdata/string_fail1.golden @@ -0,0 +1,2 @@ +COMPILATION ERROR: 6: Invalid escaping in string: "str\e" +EXIT CODE: 255 diff --git a/executable_semantics/testdata/string_fail2.carbon b/executable_semantics/testdata/string_fail2.carbon new file mode 100644 index 000000000000..6da2b1e03c42 --- /dev/null +++ b/executable_semantics/testdata/string_fail2.carbon @@ -0,0 +1,8 @@ +// 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 + +fn main() -> i32 { + Print("str\x"); + return 0; +} diff --git a/executable_semantics/testdata/string_fail2.golden b/executable_semantics/testdata/string_fail2.golden new file mode 100644 index 000000000000..cdb87bd14a6a --- /dev/null +++ b/executable_semantics/testdata/string_fail2.golden @@ -0,0 +1,2 @@ +COMPILATION ERROR: 6: Invalid escaping in string: "str\x" +EXIT CODE: 255 diff --git a/executable_semantics/testdata/string_fail3.carbon b/executable_semantics/testdata/string_fail3.carbon new file mode 100644 index 000000000000..f73b94ac07a7 --- /dev/null +++ b/executable_semantics/testdata/string_fail3.carbon @@ -0,0 +1,8 @@ +// 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 + +fn main() -> i32 { + Print("str\xaa"); + return 0; +} diff --git a/executable_semantics/testdata/string_fail3.golden b/executable_semantics/testdata/string_fail3.golden new file mode 100644 index 000000000000..f3329845368c --- /dev/null +++ b/executable_semantics/testdata/string_fail3.golden @@ -0,0 +1,2 @@ +COMPILATION ERROR: 6: Invalid escaping in string: "str\xaa" +EXIT CODE: 255 diff --git a/executable_semantics/testdata/string_fail4.carbon b/executable_semantics/testdata/string_fail4.carbon new file mode 100644 index 000000000000..438ac819f031 --- /dev/null +++ b/executable_semantics/testdata/string_fail4.carbon @@ -0,0 +1,8 @@ +// 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 + +fn main() -> i32 { + Print("str\01"); + return 0; +} diff --git a/executable_semantics/testdata/string_fail4.golden b/executable_semantics/testdata/string_fail4.golden new file mode 100644 index 000000000000..2da23bb3047d --- /dev/null +++ b/executable_semantics/testdata/string_fail4.golden @@ -0,0 +1,2 @@ +COMPILATION ERROR: 6: Invalid escaping in string: "str\01" +EXIT CODE: 255 diff --git a/executable_semantics/testdata/string_fail5.carbon b/executable_semantics/testdata/string_fail5.carbon new file mode 100644 index 000000000000..0d4ef060cf6a --- /dev/null +++ b/executable_semantics/testdata/string_fail5.carbon @@ -0,0 +1,9 @@ +// 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 + +fn main() -> i32 { + Print("new +line"); + return 0; +} diff --git a/executable_semantics/testdata/string_fail5.golden b/executable_semantics/testdata/string_fail5.golden new file mode 100644 index 000000000000..c2abd374a6be --- /dev/null +++ b/executable_semantics/testdata/string_fail5.golden @@ -0,0 +1,2 @@ +COMPILATION ERROR: 6: invalid character '\x22' in source file. +EXIT CODE: 255