Commit f08b2f90 authored by Kenton Varda's avatar Kenton Varda

Complete C++ .capnp parser. Also, add a pretty-printing function for arbitrary…

Complete C++ .capnp parser.  Also, add a pretty-printing function for arbitrary Cap'n Proto objects which is like the existing stringification but uses whitespace (newlines an indentation) to make the structures more readable.
parent 27b8137f
......@@ -126,6 +126,7 @@ includecapnp_HEADERS = \
src/capnp/schema.h \
src/capnp/schema-loader.h \
src/capnp/dynamic.h \
src/capnp/pretty-print.h \
src/capnp/serialize.h \
src/capnp/serialize-packed.h \
src/capnp/generated-header-support.h
......
......@@ -23,6 +23,7 @@
#include "lexer.h"
#include "parser.h"
#include <capnp/pretty-print.h>
#include <kj/vector.h>
#include <kj/io.h>
#include <unistd.h>
......@@ -40,33 +41,38 @@ public:
int main(int argc, char* argv[]) {
// Eventually this will be capnpc. For now it's just a dummy program that tests parsing.
// kj::Vector<char> input;
// char buffer[4096];
// for (;;) {
// ssize_t n;
// KJ_SYSCALL(n = read(STDIN_FILENO, buffer, sizeof(buffer)));
// if (n == 0) {
// break;
// }
// input.addAll(buffer, buffer + n);
// }
//
// KJ_DBG(input);
// This input triggers a data corruption bug. Fix it before doing anything else!
kj::StringPtr input = "@0xfa974d18d718428e; const x :Int32 = 1;";
kj::Vector<char> input;
char buffer[4096];
for (;;) {
ssize_t n;
KJ_SYSCALL(n = read(STDIN_FILENO, buffer, sizeof(buffer)));
if (n == 0) {
break;
}
input.addAll(buffer, buffer + n);
}
CoutErrorReporter errorReporter;
std::cout << "=========================================================================\n"
<< "lex\n"
<< "========================================================================="
<< std::endl;
capnp::MallocMessageBuilder lexerArena;
auto lexedFile = lexerArena.initRoot<capnp::compiler::LexedStatements>();
capnp::compiler::lex(input, lexedFile, errorReporter);
KJ_DBG(lexedFile);
std::cout << capnp::prettyPrint(lexedFile).cStr() << std::endl;
std::cout << "=========================================================================\n"
<< "parse\n"
<< "========================================================================="
<< std::endl;
capnp::MallocMessageBuilder parserArena;
auto parsedFile = parserArena.initRoot<capnp::compiler::ParsedFile>();
capnp::compiler::parseFile(lexedFile.getStatements(), parsedFile, errorReporter);
KJ_DBG(parsedFile);
std::cout << capnp::prettyPrint(parsedFile).cStr() << std::endl;
return 0;
}
......@@ -22,6 +22,11 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@0xc56be168dcbbc3c6;
# The structures in this file correspond to the AST of the Cap'n Proto schema language.
#
# This file is intended to be used internally by capnpc. Mostly, it is useful because it is more
# convenient that defining data classes in C++, particularly where variant types (unions) are
# needed. Over time, this file may change in backwards-incompatible ways.
using Cxx = import "/capnp/c++.capnp";
......@@ -121,7 +126,11 @@ struct Declaration {
annotations @5 :List(AnnotationApplication);
struct AnnotationApplication {
name @0 :DeclName;
value @1 :ValueExpression;
value @1 union {
none @2 :Void; # None specified; implies void value.
expression @3 :ValueExpression;
}
}
startByte @18 :UInt32;
......@@ -137,6 +146,7 @@ struct Declaration {
structDecl @11 :Struct;
fieldDecl @12 :Field;
unionDecl @13 :Union;
groupDecl @23 :Group;
interfaceDecl @14 :Interface;
methodDecl @15 :Method;
annotationDecl @16 :Annotation;
......@@ -178,13 +188,19 @@ struct Declaration {
struct Method {
params @0 :List(Param);
struct Param {
type @0 :TypeExpression;
annotations @4 :List(AnnotationApplication);
defaultValue @1 union {
none @2 :Void;
value @3 :ValueExpression;
name @0 :LocatedText; # If null, param failed to parse.
type @1 :TypeExpression;
annotations @2 :List(AnnotationApplication);
defaultValue @3 union {
none @4 :Void;
value @5 :ValueExpression;
}
}
returnType @1 union {
none @2 :Void; # No return type specified; implied Void.
expression @3 :TypeExpression;
}
}
struct Annotation {
......
This diff is collapsed.
......@@ -106,6 +106,7 @@ public:
Parser<Orphan<Declaration::AnnotationApplication>> annotation;
Parser<Orphan<LocatedInteger>> uid;
Parser<Orphan<LocatedInteger>> ordinal;
Parser<Orphan<Declaration::Method::Param>> param;
DeclParser usingDecl;
DeclParser constDecl;
......@@ -114,6 +115,7 @@ public:
DeclParser structDecl;
DeclParser fieldDecl;
DeclParser unionDecl;
DeclParser groupDecl;
DeclParser interfaceDecl;
DeclParser methodDecl;
DeclParser paramDecl;
......
......@@ -104,21 +104,13 @@ BuilderFor<DynamicTypeFor<FromBuilder<T>>> toDynamic(T&& value);
template <typename T>
DynamicTypeFor<TypeIfEnum<T>> toDynamic(T&& value);
template <typename T> struct EnableIfNotDynamic_ { typedef T Type; };
template <> struct EnableIfNotDynamic_<DynamicUnion> {};
template <> struct EnableIfNotDynamic_<DynamicStruct> {};
template <> struct EnableIfNotDynamic_<DynamicList> {};
template <> struct EnableIfNotDynamic_<DynamicValue> {};
template <typename T>
using EnableIfNotDynamic = typename EnableIfNotDynamic_<T>::Type;
// -------------------------------------------------------------------
class DynamicEnum {
public:
DynamicEnum() = default;
template <typename T, typename = TypeIfEnum<T>>
template <typename T, typename = kj::EnableIf<kind<T>() == Kind::ENUM>>
inline DynamicEnum(T&& value): DynamicEnum(toDynamic(value)) {}
template <typename T>
......@@ -271,7 +263,7 @@ public:
Reader() = default;
template <typename T, typename = EnableIfNotDynamic<FromReader<T>>>
template <typename T, typename = kj::EnableIf<kind<FromReader<T>>() == Kind::STRUCT>>
inline Reader(T&& value): Reader(toDynamic(value)) {}
template <typename T>
......@@ -322,7 +314,7 @@ public:
Builder() = default;
template <typename T, typename = EnableIfNotDynamic<FromBuilder<T>>>
template <typename T, typename = kj::EnableIf<kind<FromBuilder<T>>() == Kind::STRUCT>>
inline Builder(T&& value): Builder(toDynamic(value)) {}
template <typename T>
......@@ -433,7 +425,7 @@ public:
Reader() = default;
template <typename T, typename = EnableIfNotDynamic<FromReader<T>>>
template <typename T, typename = kj::EnableIf<kind<FromReader<T>>() == Kind::LIST>>
inline Reader(T&& value): Reader(toDynamic(value)) {}
template <typename T>
......@@ -472,7 +464,7 @@ public:
Builder() = default;
template <typename T, typename = EnableIfNotDynamic<FromBuilder<T>>>
template <typename T, typename = kj::EnableIf<kind<FromBuilder<T>>() == Kind::LIST>>
inline Builder(T&& value): Builder(toDynamic(value)) {}
template <typename T>
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNP_PRETTY_PRINT_H_
#define CAPNP_PRETTY_PRINT_H_
#include "dynamic.h"
#include <kj/string.h>
namespace capnp {
kj::String prettyPrint(DynamicStruct::Reader value);
kj::String prettyPrint(DynamicStruct::Builder value);
kj::String prettyPrint(DynamicList::Reader value);
kj::String prettyPrint(DynamicList::Builder value);
// Print the given Cap'n Proto struct or list with nice indentation. Note that you can pass any
// struct or list reader or builder type to this method, since they can be implicitly converted
// to one of the dynamic types.
//
// If you don't want indentation, just use the value's KJ stringifier (e.g. pass it to kj::str(),
// any of the KJ debug macros, etc.).
} // namespace capnp
#endif // PRETTY_PRINT_H_
......@@ -46,6 +46,7 @@
#include "message.h"
#include "dynamic.h"
#include "pretty-print.h"
#include <kj/debug.h>
#include <gtest/gtest.h>
#include "test-util.h"
......@@ -60,7 +61,7 @@ namespace capnp {
namespace _ { // private
namespace {
TEST(Stringify, DebugString) {
TEST(Stringify, KjStringification) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
......@@ -142,6 +143,205 @@ TEST(Stringify, DebugString) {
kj::str(root));
}
TEST(Stringify, PrettyPrint) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
EXPECT_EQ("()", prettyPrint(root));
initTestMessage(root);
EXPECT_EQ(
"( boolField = true,\n"
" int8Field = -123,\n"
" int16Field = -12345,\n"
" int32Field = -12345678,\n"
" int64Field = -123456789012345,\n"
" uInt8Field = 234,\n"
" uInt16Field = 45678,\n"
" uInt32Field = 3456789012,\n"
" uInt64Field = 12345678901234567890,\n"
" float32Field = 1234.5,\n"
" float64Field = -1.23e47,\n"
" textField = \"foo\",\n"
" dataField = \"bar\",\n"
" structField = (\n"
" boolField = true,\n"
" int8Field = -12,\n"
" int16Field = 3456,\n"
" int32Field = -78901234,\n"
" int64Field = 56789012345678,\n"
" uInt8Field = 90,\n"
" uInt16Field = 1234,\n"
" uInt32Field = 56789012,\n"
" uInt64Field = 345678901234567890,\n"
" float32Field = -1.25e-10,\n"
" float64Field = 345,\n"
" textField = \"baz\",\n"
" dataField = \"qux\",\n"
" structField = (\n"
" textField = \"nested\",\n"
" structField = (textField = \"really nested\")),\n"
" enumField = baz,\n"
" voidList = [void, void, void],\n"
" boolList = [false, true, false, true, true],\n"
" int8List = [12, -34, -128, 127],\n"
" int16List = [1234, -5678, -32768, 32767],\n"
" int32List = [12345678, -90123456, -2147483648, 2147483647],\n"
" int64List = [123456789012345, -678901234567890, "
"-9223372036854775808, 9223372036854775807],\n"
" uInt8List = [12, 34, 0, 255],\n"
" uInt16List = [1234, 5678, 0, 65535],\n"
" uInt32List = [12345678, 90123456, 0, 4294967295],\n"
" uInt64List = [123456789012345, 678901234567890, 0, 18446744073709551615],\n"
" float32List = [0, 1234567, 1e37, -1e37, 1e-37, -1e-37],\n"
" float64List = [0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306],\n"
" textList = [\"quux\", \"corge\", \"grault\"],\n"
" dataList = [\"garply\", \"waldo\", \"fred\"],\n"
" structList = [\n"
" (textField = \"x structlist 1\"),\n"
" (textField = \"x structlist 2\"),\n"
" (textField = \"x structlist 3\")],\n"
" enumList = [qux, bar, grault]),\n"
" enumField = corge,\n"
" voidList = [void, void, void, void, void, void],\n"
" boolList = [true, false, false, true],\n"
" int8List = [111, -111],\n"
" int16List = [11111, -11111],\n"
" int32List = [111111111, -111111111],\n"
" int64List = [1111111111111111111, -1111111111111111111],\n"
" uInt8List = [111, 222],\n"
" uInt16List = [33333, 44444],\n"
" uInt32List = [3333333333],\n"
" uInt64List = [11111111111111111111],\n"
" float32List = [5555.5, inf, -inf, nan],\n"
" float64List = [7777.75, inf, -inf, nan],\n"
" textList = [\"plugh\", \"xyzzy\", \"thud\"],\n"
" dataList = [\"oops\", \"exhausted\", \"rfc3092\"],\n"
" structList = [\n"
" (textField = \"structlist 1\"),\n"
" (textField = \"structlist 2\"),\n"
" (textField = \"structlist 3\")],\n"
" enumList = [foo, garply])",
prettyPrint(root));
}
TEST(Stringify, PrettyPrintAdvanced) {
MallocMessageBuilder builder;
{
auto root = builder.initRoot<TestAllTypes>();
auto list = root.initStructList(3);
list[0].setInt32Field(123);
list[0].setTextField("foo");
list[1].setInt32Field(456);
list[1].setTextField("bar");
list[2].setInt32Field(789);
list[2].setTextField("baz");
EXPECT_EQ(
"(structList = [\n"
" ( int32Field = 123,\n"
" textField = \"foo\"),\n"
" ( int32Field = 456,\n"
" textField = \"bar\"),\n"
" ( int32Field = 789,\n"
" textField = \"baz\")])",
prettyPrint(root));
root.setInt32Field(55);
EXPECT_EQ(
"( int32Field = 55,\n"
" structList = [\n"
" ( int32Field = 123,\n"
" textField = \"foo\"),\n"
" ( int32Field = 456,\n"
" textField = \"bar\"),\n"
" ( int32Field = 789,\n"
" textField = \"baz\")])",
prettyPrint(root));
}
{
auto root = builder.initRoot<test::TestLists>();
auto ll = root.initInt32ListList(3);
ll.set(0, {123, 456, 789});
ll.set(1, {234, 567, 891});
ll.set(2, {345, 678, 912});
EXPECT_EQ(
"[ [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]]",
prettyPrint(ll));
EXPECT_EQ(
"(int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
root.initList8(0);
EXPECT_EQ(
"( list8 = [],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
auto l8 = root.initList8(1);
l8[0].setF(12);
EXPECT_EQ(
"( list8 = [(f = 12)],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
l8 = root.initList8(2);
l8[0].setF(12);
l8[1].setF(34);
EXPECT_EQ(
"( list8 = [\n"
" (f = 12),\n"
" (f = 34)],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
}
{
auto root = builder.initRoot<test::TestStructUnion>();
auto s = root.getUn().initAllTypes();
EXPECT_EQ(
"(un = allTypes())",
prettyPrint(root));
s.setInt32Field(123);
EXPECT_EQ(
"(un = allTypes(int32Field = 123))",
prettyPrint(root));
s.setTextField("foo");
EXPECT_EQ(
"(un = allTypes(\n"
" int32Field = 123,\n"
" textField = \"foo\"))",
prettyPrint(root));
}
}
TEST(Stringify, Unions) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestUnion>();
......
......@@ -33,8 +33,63 @@ namespace {
static const char HEXDIGITS[] = "0123456789abcdef";
class Indent {
public:
explicit Indent(bool enable): amount(enable ? 1 : 0), hasPrefix(false), isFirst(true) {}
enum ItemType {
INLINE, // Items are simple values that don't need to be on their own lines.
PREFIXED, // Each item is on a new line with some prefix attached (e.g. a field name).
STANDALONE // Each item is on a new line with no prefix.
};
Indent startItem(std::ostream& os, ItemType type) {
// Start a new item (list element or struct field) within the parent value, writing a newline
// and indentation if necessary.
if (isFirst) {
isFirst = false;
if (type == INLINE || amount == 0) {
return Indent(amount, true);
}
if (hasPrefix) {
os << '\n';
for (uint i = 0; i < amount; i++) {
os << " ";
}
} else {
os << ' ';
}
return Indent(amount + 1, type == PREFIXED);
} else {
if (type == INLINE || amount == 0) {
os << ", ";
return Indent(amount, true);
}
os << ",\n";
for (uint i = 0; i < amount; i++) {
os << " ";
}
return Indent(amount + 1, type == PREFIXED);
}
}
Indent withPrefix() {
return Indent(amount, true);
}
private:
Indent(uint amount, bool hasPrefix): amount(amount), hasPrefix(hasPrefix), isFirst(true) {}
uint amount;
bool hasPrefix;
bool isFirst;
};
static void print(std::ostream& os, const DynamicValue::Reader& value,
schema::Type::Body::Which which, bool alreadyParenthesized = false) {
schema::Type::Body::Which which, Indent indent,
bool alreadyParenthesized = false) {
// Print an arbitrary message via the dynamic API by
// iterating over the schema. Look at the handling
// of STRUCT in particular.
......@@ -103,15 +158,24 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
}
case DynamicValue::LIST: {
os << "[";
bool first = true;
auto listValue = value.as<DynamicList>();
// If the members are not primitives and there is more than one member, arrange for
// identation.
Indent::ItemType itemType;
switch (listValue.getSchema().whichElementType()) {
case schema::Type::Body::STRUCT_TYPE:
case schema::Type::Body::LIST_TYPE:
itemType = listValue.size() <= 1 ? Indent::INLINE : Indent::STANDALONE;
break;
default:
itemType = Indent::INLINE;
break;
}
for (auto element: listValue) {
if (first) {
first = false;
} else {
os << ", ";
}
print(os, element, listValue.getSchema().whichElementType());
print(os, element, listValue.getSchema().whichElementType(),
indent.startItem(os, itemType));
}
os << "]";
break;
......@@ -129,28 +193,41 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
case DynamicValue::STRUCT: {
if (!alreadyParenthesized) os << "(";
auto structValue = value.as<DynamicStruct>();
bool first = true;
Indent::ItemType itemType = Indent::INLINE;
// If there is more than one member, arrange for indentation.
bool sawOne = false;
for (auto member: structValue.getSchema().getMembers()) {
if (structValue.has(member)) {
if (first) {
first = false;
if (sawOne) {
itemType = Indent::PREFIXED;
break;
} else {
os << ", ";
sawOne = true;
}
}
}
// Print the members.
for (auto member: structValue.getSchema().getMembers()) {
if (structValue.has(member)) {
Indent subIndent = indent.startItem(os, itemType);
os << member.getProto().getName().cStr() << " = ";
auto memberBody = member.getProto().getBody();
switch (memberBody.which()) {
case schema::StructNode::Member::Body::UNION_MEMBER:
print(os, structValue.get(member), schema::Type::Body::VOID_TYPE);
print(os, structValue.get(member), schema::Type::Body::VOID_TYPE, subIndent);
break;
case schema::StructNode::Member::Body::FIELD_MEMBER:
print(os, structValue.get(member),
memberBody.getFieldMember().getType().getBody().which());
memberBody.getFieldMember().getType().getBody().which(), subIndent);
break;
}
}
}
if (!alreadyParenthesized) os << ")";
break;
}
......@@ -160,7 +237,7 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
os << tag->getProto().getName().cStr() << "(";
print(os, unionValue.get(),
tag->getProto().getBody().getFieldMember().getType().getBody().which(),
true /* alreadyParenthesized */);
indent.withPrefix(), true /* alreadyParenthesized */);
os << ")";
} else {
// Unknown union member; must have come from newer
......@@ -182,13 +259,30 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
kj::String stringify(DynamicValue::Reader value) {
std::stringstream out;
print(out, value, schema::Type::Body::STRUCT_TYPE);
print(out, value, schema::Type::Body::STRUCT_TYPE, Indent(false));
auto content = out.str();
return kj::heapString(content.data(), content.size());
}
} // namespace
kj::String prettyPrint(DynamicStruct::Reader value) {
std::stringstream out;
print(out, value, schema::Type::Body::STRUCT_TYPE, Indent(true));
auto content = out.str();
return kj::heapString(content.data(), content.size());
}
kj::String prettyPrint(DynamicList::Reader value) {
std::stringstream out;
print(out, value, schema::Type::Body::LIST_TYPE, Indent(true));
auto content = out.str();
return kj::heapString(content.data(), content.size());
}
kj::String prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::String prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
kj::String KJ_STRINGIFY(const DynamicValue::Reader& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicValue::Builder& value) { return stringify(value.asReader()); }
kj::String KJ_STRINGIFY(DynamicEnum value) { return stringify(value); }
......
......@@ -36,6 +36,19 @@ TEST(String, Str) {
EXPECT_EQ("foo", str('f', 'o', 'o'));
}
TEST(String, StartsEndsWith) {
EXPECT_TRUE(StringPtr("foobar").startsWith("foo"));
EXPECT_FALSE(StringPtr("foobar").startsWith("bar"));
EXPECT_FALSE(StringPtr("foobar").endsWith("foo"));
EXPECT_TRUE(StringPtr("foobar").endsWith("bar"));
EXPECT_FALSE(StringPtr("fo").startsWith("foo"));
EXPECT_FALSE(StringPtr("fo").endsWith("foo"));
EXPECT_TRUE(StringPtr("foobar").startsWith(""));
EXPECT_TRUE(StringPtr("foobar").endsWith(""));
}
} // namespace
} // namespace _ (private)
} // namespace kj
......@@ -81,6 +81,9 @@ public:
// A string slice is only NUL-terminated if it is a suffix, so slice() has a one-parameter
// version that assumes end = size().
inline bool startsWith(const StringPtr& other) const;
inline bool endsWith(const StringPtr& other) const;
private:
inline StringPtr(ArrayPtr<const char> content): content(content) {}
......@@ -132,6 +135,9 @@ public:
inline bool operator==(const StringPtr& other) const { return StringPtr(*this) == other; }
inline bool operator!=(const StringPtr& other) const { return !(*this == other); }
inline bool startsWith(const StringPtr& other) const { return StringPtr(*this).startsWith(other);}
inline bool endsWith(const StringPtr& other) const { return StringPtr(*this).endsWith(other); }
private:
Array<char> content;
};
......@@ -353,6 +359,15 @@ inline ArrayPtr<const char> StringPtr::slice(size_t start, size_t end) const {
return content.slice(start, end);
}
inline bool StringPtr::startsWith(const StringPtr& other) const {
return other.content.size() <= content.size() &&
memcmp(content.begin(), other.content.begin(), other.size()) == 0;
}
inline bool StringPtr::endsWith(const StringPtr& other) const {
return other.content.size() <= content.size() &&
memcmp(end() - other.size(), other.content.begin(), other.size()) == 0;
}
inline String::operator ArrayPtr<char>() {
return content == nullptr ? ArrayPtr<char>(nullptr) : content.slice(0, content.size() - 1);
}
......
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