Просмотр исходного кода

Merge the source library from the toolchain repository. (#210)

This library manages buffers of source code, either in-memory or mapped
from the filesystem. At the moment it is a bit simplistic and assumes
`mmap` is available in its implementation details. We should make this
more portable in the future, but this is currently just a direct copy
from the toolchain repository.
Chandler Carruth 5 лет назад
Родитель
Сommit
2ddf0936fd
4 измененных файлов с 271 добавлено и 0 удалено
  1. 26 0
      source/BUILD
  2. 88 0
      source/source_buffer.cpp
  3. 98 0
      source/source_buffer.h
  4. 59 0
      source/source_buffer_test.cpp

+ 26 - 0
source/BUILD

@@ -0,0 +1,26 @@
+# 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
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+    name = "source_buffer",
+    srcs = ["source_buffer.cpp"],
+    hdrs = ["source_buffer.h"],
+    deps = ["@llvm-project//llvm:Support"],
+)
+
+cc_test(
+    name = "source_buffer_test",
+    srcs = ["source_buffer_test.cpp"],
+    deps = [
+        ":source_buffer",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:gmock",
+        "@llvm-project//llvm:gtest",
+        "@llvm-project//llvm:gtest_main",
+    ],
+)

+ 88 - 0
source/source_buffer.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 "source/source_buffer.h"
+#include "llvm/ADT/ScopeExit.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <system_error>
+
+namespace Carbon {
+
+auto SourceBuffer::CreateFromText(llvm::Twine text, llvm::StringRef filename)
+    -> SourceBuffer {
+  return SourceBuffer(filename, text.str());
+}
+
+static auto ErrnoToError(int errno_value) -> llvm::Error {
+  return llvm::errorCodeToError(
+      std::error_code(errno_value, std::generic_category()));
+}
+
+auto SourceBuffer::CreateFromFile(llvm::StringRef filename)
+    -> llvm::Expected<SourceBuffer> {
+  SourceBuffer buffer(filename);
+
+  errno = 0;
+  int file_descriptor = open(buffer.filename_.c_str(), O_RDONLY);
+  if (file_descriptor == -1)
+    return ErrnoToError(errno);
+
+  // Now that we have an open file, we need to close it on any error.
+  auto closer = llvm::make_scope_exit([file_descriptor] { close(file_descriptor); });
+
+  struct stat stat_buffer = {};
+  errno = 0;
+  if (fstat(file_descriptor, &stat_buffer) == -1)
+    return ErrnoToError(errno);
+
+  int64_t size = stat_buffer.st_size;
+  if (size == 0) {
+    // Nothing to do for an empty file.
+    return {std::move(buffer)};
+  }
+
+  errno = 0;
+  void* mapped_text = mmap(nullptr, size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
+                          file_descriptor, /*offset=*/0);
+  if (mapped_text == MAP_FAILED)
+    return ErrnoToError(errno);
+
+  errno = 0;
+  closer.release();
+  if (close(file_descriptor) == -1) {
+    // Try to unmap the text. No errer handling as this is just best-effort
+    // cleanup.
+    munmap(mapped_text, size);
+    return ErrnoToError(errno);
+  }
+
+  buffer.text_ = llvm::StringRef(static_cast<const char*>(mapped_text), size);
+  assert(!buffer.text_.empty() &&
+         "Must not have an empty text when we have mapped data from a file!");
+  return {std::move(buffer)};
+}
+
+SourceBuffer::~SourceBuffer() {
+  if (is_string_rep_) {
+    string_storage_.~decltype(string_storage_)();
+    return;
+  }
+
+  if (!text_.empty()) {
+    errno = 0;
+    int result =
+        munmap(const_cast<void*>((const void*)text_.data()), text_.size());
+    (void)result;
+    assert(result != -1 && "Unmapping text failed!");
+  }
+}
+
+}  // namespace Carbon

+ 98 - 0
source/source_buffer.h

@@ -0,0 +1,98 @@
+// 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 SOURCE_SOURCEBUFFER_H_
+#define SOURCE_SOURCEBUFFER_H_
+
+#include <string>
+#include <utility>
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Error.h"
+
+namespace Carbon {
+
+// A buffer of Carbon source code.
+//
+// This class holds a buffer of Carbon source code as text and makes it
+// available for use in the rest of the Carbon compiler. It owns the memory for
+// the underlying source code text and ensures it lives as long as the buffer
+// objects.
+//
+// Every buffer of source code text is notionally loaded from a Carbon source
+// file, even if provided directly when constructing the buffer. The name that
+// should be used for that Carbon source file is also retained and made
+// available.
+//
+// Because the underlying memory for the source code text may have been read
+// from a file, and we may want to use facilities like `mmap` to simply map that
+// file into memory, the buffer itself is not copyable to avoid needing to
+// define copy semantics for a mapped file. We can relax this restriction with
+// some implementation complexity in the future if needed.
+class SourceBuffer {
+ public:
+  static auto CreateFromText(llvm::Twine text,
+                             llvm::StringRef filename = "/text")
+      -> SourceBuffer;
+  static auto CreateFromFile(llvm::StringRef filename)
+      -> llvm::Expected<SourceBuffer>;
+
+  // Use one of the factory functions above to create a source buffer.
+  SourceBuffer() = delete;
+
+  // Cannot copy as there may be non-trivial owned file data, see the class
+  // comment for details.
+  SourceBuffer(const SourceBuffer& arg) = delete;
+
+  SourceBuffer(SourceBuffer&& arg)
+      : filename_(std::move(arg.filename_)),
+        text_(arg.text_),
+        is_string_rep_(arg.is_string_rep_) {
+    // The easy case in when we don't need to transfer an allocated string
+    // representation.
+    if (!arg.is_string_rep_) {
+      // Take ownership of a non-string representation by clearing its text.
+      arg.text_ = llvm::StringRef();
+      return;
+    }
+
+    // If the argument is using a string rep we need to move that storage over
+    // and recreate our text `StringRef` to point at our storage.
+    new (&string_storage_) std::string(std::move(arg.string_storage_));
+    text_ = string_storage_;
+  }
+
+  ~SourceBuffer();
+
+  llvm::StringRef Filename() const { return filename_; }
+
+  llvm::StringRef Text() const { return text_; }
+
+ private:
+  std::string filename_;
+
+  llvm::StringRef text_;
+
+  bool is_string_rep_;
+
+  // We use a transparent union to avoid constructing the storage.
+  // FIXME: We should replace this and the boolean with an optional which would
+  // be much simpler.
+  union {
+    std::string string_storage_;
+  };
+
+  explicit SourceBuffer(llvm::StringRef fake_filename, std::string buffer_text)
+      : filename_(fake_filename.str()), is_string_rep_(true), string_storage_(buffer_text) {
+    text_ = string_storage_;
+  }
+
+  explicit SourceBuffer(llvm::StringRef filename)
+      : filename_(filename.str()), text_(), is_string_rep_(false) {}
+};
+
+}  // namespace Carbon
+
+#endif  // SOURCE_SOURCEBUFFER_H_

+ 59 - 0
source/source_buffer_test.cpp

@@ -0,0 +1,59 @@
+// 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 "source/source_buffer.h"
+
+#include "gtest/gtest.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace Carbon {
+namespace {
+
+TEST(SourceBufferTest, StringRep) {
+  SourceBuffer buffer =
+      SourceBuffer::CreateFromText(llvm::Twine("Hello") + " World");
+
+  EXPECT_EQ("/text", buffer.Filename());
+  EXPECT_EQ("Hello World", buffer.Text());
+
+  // Give a custom filename.
+  auto buffer2 = SourceBuffer::CreateFromText("Hello World Again!", "/custom/text");
+  EXPECT_EQ("/custom/text", buffer2.Filename());
+  EXPECT_EQ("Hello World Again!", buffer2.Text());
+}
+
+auto CreateTestFile(llvm::StringRef text) -> std::string {
+  int fd = -1;
+  llvm::SmallString<1024> path;
+  auto error_code = llvm::sys::fs::createTemporaryFile("test_file", ".txt", fd, path);
+  if (error_code) {
+    llvm::report_fatal_error(llvm::Twine("Failed to create temporary file: ") +
+                             error_code.message());
+  }
+
+  llvm::raw_fd_ostream out_stream(fd, /*shouldClose=*/true);
+  out_stream << text;
+  out_stream.close();
+
+  return path.str().str();
+}
+
+TEST(SourceBufferTest, FileRep) {
+  auto test_file_path = CreateTestFile("Hello World");
+
+  auto expected_buffer = SourceBuffer::CreateFromFile(test_file_path);
+  ASSERT_TRUE(static_cast<bool>(expected_buffer))
+      << "Error message: " << expected_buffer.takeError();
+
+  SourceBuffer& buffer = *expected_buffer;
+
+  EXPECT_EQ(test_file_path, buffer.Filename());
+  EXPECT_EQ("Hello World", buffer.Text());
+}
+
+}  // namespace
+}  // namespace Carbon