mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 12:41:06 +01:00
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.
This commit is contained in:
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user