Commit 82ead55c authored by Kenton Varda's avatar Kenton Varda

Add 'embed' keyword for wholesale embedding of external files as constant values.

Example:

    const data :Data = embed "some-file.dat";

Files are looked up the same way an import would be.

You can use embed when Data or Text is expected. You can also use it when a struct type is expected -- the file will be interpreted as a message using standard binary serialization.
parent 6bdd0aaf
......@@ -1352,11 +1352,16 @@ private:
return loader.get(id);
}
kj::Maybe<DynamicValue::Reader> resolveConstant(Expression::Reader name) {
kj::Maybe<DynamicValue::Reader> resolveConstant(Expression::Reader name) override {
errorReporter.addErrorOn(name, kj::str("External constants not allowed in encode input."));
return nullptr;
}
kj::Maybe<kj::Array<const byte>> readEmbed(LocatedText::Reader filename) override {
errorReporter.addErrorOn(filename, kj::str("External embeds not allowed in encode input."));
return nullptr;
}
private:
const SchemaLoader& loader;
ErrorReporter& errorReporter;
......
......@@ -99,6 +99,7 @@ public:
uint64_t id, schema::Brand::Reader brand) override;
kj::Maybe<schema::Node::Reader> resolveFinalSchema(uint64_t id) override;
kj::Maybe<ResolvedDecl> resolveImport(kj::StringPtr name) override;
kj::Maybe<kj::Array<const byte>> readEmbed(kj::StringPtr name) override;
kj::Maybe<Type> resolveBootstrapType(schema::Type::Reader type, Schema scope) override;
private:
......@@ -232,6 +233,7 @@ public:
kj::StringPtr getSourceName() { return parserModule.getSourceName(); }
kj::Maybe<CompiledModule&> importRelative(kj::StringPtr importPath);
kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr importPath);
Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
getFileImportTable(Orphanage orphanage);
......@@ -932,6 +934,10 @@ Compiler::Node::resolveImport(kj::StringPtr name) {
}
}
kj::Maybe<kj::Array<const byte>> Compiler::Node::readEmbed(kj::StringPtr name) {
return module->embedRelative(name);
}
kj::Maybe<Type> Compiler::Node::resolveBootstrapType(schema::Type::Reader type, Schema scope) {
// TODO(someday): Arguably should return null if the type or its dependencies are placeholders.
......@@ -963,6 +969,10 @@ kj::Maybe<Compiler::CompiledModule&> Compiler::CompiledModule::importRelative(
});
}
kj::Maybe<kj::Array<const byte>> Compiler::CompiledModule::embedRelative(kj::StringPtr embedPath) {
return parserModule.embedRelative(embedPath);
}
static void findImports(Expression::Reader exp, std::set<kj::StringPtr>& output) {
switch (exp.which()) {
case Expression::UNKNOWN:
......@@ -973,6 +983,7 @@ static void findImports(Expression::Reader exp, std::set<kj::StringPtr>& output)
case Expression::BINARY:
case Expression::RELATIVE_NAME:
case Expression::ABSOLUTE_NAME:
case Expression::EMBED:
break;
case Expression::IMPORT:
......@@ -1068,6 +1079,11 @@ static void findImports(Declaration::Reader decl, std::set<kj::StringPtr>& outpu
Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
Compiler::CompiledModule::getFileImportTable(Orphanage orphanage) {
// Build a table of imports for CodeGeneratorRequest.RequestedFile.imports. Note that we only
// care about type imports, not constant value imports, since constant values (including default
// values) are already embedded in full in the schema. In other words, we only need the imports
// that would need to be #included in the generated code.
std::set<kj::StringPtr> importNames;
findImports(content.getReader().getRoot(), importNames);
......
......@@ -47,6 +47,9 @@ public:
// Find another module, relative to this one. Importing the same logical module twice should
// produce the exact same object, comparable by identity. These objects are owned by some
// outside pool that outlives the Compiler instance.
virtual kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) = 0;
// Read and return the content of a file specified using `embed`.
};
class Compiler: private SchemaLoader::LazyLoadCallback {
......
......@@ -429,6 +429,7 @@ static ChangeInfo fieldChangeType(Declaration::Builder decl, uint& nextOrdinal,
case Expression::ABSOLUTE_NAME:
case Expression::IMPORT:
case Expression::EMBED:
case Expression::APPLICATION:
case Expression::MEMBER:
KJ_FAIL_ASSERT("Unexpected expression type.");
......@@ -645,6 +646,9 @@ public:
kj::Maybe<Module&> importRelative(kj::StringPtr importPath) override {
return nullptr;
}
kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) override {
return nullptr;
}
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
KJ_FAIL_ASSERT("Unexpected parse error.", startByte, endByte, message);
......
......@@ -71,6 +71,9 @@ struct Expression {
import @16 :LocatedText;
# An import directive.
embed @17 :LocatedText;
# An embed directive.
list @6 :List(Expression);
# Bracketed list; members are never named.
......
This diff is collapsed.
......@@ -111,6 +111,7 @@ struct Expression {
MEMBER,
ABSOLUTE_NAME,
IMPORT,
EMBED,
};
struct Param;
struct Application;
......@@ -837,6 +838,10 @@ public:
inline bool hasImport() const;
inline ::capnp::compiler::LocatedText::Reader getImport() const;
inline bool isEmbed() const;
inline bool hasEmbed() const;
inline ::capnp::compiler::LocatedText::Reader getEmbed() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
......@@ -952,6 +957,14 @@ public:
inline void adoptImport(::capnp::Orphan< ::capnp::compiler::LocatedText>&& value);
inline ::capnp::Orphan< ::capnp::compiler::LocatedText> disownImport();
inline bool isEmbed();
inline bool hasEmbed();
inline ::capnp::compiler::LocatedText::Builder getEmbed();
inline void setEmbed( ::capnp::compiler::LocatedText::Reader value);
inline ::capnp::compiler::LocatedText::Builder initEmbed();
inline void adoptEmbed(::capnp::Orphan< ::capnp::compiler::LocatedText>&& value);
inline ::capnp::Orphan< ::capnp::compiler::LocatedText> disownEmbed();
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
......@@ -3882,6 +3895,58 @@ inline ::capnp::Orphan< ::capnp::compiler::LocatedText> Expression::Builder::dis
_builder.getPointerField(0 * ::capnp::POINTERS));
}
inline bool Expression::Reader::isEmbed() const {
return which() == Expression::EMBED;
}
inline bool Expression::Builder::isEmbed() {
return which() == Expression::EMBED;
}
inline bool Expression::Reader::hasEmbed() const {
if (which() != Expression::EMBED) return false;
return !_reader.getPointerField(0 * ::capnp::POINTERS).isNull();
}
inline bool Expression::Builder::hasEmbed() {
if (which() != Expression::EMBED) return false;
return !_builder.getPointerField(0 * ::capnp::POINTERS).isNull();
}
inline ::capnp::compiler::LocatedText::Reader Expression::Reader::getEmbed() const {
KJ_IREQUIRE(which() == Expression::EMBED,
"Must check which() before get()ing a union member.");
return ::capnp::_::PointerHelpers< ::capnp::compiler::LocatedText>::get(
_reader.getPointerField(0 * ::capnp::POINTERS));
}
inline ::capnp::compiler::LocatedText::Builder Expression::Builder::getEmbed() {
KJ_IREQUIRE(which() == Expression::EMBED,
"Must check which() before get()ing a union member.");
return ::capnp::_::PointerHelpers< ::capnp::compiler::LocatedText>::get(
_builder.getPointerField(0 * ::capnp::POINTERS));
}
inline void Expression::Builder::setEmbed( ::capnp::compiler::LocatedText::Reader value) {
_builder.setDataField<Expression::Which>(
0 * ::capnp::ELEMENTS, Expression::EMBED);
::capnp::_::PointerHelpers< ::capnp::compiler::LocatedText>::set(
_builder.getPointerField(0 * ::capnp::POINTERS), value);
}
inline ::capnp::compiler::LocatedText::Builder Expression::Builder::initEmbed() {
_builder.setDataField<Expression::Which>(
0 * ::capnp::ELEMENTS, Expression::EMBED);
return ::capnp::_::PointerHelpers< ::capnp::compiler::LocatedText>::init(
_builder.getPointerField(0 * ::capnp::POINTERS));
}
inline void Expression::Builder::adoptEmbed(
::capnp::Orphan< ::capnp::compiler::LocatedText>&& value) {
_builder.setDataField<Expression::Which>(
0 * ::capnp::ELEMENTS, Expression::EMBED);
::capnp::_::PointerHelpers< ::capnp::compiler::LocatedText>::adopt(
_builder.getPointerField(0 * ::capnp::POINTERS), kj::mv(value));
}
inline ::capnp::Orphan< ::capnp::compiler::LocatedText> Expression::Builder::disownEmbed() {
KJ_IREQUIRE(which() == Expression::EMBED,
"Must check which() before get()ing a union member.");
return ::capnp::_::PointerHelpers< ::capnp::compiler::LocatedText>::disown(
_builder.getPointerField(0 * ::capnp::POINTERS));
}
inline ::capnp::compiler::Expression::Param::Which Expression::Param::Reader::which() const {
return _reader.getDataField<Which>(0 * ::capnp::ELEMENTS);
}
......
......@@ -59,7 +59,7 @@ protected:
constexpr MmapDisposer mmapDisposer = MmapDisposer();
kj::Array<const char> mmapForRead(kj::StringPtr filename) {
kj::Array<const byte> mmapForRead(kj::StringPtr filename) {
int fd;
// We already established that the file exists, so this should not fail.
KJ_SYSCALL(fd = open(filename.cStr(), O_RDONLY), filename);
......@@ -90,14 +90,14 @@ kj::Array<const char> mmapForRead(kj::StringPtr filename) {
}
#endif // _WIN32, else
return kj::Array<const char>(
reinterpret_cast<const char*>(mapping), stats.st_size, mmapDisposer);
return kj::Array<const byte>(
reinterpret_cast<const byte*>(mapping), stats.st_size, mmapDisposer);
} else {
// This could be a stream of some sort, like a pipe. Fall back to read().
// TODO(cleanup): This does a lot of copies. Not sure I care.
kj::Vector<char> data(8192);
kj::Vector<byte> data(8192);
char buffer[4096];
byte buffer[4096];
for (;;) {
ssize_t n;
KJ_SYSCALL(n = read(fd, buffer, sizeof(buffer)));
......@@ -223,6 +223,8 @@ public:
kj::Maybe<Module&> loadModule(kj::StringPtr localName, kj::StringPtr sourceName);
kj::Maybe<Module&> loadModuleFromSearchPath(kj::StringPtr sourceName);
kj::Maybe<kj::Array<const byte>> readEmbed(kj::StringPtr localName, kj::StringPtr sourceName);
kj::Maybe<kj::Array<const byte>> readEmbedFromSearchPath(kj::StringPtr sourceName);
GlobalErrorReporter& getErrorReporter() { return errorReporter; }
private:
......@@ -245,7 +247,7 @@ public:
}
Orphan<ParsedFile> loadContent(Orphanage orphanage) override {
kj::Array<const char> content = mmapForRead(localName);
kj::Array<const char> content = mmapForRead(localName).releaseAsChars();
lineBreaks = nullptr; // In case loadContent() is called multiple times.
lineBreaks = lineBreaksSpace.construct(content);
......@@ -267,6 +269,14 @@ public:
}
}
kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) override {
if (embedPath.size() > 0 && embedPath[0] == '/') {
return loader.readEmbedFromSearchPath(embedPath.slice(1));
} else {
return loader.readEmbed(catPath(localName, embedPath), catPath(sourceName, embedPath));
}
}
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
auto& lines = *KJ_REQUIRE_NONNULL(lineBreaks,
"Can't report errors until loadContent() is called.");
......@@ -326,6 +336,33 @@ kj::Maybe<Module&> ModuleLoader::Impl::loadModuleFromSearchPath(kj::StringPtr so
return nullptr;
}
kj::Maybe<kj::Array<const byte>> ModuleLoader::Impl::readEmbed(
kj::StringPtr localName, kj::StringPtr sourceName) {
kj::String canonicalLocalName = canonicalizePath(localName);
kj::String canonicalSourceName = canonicalizePath(sourceName);
if (access(canonicalLocalName.cStr(), F_OK) < 0) {
// No such file.
return nullptr;
}
return mmapForRead(localName);
}
kj::Maybe<kj::Array<const byte>> ModuleLoader::Impl::readEmbedFromSearchPath(
kj::StringPtr sourceName) {
for (auto& search: searchPath) {
kj::String candidate = kj::str(search, "/", sourceName);
char* end = canonicalizePath(candidate.begin() + (candidate[0] == '/'));
KJ_IF_MAYBE(module, readEmbed(
kj::heapString(candidate.slice(0, end - candidate.begin())), sourceName)) {
return kj::mv(*module);
}
}
return nullptr;
}
// =======================================================================================
ModuleLoader::ModuleLoader(GlobalErrorReporter& errorReporter)
......
......@@ -21,6 +21,7 @@
#include "node-translator.h"
#include "parser.h" // only for generateGroupId()
#include <capnp/serialize.h>
#include <kj/debug.h>
#include <kj/arena.h>
#include <set>
......@@ -1215,6 +1216,7 @@ kj::Maybe<NodeTranslator::BrandedDecl> NodeTranslator::BrandScope::compileDeclEx
case Expression::BINARY:
case Expression::LIST:
case Expression::TUPLE:
case Expression::EMBED:
errorReporter.addErrorOn(source, "Expected name.");
return nullptr;
......@@ -1256,7 +1258,7 @@ kj::Maybe<NodeTranslator::BrandedDecl> NodeTranslator::BrandScope::compileDeclEx
case Expression::IMPORT: {
auto filename = source.getImport();
KJ_IF_MAYBE(decl, resolver.resolveImport(filename.getValue())) {
// Import is always a root scopee, so create a fresh BrandScope.
// Import is always a root scope, so create a fresh BrandScope.
return BrandedDecl(*decl, kj::refcounted<BrandScope>(
errorReporter, decl->id, decl->genericParamCount, *decl->resolver), source);
} else {
......@@ -2422,6 +2424,8 @@ static kj::StringTree expressionStringTree(Expression::Reader exp) {
return kj::strTree('.', exp.getAbsoluteName().getValue());
case Expression::IMPORT:
return kj::strTree("import ", stringLiteral(exp.getImport().getValue()));
case Expression::EMBED:
return kj::strTree("embed ", stringLiteral(exp.getEmbed().getValue()));
case Expression::LIST: {
auto list = exp.getList();
......@@ -2547,6 +2551,10 @@ void NodeTranslator::compileValue(Expression::Reader source, schema::Type::Reade
return translator.readConstant(name, isBootstrap);
}
kj::Maybe<kj::Array<const byte>> readEmbed(LocatedText::Reader filename) override {
return translator.readEmbed(filename);
}
private:
NodeTranslator& translator;
bool isBootstrap;
......@@ -2750,6 +2758,62 @@ Orphan<DynamicValue> ValueTranslator::compileValueInner(Expression::Reader src,
return nullptr;
}
case Expression::EMBED:
KJ_IF_MAYBE(data, resolver.readEmbed(src.getEmbed())) {
switch (type.which()) {
case schema::Type::TEXT: {
// Sadly, we need to make a copy to add the NUL terminator.
auto text = orphanage.newOrphan<Text>(data->size());
memcpy(text.get().begin(), data->begin(), data->size());
return kj::mv(text);
}
case schema::Type::DATA:
// TODO(perf): It would arguably be neat to use orphanage.referenceExternalData(),
// since typically the data is mmap()ed and this would avoid forcing a large file
// to become memory-resident. However, we'd have to figure out who should own the
// Array<byte>. Also, we'd have to deal with the possibility of misaligned data --
// though arguably in that case we know it's not mmap()ed so whatever. One more
// thing: it would be neat to be able to reference text blobs this way too, if only
// we could rely on the assumption that as long as the data doesn't end on a page
// boundary, it will be zero-padded, thus giving us our NUL terminator (4095/4096 of
// the time), but this seems to require documenting constraints on the underlying
// file-reading interfaces. Hm.
return orphanage.newOrphanCopy(Data::Reader(*data));
case schema::Type::STRUCT: {
// We will almost certainly
if (data->size() % sizeof(word) != 0) {
errorReporter.addErrorOn(src,
"Embedded file is not a valid Cap'n Proto message.");
return nullptr;
}
kj::Array<word> copy;
kj::ArrayPtr<const word> words;
if (reinterpret_cast<uintptr_t>(data->begin()) % sizeof(void*) == 0) {
// Hooray, data is aligned.
words = kj::ArrayPtr<const word>(
reinterpret_cast<const word*>(data->begin()),
data->size() / sizeof(word));
} else {
// Ugh, data not aligned. Make a copy.
copy = kj::heapArray<word>(data->size() / sizeof(word));
memcpy(copy.begin(), data->begin(), data->size());
words = copy;
}
ReaderOptions options;
options.traversalLimitInWords = kj::maxValue;
options.nestingLimit = kj::maxValue;
FlatArrayMessageReader reader(words, options);
return orphanage.newOrphanCopy(reader.getRoot<DynamicStruct>(type.asStruct()));
}
default:
errorReporter.addErrorOn(src,
"Embeds can only be used when Text, Data, or a struct is expected.");
return nullptr;
}
} else {
return nullptr;
}
case Expression::POSITIVE_INT:
return src.getPositiveInt();
......@@ -2981,6 +3045,16 @@ kj::Maybe<DynamicValue::Reader> NodeTranslator::readConstant(
return constValue;
}
kj::Maybe<kj::Array<const byte>> NodeTranslator::readEmbed(LocatedText::Reader filename) {
KJ_IF_MAYBE(data, resolver.readEmbed(filename.getValue())) {
return kj::mv(*data);
} else {
errorReporter.addErrorOn(filename,
kj::str("Couldn't read file for embed: ", filename.getValue()));
return nullptr;
}
}
Orphan<List<schema::Annotation>> NodeTranslator::compileAnnotationApplications(
List<Declaration::AnnotationApplication>::Reader annotations,
kj::StringPtr targetsFlagName) {
......
......@@ -110,12 +110,13 @@ public:
virtual kj::Maybe<ResolvedDecl> resolveImport(kj::StringPtr name) = 0;
// Get the ID of an imported file given the import path.
virtual kj::Maybe<kj::Array<const byte>> readEmbed(kj::StringPtr name) = 0;
// Read and return the contents of a file for an `embed` expression.
virtual kj::Maybe<Type> resolveBootstrapType(schema::Type::Reader type, Schema scope) = 0;
// Compile a schema::Type into a Type whose dependencies may safely be traversed via the schema
// API. These dependencies may have only bootstrap schemas. Returns null if the type could not
// be constructed due to already-reported errors.
//
// `scope` is the schema
};
NodeTranslator(Resolver& resolver, ErrorReporter& errorReporter,
......@@ -265,6 +266,9 @@ private:
// Get the value of the given constant. May return null if some error occurs, which will already
// have been reported.
kj::Maybe<kj::Array<const byte>> readEmbed(LocatedText::Reader filename);
// Read a raw file for embedding.
Orphan<List<schema::Annotation>> compileAnnotationApplications(
List<Declaration::AnnotationApplication>::Reader annotations,
kj::StringPtr targetsFlagName);
......@@ -275,6 +279,7 @@ public:
class Resolver {
public:
virtual kj::Maybe<DynamicValue::Reader> resolveConstant(Expression::Reader name) = 0;
virtual kj::Maybe<kj::Array<const byte>> readEmbed(LocatedText::Reader filename) = 0;
};
ValueTranslator(Resolver& resolver, ErrorReporter& errorReporter, Orphanage orphanage)
......
......@@ -552,6 +552,15 @@ CapnpParser::CapnpParser(Orphanage orphanageParam, ErrorReporter& errorReporterP
filename.copyTo(builder.initImport());
return result;
}),
p::transformWithLocation(p::sequence(keyword("embed"), stringLiteral),
[this](kj::parse::Span<List<Token>::Reader::Iterator> location,
Located<Text::Reader>&& filename) -> Orphan<Expression> {
auto result = orphanage.newOrphan<Expression>();
auto builder = result.get();
initLocation(location, builder);
filename.copyTo(builder.initEmbed());
return result;
}),
p::transformWithLocation(p::sequence(op("."), identifier),
[this](kj::parse::Span<List<Token>::Reader::Iterator> location,
Located<Text::Reader>&& name) -> Orphan<Expression> {
......
......@@ -26,6 +26,7 @@
#include <kj/compat/gtest.h>
#include "test-util.h"
#include "schema-lite.h"
#include "serialize-packed.h"
namespace capnp {
namespace _ { // private
......@@ -1659,6 +1660,26 @@ TEST(Encoding, GlobalConstants) {
}
}
TEST(Encoding, Embeds) {
{
kj::ArrayInputStream input(test::EMBEDDED_DATA);
PackedMessageReader reader(input);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
{
MallocMessageBuilder builder;
auto root = builder.getRoot<TestAllTypes>();
initTestMessage(root);
kj::StringPtr text = test::EMBEDDED_TEXT;
EXPECT_EQ(kj::str(root, '\n').size(), text.size());
}
{
checkTestMessage(test::EMBEDDED_STRUCT);
}
}
TEST(Encoding, HasEmptyStruct) {
MallocMessageBuilder message;
auto root = message.initRoot<test::TestAnyPointer>();
......
......@@ -110,6 +110,14 @@ public:
}
}
kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) override {
KJ_IF_MAYBE(importedFile, file->import(embedPath)) {
return importedFile->get()->readContent().releaseAsBytes();
} else {
return nullptr;
}
}
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
auto& lines = lineBreaks.get(
[](kj::SpaceFor<kj::Vector<uint>>& space) {
......
......@@ -723,6 +723,10 @@ const derivedConstant :TestAllTypes = (
const genericConstant :TestGenerics(TestAllTypes, Text) =
(foo = (int16Field = 123), rev = (foo = "text", rev = (foo = (int16Field = 321))));
const embeddedData :Data = embed "testdata/packed";
const embeddedText :Text = embed "testdata/short.txt";
const embeddedStruct :TestAllTypes = embed "testdata/binary";
interface TestInterface {
foo @0 (i :UInt32, j :Bool) -> (x :Text);
bar @1 () -> ();
......
......@@ -149,3 +149,6 @@ struct UseGeneric {
doubleBind @2 :Generic(Text, Data)(Data, Text);
primitiveBinding @3 :Generic(Text, Int32);
}
const embedBadType :UInt32 = embed "binary";
const embedNoSuchFile :Data = embed "no-such-file";
......@@ -54,3 +54,5 @@ file:147:14-27: error: Not enough generic parameters.
file:148:15-47: error: Too many generic parameters.
file:149:18-49: error: Double-application of generic parameters.
file:150:38-43: error: Sorry, only pointer types can be used as generic parameters.
file:153:30-44: error: Embeds can only be used when Text, Data, or a struct is expected.
file:154:37-51: error: Couldn't read file for embed: no-such-file
......@@ -333,5 +333,40 @@ TEST(Array, Map) {
EXPECT_STREQ("bcde", str(bar).cStr());
}
TEST(Array, ReleaseAsBytesOrChars) {
{
Array<char> chars = kj::heapArray<char>("foo", 3);
Array<byte> bytes = chars.releaseAsBytes();
EXPECT_TRUE(chars == nullptr);
ASSERT_EQ(3, bytes.size());
EXPECT_EQ('f', bytes[0]);
EXPECT_EQ('o', bytes[1]);
EXPECT_EQ('o', bytes[2]);
chars = bytes.releaseAsChars();
EXPECT_TRUE(bytes == nullptr);
ASSERT_EQ(3, chars.size());
EXPECT_EQ('f', chars[0]);
EXPECT_EQ('o', chars[1]);
EXPECT_EQ('o', chars[2]);
}
{
Array<const char> chars = kj::heapArray<char>("foo", 3);
Array<const byte> bytes = chars.releaseAsBytes();
EXPECT_TRUE(chars == nullptr);
ASSERT_EQ(3, bytes.size());
EXPECT_EQ('f', bytes[0]);
EXPECT_EQ('o', bytes[1]);
EXPECT_EQ('o', bytes[2]);
chars = bytes.releaseAsChars();
EXPECT_TRUE(bytes == nullptr);
ASSERT_EQ(3, chars.size());
EXPECT_EQ('f', chars[0]);
EXPECT_EQ('o', chars[1]);
EXPECT_EQ('o', chars[2]);
}
}
} // namespace
} // namespace kj
......@@ -191,6 +191,27 @@ public:
inline ArrayPtr<const char> asChars() const { return asPtr().asChars(); }
inline ArrayPtr<PropagateConst<T, char>> asChars() { return asPtr().asChars(); }
inline Array<PropagateConst<T, byte>> releaseAsBytes() {
// Like asBytes() but transfers ownership.
static_assert(sizeof(T) == sizeof(byte),
"releaseAsBytes() only possible on arrays with byte-size elements (e.g. chars).");
Array<PropagateConst<T, byte>> result(
reinterpret_cast<PropagateConst<T, byte>*>(ptr), size_, *disposer);
ptr = nullptr;
size_ = 0;
return result;
}
inline Array<PropagateConst<T, char>> releaseAsChars() {
// Like asChars() but transfers ownership.
static_assert(sizeof(T) == sizeof(PropagateConst<T, char>),
"releaseAsChars() only possible on arrays with char-size elements (e.g. bytes).");
Array<PropagateConst<T, char>> result(
reinterpret_cast<PropagateConst<T, char>*>(ptr), size_, *disposer);
ptr = nullptr;
size_ = 0;
return result;
}
inline bool operator==(decltype(nullptr)) const { return size_ == 0; }
inline bool operator!=(decltype(nullptr)) const { return size_ != 0; }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment