aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/tanya/container/string.d283
1 files changed, 283 insertions, 0 deletions
diff --git a/source/tanya/container/string.d b/source/tanya/container/string.d
new file mode 100644
index 0000000..75298eb
--- /dev/null
+++ b/source/tanya/container/string.d
@@ -0,0 +1,283 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * UTF-8 string.
+ *
+ * Copyright: Eugene Wissner 2017.
+ * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
+ * Mozilla Public License, v. 2.0).
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ */
+module tanya.container.string;
+
+import core.checkedint;
+import core.exception;
+import core.stdc.string;
+import std.algorithm.comparison;
+import tanya.memory;
+
+/**
+ * UTF-8 string.
+ */
+struct String
+{
+ private size_t length_;
+ private char* data;
+ private size_t capacity_;
+
+ invariant
+ {
+ assert(length_ <= capacity_);
+ }
+
+ /**
+ * Params:
+ * str = Initial string.
+ * allocator = Allocator.
+ */
+ this(const(char)[] str, shared Allocator allocator = defaultAllocator)
+ nothrow @trusted @nogc
+ {
+ this(allocator);
+ reserve(str.length);
+ length_ = str.length;
+ memcpy(data, str.ptr, length_);
+ }
+
+ /// Ditto.
+ this(const(wchar)[] str, shared Allocator allocator = defaultAllocator)
+ nothrow @trusted @nogc
+ {
+ this(allocator);
+
+ bool overflow;
+ auto size = mulu(str.length, 4, overflow);
+ assert(!overflow);
+
+ reserve(size);
+
+ auto s = data;
+ auto sourceLength = str.length;
+ for (auto c = str.ptr; sourceLength != 0; ++c, --sourceLength)
+ {
+ if (*c < 0x80)
+ {
+ *s++ = *c & 0x7f;
+ length_ += 1;
+ }
+ else if (*c < 0x800)
+ {
+ *s++ = 0xc0 | (*c >> 6) & 0xff;
+ *s++ = 0x80 | (*c & 0x3f);
+ length_ += 2;
+ }
+ else if (*c < 0xd800 || *c - 0xe000 < 0x2000)
+ {
+ *s++ = 0xe0 | (*c >> 12) & 0xff;
+ *s++ = 0x80 | ((*c >> 6) & 0x3f);
+ *s++ = 0x80 | (*c & 0x3f);
+ length_ += 3;
+ }
+ else if ((*c - 0xd800) < 2048 && sourceLength > 0 && *(c + 1) - 0xdc00 < 0x400)
+ { // Surrogate pair
+ dchar d = (*c - 0xd800) | ((*c++ - 0xdc00) >> 10);
+
+ *s++ = 0xf0 | (d >> 18);
+ *s++ = 0x80 | ((d >> 12) & 0x3f);
+ *s++ = 0x80 | ((d >> 6) & 0x3f);
+ *s++ = 0x80 | (d & 0x3f);
+ --sourceLength;
+ length_ += 4;
+ }
+ }
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto s = String("\u10437"w);
+ assert("\u10437" == s.get());
+ }
+
+ /// Ditto.
+ this(const(dchar)[] str, shared Allocator allocator = defaultAllocator)
+ nothrow @trusted @nogc
+ {
+ this(allocator);
+
+ bool overflow;
+ auto size = mulu(str.length, 4, overflow);
+ assert(!overflow);
+
+ reserve(size);
+
+ auto s = data;
+ foreach (c; str)
+ {
+ if (c < 0x80)
+ {
+ *s++ = c & 0x7f;
+ length_ += 1;
+ }
+ else if (c < 0x800)
+ {
+ *s++ = 0xc0 | (c >> 6) & 0xff;
+ *s++ = 0x80 | (c & 0x3f);
+ length_ += 2;
+ }
+ else if (c < 0xd800 || c - 0xe000 < 0x2000)
+ {
+ *s++ = 0xe0 | (c >> 12) & 0xff;
+ *s++ = 0x80 | ((c >> 6) & 0x3f);
+ *s++ = 0x80 | (c & 0x3f);
+ length_ += 3;
+ }
+ else if (c - 0x10000 < 0x100000)
+ {
+ *s++ = 0xf0 | (c >> 18);
+ *s++ = 0x80 | ((c >> 12) & 0x3f);
+ *s++ = 0x80 | ((c >> 6) & 0x3f);
+ *s++ = 0x80 | (c & 0x3f);
+ length_ += 4;
+ }
+ }
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ auto s = String("Отказаться от вина - в этом страшная вина."d);
+ assert("Отказаться от вина - в этом страшная вина." == s.get());
+ }
+
+ /// Ditto.
+ this(shared Allocator allocator) pure nothrow @safe @nogc
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ allocator_ = allocator;
+ }
+
+ /**
+ * Destroys the string.
+ */
+ ~this() nothrow @trusted @nogc
+ {
+ allocator.deallocate(data[0 .. capacity_]);
+ }
+
+ /**
+ * Reserves $(D_PARAM size) bytes for the string.
+ *
+ * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the
+ * function call does not cause a reallocation and the string capacity is not
+ * affected.
+ *
+ * Params:
+ * size = Desired size in bytes.
+ */
+ void reserve(in size_t size) nothrow @trusted @nogc
+ {
+ if (capacity_ >= size)
+ {
+ return;
+ }
+
+ void[] buf = data[0 .. capacity_];
+ if (!allocator.reallocate(buf, size))
+ {
+ onOutOfMemoryErrorNoGC();
+ }
+ data = cast(char*) buf;
+ capacity_ = size;
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ String s;
+ assert(s.capacity == 0);
+
+ s.reserve(3);
+ assert(s.capacity == 3);
+
+ s.reserve(3);
+ assert(s.capacity == 3);
+
+ s.reserve(1);
+ assert(s.capacity == 3);
+ }
+
+ /**
+ * Requests the string to reduce its capacity to fit the $(D_PARAM size).
+ *
+ * The request is non-binding. The string won't become smaller than the
+ * string byte length.
+ *
+ * Params:
+ * size = Desired size.
+ */
+ void shrink(in size_t size) nothrow @trusted @nogc
+ {
+ if (capacity_ <= size)
+ {
+ return;
+ }
+
+ immutable n = max(length_, size);
+ void[] buf = data[0 .. capacity_];
+ if (allocator.reallocate(buf, size))
+ {
+ capacity_ = n;
+ data = cast(char*) buf;
+ }
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ auto s = String("Die Alten lasen laut.");
+ assert(s.capacity == 21);
+
+ s.reserve(30);
+ s.shrink(25);
+ assert(s.capacity == 25);
+
+ s.shrink(18);
+ assert(s.capacity == 21);
+ }
+
+ /**
+ * Returns: String capacity in bytes.
+ */
+ @property size_t capacity() const pure nothrow @safe @nogc
+ {
+ return capacity_;
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ auto s = String("In allem Schreiben ist Schamlosigkeit.");
+ assert(s.capacity == 38);
+ }
+
+ /**
+ * Returns an array used internally by the string.
+ * The length of the returned array may be smaller than the size of the
+ * reserved memory for the string.
+ *
+ * Returns: The array representing the string.
+ */
+ inout(char[]) get() inout pure nothrow @trusted @nogc
+ {
+ return data[0 .. length_];
+ }
+
+ mixin DefaultAllocator;
+}