diff options
Diffstat (limited to 'external/glim/gstring.hpp')
-rw-r--r-- | external/glim/gstring.hpp | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/external/glim/gstring.hpp b/external/glim/gstring.hpp new file mode 100644 index 000000000..eb0ea2cb7 --- /dev/null +++ b/external/glim/gstring.hpp @@ -0,0 +1,578 @@ +#ifndef _GSTRING_INCLUDED +#define _GSTRING_INCLUDED + +/** + * A C++ char string.\n + * Can reuse (stack-allocated) buffers.\n + * Can create zero-copy views. + * @code +Copyright 2012 Kozarezov Artem Aleksandrovich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + * @endcode + * @file + */ + +#include <assert.h> +#include <stdlib.h> // malloc, realloc, free +#include <stdint.h> +#include <string.h> // memcpy, memmem +#include <stdio.h> // snprintf +#include <stdexcept> +#include <iostream> +#include <iterator> + +#include "exception.hpp" + +/// Make a read-only gstring from a C string: `const gstring foo = C2GSTRING("foo")`. +#define C2GSTRING(CSTR) ::glim::gstring (::glim::gstring::ReferenceConstructor(), CSTR, sizeof (CSTR) - 1, true) +/// Usage: GSTRING_ON_STACK (buf, 64) << "foo" << "bar"; +#define GSTRING_ON_STACK(NAME, SIZE) char NAME##Buf[SIZE]; ::glim::gstring NAME (SIZE, NAME##Buf, false, 0); NAME.self() + +namespace glim { + +/** + * Based on: C++ version 0.4 char* style "itoa": Written by Lukás Chmela, http://www.strudel.org.uk/itoa/ (GPLv3). + * Returns a pointer to the end of the string. + * NB about `inline`: http://stackoverflow.com/a/1759575/257568 + * @param base Maximum is 36 (see http://en.wikipedia.org/wiki/Base_36). + */ +inline char* itoa (char* ptr, int64_t value, const int base = 10) { + // check that the base is valid + if (base < 2 || base > 36) {*ptr = '\0'; return ptr;} + + char *ptr1 = ptr; + int64_t tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; + } while (value); + + // Apply negative sign + if (tmp_value < 0) *ptr++ = '-'; + char* end = ptr; + *ptr-- = '\0'; + char tmp_char; + while (ptr1 < ptr) { + tmp_char = *ptr; + *ptr--= *ptr1; + *ptr1++ = tmp_char; + } + return end; +} + +class gstring_stream; + +class gstring { + enum Flags { + FREE_FLAG = 0x80000000, // 1st bit; `_buf` needs `free`ing + FREE_OFFSET = 31, + REF_FLAG = 0x40000000, // 2nd bit; `_buf` has an extended life-time (such as C string literals) and can be shared (passed by reference) + REF_OFFSET = 30, + CAPACITY_MASK = 0x3F000000, // 3..8 bits; `_buf` size is 2^this + CAPACITY_OFFSET = 24, + LENGTH_MASK = 0x00FFFFFF, // 9th bit; allocated capacity + }; + uint32_t _meta; +public: + void* _buf; +public: + constexpr gstring() noexcept: _meta (0), _buf (nullptr) {} + /** + * Reuse `buf` of size `bufSize`. + * To fully use `buf` the `bufSize` should be the power of two. + * @param bufSize The size of the memory allocated to `buf`. + * @param buf The memory region to be reused. + * @param free Whether the `buf` should be `free`d on resize or gstring destruction. + * @param length String length inside the `buf`. + * @param ref If true then the `buf` isn't copied by gstring's copy constructors. + * This is useful for wrapping C string literals. + */ + explicit gstring (uint32_t bufSize, void* buf, bool free, uint32_t length, bool ref = false) noexcept { + uint32_t power = 0; while (((uint32_t) 1 << (power + 1)) <= bufSize) ++power; + _meta = ((uint32_t) free << FREE_OFFSET) | + ((uint32_t) ref << REF_OFFSET) | + (power << CAPACITY_OFFSET) | + (length & LENGTH_MASK); + _buf = buf; + } + + struct ReferenceConstructor {}; + /// Make a view to the given cstring. + /// @param buf The memory region to be reused. + /// @param length String length inside the `buf`. + /// @param ref If true then the `buf` isn't copied by gstring's copy constructors. + /// This is useful for wrapping C string literals. + explicit constexpr gstring (ReferenceConstructor, const char* buf, uint32_t length, bool ref = false) noexcept: + _meta (((uint32_t) ref << REF_OFFSET) | (length & LENGTH_MASK)), _buf ((void*) buf) {} + + /// Copy the characters into `gstring`. + gstring (const char* chars): _meta (0), _buf (nullptr) { + if (chars && *chars) { + size_t length = ::strlen (chars); + _buf = ::malloc (length); + ::memcpy (_buf, chars, length); + _meta = (uint32_t) FREE_FLAG | + (length & LENGTH_MASK); + } + } + + /// Copy the characters into `gstring`. + gstring (const char* chars, size_t length) { + if (length != 0) { + _buf = ::malloc (length); + ::memcpy (_buf, chars, length); + } else _buf = nullptr; + _meta = (uint32_t) FREE_FLAG | + (length & LENGTH_MASK); + } + + /// Copy into `gstring`. + gstring (const std::string& str): _meta (0), _buf (nullptr) { + if (!str.empty()) { + _buf = ::malloc (str.length()); + ::memcpy (_buf, str.data(), str.length()); + _meta = (uint32_t) FREE_FLAG | + (str.length() & LENGTH_MASK); + } + } + + /// If `gstr` is `copiedByReference` then make a shallow copy of it, + /// otherwise copy `gstr` contents into a `malloc`ed buffer. + gstring (const gstring& gstr) { + uint32_t glen = gstr.length(); + if (glen != 0) { + if (gstr.copiedByReference()) { + _meta = gstr._meta; _buf = gstr._buf; + } else { + _buf = ::malloc (glen); + if (!_buf) GTHROW ("!malloc"); + ::memcpy (_buf, gstr._buf, glen); + _meta = (uint32_t) FREE_FLAG | + (glen & LENGTH_MASK); + } + } else { + _meta = 0; _buf = nullptr; + } + } + gstring (gstring&& gstr) noexcept: _meta (gstr._meta), _buf (gstr._buf) { + gstr._meta = 0; gstr._buf = nullptr; + } + gstring& operator = (const gstring& gstr) { + // cf. http://stackoverflow.com/questions/9322174/move-assignment-operator-and-if-this-rhs + if (this != &gstr) { + uint32_t glen = gstr.length(); + uint32_t power = 0; + uint32_t capacity = this->capacity(); + if (glen <= capacity && capacity > 1) { // `capacity <= 1` means there is no _buf. + // We reuse existing buffer. Keep capacity info. + power = (_meta & CAPACITY_MASK) >> CAPACITY_OFFSET; + } else { + if (_buf != nullptr && needsFreeing()) ::free (_buf); + if (gstr.copiedByReference()) { + _meta = gstr._meta; _buf = gstr._buf; + return *this; + } + _buf = ::malloc (glen); + if (_buf == nullptr) GTHROW ("malloc failed"); + } + ::memcpy (_buf, gstr._buf, glen); + _meta = (uint32_t) FREE_FLAG | + (power << CAPACITY_OFFSET) | + (glen & LENGTH_MASK); + } + return *this; + } + gstring& operator = (gstring&& gstr) noexcept { + assert (this != &gstr); + if (_buf != nullptr && needsFreeing()) free (_buf); + _meta = gstr._meta; _buf = gstr._buf; + gstr._meta = 0; gstr._buf = nullptr; + return *this; + } + + /// Return a copy of the string. + gstring clone() const {return gstring (data(), length());} + /// If the gstring's buffer is not owned then copy the bytes into the owned one. + /// Useful for turning a stack-allocated gstring into a heap-allocated gstring. + gstring& owned() {if (!needsFreeing()) *this = gstring (data(), length()); return *this;} + /** Returns a reference to the gstring: when the reference is copied the internal buffer is not copied but referenced (shallow copy).\n + * This method should only be used if it is know that the life-time of the reference and its copies is less than the life-time of the buffer. */ + gstring ref() const noexcept {return gstring (0, _buf, false, length(), true);} + + bool needsFreeing() const noexcept {return _meta & FREE_FLAG;} + bool copiedByReference() const noexcept {return _meta & REF_FLAG;} + /// Current buffer capacity (memory allocated to the string). Returns 1 if no memory allocated. + uint32_t capacity() const noexcept {return 1 << ((_meta & CAPACITY_MASK) >> CAPACITY_OFFSET);} + uint32_t length() const noexcept {return _meta & LENGTH_MASK;} + size_t size() const noexcept {return _meta & LENGTH_MASK;} + bool empty() const noexcept {return (_meta & LENGTH_MASK) == 0;} + std::string str() const {size_t len = size(); return len ? std::string ((const char*) _buf, len) : std::string();} + /// NB: might move the string to a new buffer. + const char* c_str() const { + uint32_t len = length(); if (len == 0) return ""; + uint32_t cap = capacity(); + // c_str should work even for const gstring's, otherwise it's too much of a pain. + if (cap < len + 1) const_cast<gstring*> (this) ->reserve (len + 1); + char* buf = (char*) _buf; buf[len] = 0; return buf; + } + bool equals (const char* cstr) const noexcept { + const char* cstr_; uint32_t clen_; + if (cstr != nullptr) {cstr_ = cstr; clen_ = strlen (cstr);} else {cstr_ = ""; clen_ = 0;} + const uint32_t len = length(); + if (len != clen_) return false; + const char* gstr_ = _buf != nullptr ? (const char*) _buf : ""; + return memcmp (gstr_, cstr_, len) == 0; + } + bool equals (const gstring& gs) const noexcept { + uint32_t llen = length(), olen = gs.length(); + if (llen != olen) return false; + return memcmp ((const char*) _buf, (const char*) gs._buf, llen) == 0; + } + + char& operator[] (unsigned index) noexcept {return ((char*)_buf)[index];} + const char& operator[] (unsigned index) const noexcept {return ((const char*)_buf)[index];} + + /// Access `_buf` as `char*`. `_buf` might be nullptr. + char* data() noexcept {return (char*)_buf;} + const char* data() const noexcept {return (const char*)_buf;} + + char* endp() noexcept {return (char*)_buf + length();} + const char* endp() const noexcept {return (const char*)_buf + length();} + + gstring view (uint32_t pos, int32_t count = -1) noexcept { + return gstring (0, data() + pos, false, count >= 0 ? count : length() - pos, copiedByReference());} + const gstring view (uint32_t pos, int32_t count = -1) const noexcept { + return gstring (0, (void*)(data() + pos), false, count >= 0 ? count : length() - pos, copiedByReference());} + + // http://en.cppreference.com/w/cpp/concept/Iterator + template<typename CT> struct iterator_t: public std::iterator<std::random_access_iterator_tag, CT, int32_t> { + CT* _ptr; + iterator_t () noexcept: _ptr (nullptr) {} + iterator_t (CT* ptr) noexcept: _ptr (ptr) {} + iterator_t (const iterator_t<CT>& it) noexcept: _ptr (it._ptr) {} + + CT& operator*() const noexcept {return *_ptr;} + CT* operator->() const noexcept {return _ptr;} + CT& operator[](int32_t ofs) const noexcept {return _ptr[ofs];} + + iterator_t<CT>& operator++() noexcept {++_ptr; return *this;} + iterator_t<CT> operator++(int) noexcept {return iterator_t<CT> (_ptr++);}; + iterator_t<CT>& operator--() noexcept {--_ptr; return *this;} + iterator_t<CT> operator--(int) noexcept {return iterator_t<CT> (_ptr--);}; + bool operator == (const iterator_t<CT>& i2) const noexcept {return _ptr == i2._ptr;} + bool operator != (const iterator_t<CT>& i2) const noexcept {return _ptr != i2._ptr;} + bool operator < (const iterator_t<CT>& i2) const noexcept {return _ptr < i2._ptr;} + bool operator > (const iterator_t<CT>& i2) const noexcept {return _ptr > i2._ptr;} + bool operator <= (const iterator_t<CT>& i2) const noexcept {return _ptr <= i2._ptr;} + bool operator >= (const iterator_t<CT>& i2) const noexcept {return _ptr >= i2._ptr;} + iterator_t<CT> operator + (int32_t ofs) const noexcept {return iterator (_ptr + ofs);} + iterator_t<CT>& operator += (int32_t ofs) noexcept {_ptr += ofs; return *this;} + iterator_t<CT> operator - (int32_t ofs) const noexcept {return iterator (_ptr - ofs);} + iterator_t<CT>& operator -= (int32_t ofs) noexcept {_ptr -= ofs; return *this;} + }; + // http://en.cppreference.com/w/cpp/concept/Container + typedef char value_type; + typedef char& reference; + typedef const char& const_reference; + typedef uint32_t size_type; + typedef int32_t difference_type; + typedef iterator_t<char> iterator; + typedef iterator_t<const char> const_iterator; + iterator begin() noexcept {return iterator ((char*) _buf);} + const_iterator begin() const noexcept {return const_iterator ((char*) _buf);} + iterator end() noexcept {return iterator ((char*) _buf + size());} + const_iterator end() const noexcept {return const_iterator ((char*) _buf + size());} + const_iterator cbegin() const noexcept {return const_iterator ((char*) _buf);} + const_iterator cend() const noexcept {return const_iterator ((char*) _buf + size());} + + /** Returns -1 if not found. */ + int32_t find (const char* str, int32_t pos, int32_t count) const noexcept { + const int32_t hlen = (int32_t) length() - pos; + if (hlen <= 0) return -1; + char* haystack = (char*) _buf + pos; + void* mret = memmem (haystack, hlen, str, count); + if (mret == 0) return -1; + return (char*) mret - (char*) _buf; + } + int32_t find (const char* str, int32_t pos = 0) const noexcept {return find (str, pos, strlen (str));} + + /** Index of `ch` inside the string or -1 if not found. */ + int32_t indexOf (char ch) const noexcept { + void* ret = memchr (_buf, ch, size()); + return ret == nullptr ? -1 : (char*) ret - (char*) _buf; + } + + // Helps to workaround the "statement has no effect" warning in `GSTRING_ON_STACK`. + gstring& self() noexcept {return *this;} + + /** Grow buffer to be at least `to` characters long. */ + void reserve (uint32_t to) { + uint32_t power = (_meta & CAPACITY_MASK) >> CAPACITY_OFFSET; + if (((uint32_t) 1 << power) < to) { + ++power; + while (((uint32_t) 1 << power) < to) ++power; + if (power > 24) {GSTRING_ON_STACK (error, 64) << "gstring too large: " << (int) to; GTHROW (error.str());} + } else if (power) { + // No need to grow. + return; + } + _meta = (_meta & ~CAPACITY_MASK) | (power << CAPACITY_OFFSET); + if (needsFreeing() && _buf != nullptr) { + _buf = ::realloc (_buf, capacity()); + if (_buf == nullptr) GTHROW ("realloc failed"); + } else { + const char* oldBuf = (const char*) _buf; + _buf = ::malloc (capacity()); + if (_buf == nullptr) GTHROW ("malloc failed"); + if (oldBuf != nullptr) ::memcpy (_buf, oldBuf, length()); + _meta |= FREE_FLAG; + } + } + + /** Length setter. Useful when you manually write into the buffer or to cut the string. */ + void length (uint32_t len) noexcept { + _meta = (_meta & ~LENGTH_MASK) | (len & LENGTH_MASK); + } + +protected: + friend class gstring_stream; +public: + /** Appends an integer to the string. + * @param base Radix, from 1 to 36 (default 10). + * @param bytes How many bytes to reserve (24 by default). */ + void append64 (int64_t iv, int base = 10, uint_fast8_t bytes = 24) { + uint32_t pos = length(); + if (capacity() < pos + bytes) reserve (pos + bytes); + length (itoa ((char*) _buf + pos, iv, base) - (char*) _buf); + } + void append (char ch) { + uint32_t pos = length(); + const uint32_t cap = capacity(); + if (pos >= cap || cap <= 1) reserve (pos + 1); + ((char*)_buf)[pos] = ch; + length (++pos); + } + void append (const char* cstr, uint32_t clen) { + uint32_t len = length(); + uint32_t need = len + clen; + const uint32_t cap = capacity(); + if (need > cap || cap <= 1) reserve (need); + ::memcpy ((char*) _buf + len, cstr, clen); + length (need); + } + /** This one is for http://code.google.com/p/re2/; `clear` then `append`. */ + bool ParseFrom (const char* cstr, int clen) { + if (clen < 0 || clen > (int) LENGTH_MASK) return false; + length (0); append (cstr, (uint32_t) clen); return true;} + gstring& operator << (const gstring& gs) {append (gs.data(), gs.length()); return *this;} + gstring& operator << (const std::string& str) {append (str.data(), str.length()); return *this;} + gstring& operator << (const char* cstr) {if (cstr) append (cstr, ::strlen (cstr)); return *this;} + gstring& operator << (char ch) {append (ch); return *this;} + gstring& operator << (int iv) {append64 (iv, 10, sizeof (int) * 3); return *this;} + gstring& operator << (long iv) {append64 (iv, 10, sizeof (long) * 3); return *this;} + gstring& operator << (long long iv) {append64 (iv, 10, sizeof (long long) * 3); return *this;} + gstring& operator << (double dv) { + uint32_t len = length(); + reserve (len + 32); + int rc = snprintf (endp(), 31, "%f", dv); + if (rc > 0) {length (len + std::min (rc, 31));} + return *this; + } + + bool operator < (const gstring &gs) const noexcept { + uint32_t len1 = length(); uint32_t len2 = gs.length(); + if (len1 == len2) return ::strncmp (data(), gs.data(), len1) < 0; + int cmp = ::strncmp (data(), gs.data(), std::min (len1, len2)); + if (cmp) return cmp < 0; + return len1 < len2; + } + + /// Asks `strftime` to generate a time string. Capacity is increased if necessary (up to a limit of +1024 bytes). + gstring& appendTime (const char* format, struct tm* tmv) { + int32_t pos = length(), cap = capacity(), left = cap - pos; + if (left < 8) {reserve (pos + 8); return appendTime (format, tmv);} + size_t got = strftime ((char*) _buf + pos, left, format, tmv); + if (got == 0) { + if (left > 1024) return *this; // Guard against perpetual growth. + reserve (pos + left * 2); return appendTime (format, tmv); + } + length (pos + got); + return *this; + } + + /// Append the characters to this `gstring` wrapping them in the netstring format. + gstring& appendNetstring (const char* cstr, uint32_t clen) { + *this << (int) clen; append (':'); append (cstr, clen); append (','); return *this;} + /// Append the `gstr` wrapping it in the netstring format. + gstring& appendNetstring (const gstring& gstr) {return appendNetstring (gstr.data(), gstr.length());} + + std::ostream& writeAsNetstring (std::ostream& stream) const; + + /// Parse netstring at `pos` and return a `gstring` *pointing* at the parsed netstring.\n + /// No heap space allocated.\n + /// Throws std::runtime_error if netstring parsing fails.\n + /// If parsing was successfull, then `after` is set to point after the parsed netstring. + gstring netstringAt (uint32_t pos, uint32_t* after = nullptr) const { + const uint32_t len = length(); char* buf = (char*) _buf; + if (buf == nullptr) GTHROW ("gstring: netstringAt: nullptr"); + uint32_t next = pos; + while (next < len && buf[next] >= '0' && buf[next] <= '9') ++next; + if (next >= len || buf[next] != ':' || next - pos > 10) GTHROW ("gstring: netstringAt: no header"); + char* endptr = 0; + long nlen = ::strtol (buf + pos, &endptr, 10); + if (endptr != buf + next) GTHROW ("gstring: netstringAt: unexpected header end"); + pos = next + 1; next = pos + nlen; + if (next >= len || buf[next] != ',') GTHROW ("gstring: netstringAt: no body"); + if (after) *after = next + 1; + return gstring (0, buf + pos, false, next - pos); + } + + /// Wrapper around strtol, not entirely safe (make sure the string is terminated with a non-digit, by calling c_str, for example). + long intAt (uint32_t pos, uint32_t* after = nullptr, int base = 10) const { + // BTW: http://www.kumobius.com/2013/08/c-string-to-int/ + const uint32_t len = length(); char* buf = (char*) _buf; + if (pos >= len || buf == nullptr) GTHROW ("gstring: intAt: pos >= len"); + char* endptr = 0; + long lv = ::strtol (buf + pos, &endptr, base); + uint32_t next = endptr - buf; + if (next > len) GTHROW ("gstring: intAt: endptr > len"); + if (after) *after = next; + return lv; + } + + /// Wrapper around strtol. Copies the string into a temporary buffer in order to pass it to strtol. Empty string returns 0. + long toInt (int base = 10) const noexcept { + const uint32_t len = length(); if (len == 0) return 0; + char buf[len + 1]; memcpy (buf, _buf, len); buf[len] = 0; + return ::strtol (buf, nullptr, base); + } + + /// Get a single netstring from the `stream` and append it to the end of `gstring`. + /// Throws an exception if the input is not a well-formed netstring. + gstring& readNetstring (std::istream& stream) { + int32_t nlen; stream >> nlen; + if (!stream.good() || nlen < 0) GTHROW ("!netstring"); + int ch = stream.get(); + if (!stream.good() || ch != ':') GTHROW ("!netstring"); + uint32_t glen = length(); + const uint32_t cap = capacity(); + if (cap < glen + nlen || cap <= 1) reserve (glen + nlen); + stream.read ((char*) _buf + glen, nlen); + if (!stream.good()) GTHROW ("!netstring"); + ch = stream.get(); + if (ch != ',') GTHROW ("!netstring"); + length (glen + nlen); + return *this; + } + + /// Set length to 0. `_buf` not changed. + gstring& clear() noexcept {length (0); return *this;} + + /// Removes `count` characters starting at `pos`. + gstring& erase (uint32_t pos, uint32_t count = 1) noexcept { + const char* buf = (const char*) _buf; + const char* pt1 = buf + pos; + const char* pt2 = pt1 + count; + uint32_t len = length(); + const char* end = buf + len; + if (pt2 <= end) { + length (len - count); + ::memmove ((void*) pt1, (void*) pt2, end - pt2); + } + return *this; + } + /// Remove characters [from,till) and return `from`.\n + /// Compatible with "boost/algorithm/string/trim.hpp". + iterator_t<char> erase (iterator_t<char> from, iterator_t<char> till) noexcept { + intptr_t ipos = from._ptr - (char*) _buf; + intptr_t count = till._ptr - from._ptr; + if (ipos >= 0 && count > 0) erase (ipos, count); + return from; + } + + ~gstring() noexcept { + if (_buf != nullptr && needsFreeing()) {::free (_buf); _buf = nullptr;} + } +}; + +inline bool operator == (const gstring& gs1, const gstring& gs2) noexcept {return gs1.equals (gs2);} +inline bool operator == (const char* cstr, const gstring& gstr) noexcept {return gstr.equals (cstr);} +inline bool operator == (const gstring& gstr, const char* cstr) noexcept {return gstr.equals (cstr);} +inline bool operator != (const gstring& gs1, const gstring& gs2) noexcept {return !gs1.equals (gs2);} +inline bool operator != (const char* cstr, const gstring& gstr) noexcept {return !gstr.equals (cstr);} +inline bool operator != (const gstring& gstr, const char* cstr) noexcept {return !gstr.equals (cstr);} + +inline bool operator == (const gstring& gstr, const std::string& str) noexcept { + return gstr.equals (gstring (gstring::ReferenceConstructor(), str.data(), str.size()));} +inline bool operator != (const gstring& gstr, const std::string& str) noexcept {return !(gstr == str);} +inline bool operator == (const std::string& str, const gstring& gstr) noexcept {return gstr == str;} +inline bool operator != (const std::string& str, const gstring& gstr) noexcept {return !(gstr == str);} +inline std::string operator += (std::string& str, const gstring& gstr) {return str.append (gstr.data(), gstr.size());} +inline std::string operator + (const std::string& str, const gstring& gstr) {return std::string (str) .append (gstr.data(), gstr.size());} + +inline std::ostream& operator << (std::ostream& os, const gstring& gstr) { + if (gstr._buf != nullptr) os.write ((const char*) gstr._buf, gstr.length()); + return os; +} + +/// Encode this `gstring` into `stream` as a netstring. +inline std::ostream& gstring::writeAsNetstring (std::ostream& stream) const { + stream << length() << ':' << *this << ','; + return stream; +} + +// http://www.mr-edd.co.uk/blog/beginners_guide_streambuf +// http://www.dreamincode.net/code/snippet2499.htm +// http://spec.winprog.org/streams/ +class gstring_stream: public std::basic_streambuf<char, std::char_traits<char> > { + gstring& _gstr; +public: + gstring_stream (gstring& gstr) noexcept: _gstr (gstr) { + char* buf = (char*) gstr._buf; + if (buf != nullptr) setg (buf, buf, buf + gstr.length()); + } +protected: + virtual int_type overflow (int_type ch) { + if (__builtin_expect (ch != traits_type::eof(), 1)) _gstr.append ((char) ch); + return 0; + } + // no copying + gstring_stream (const gstring_stream &); + gstring_stream& operator = (const gstring_stream &); +}; + +} // namespace glim + +// hash specialization +// cf. http://stackoverflow.com/questions/8157937/how-to-specialize-stdhashkeyoperator-for-user-defined-type-in-unordered +namespace std { + template <> struct hash<glim::gstring> { + size_t operator()(const glim::gstring& gs) const noexcept { + // cf. http://stackoverflow.com/questions/7666509/hash-function-for-string + // Would be nice to use https://131002.net/siphash/ here. + uint32_t hash = 5381; + uint32_t len = gs.length(); + if (len) { + const char* str = (const char*) gs._buf; + const char* end = str + len; + while (str < end) hash = ((hash << 5) + hash) + *str++; /* hash * 33 + c */ + } + return hash; + } + }; +} + +#endif // _GSTRING_INCLUDED |