diff --git a/Makefile b/Makefile index 924a71e..e74677b 100644 --- a/Makefile +++ b/Makefile @@ -22,11 +22,7 @@ DEPFILE := $(OBJDIR)/$(TARGETNAME).d DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in -ifeq ($(OS),windows) -SOURCES := $(shell dir /s /b $(SRCDIR)\\*.d) -else SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d') -endif # Set target file based on build type and OS ifeq ($(BUILD_TYPE),exe) @@ -93,30 +89,19 @@ else endif ifeq ($(CONFIG),unittest) - DFLAGS := $(DFLAGS) -unittest + DFLAGS := $(DFLAGS) -unittest -main endif -include $(DEPFILE) $(TARGET): -ifeq ($(OS),windows) - @if not exist "obj" mkdir "obj" > nul 2>&1 - @if not exist "$(subst /,\,$(OBJDIR))" mkdir "$(subst /,\,$(OBJDIR))" > nul 2>&1 - @if not exist "bin" mkdir "bin" > nul 2>&1 - @if not exist "$(subst /,\,$(TARGETDIR))" mkdir "$(subst /,\,$(TARGETDIR))" > nul 2>&1 -else mkdir -p $(OBJDIR) $(TARGETDIR) -endif ifeq ($(D_COMPILER),ldc) "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -deps=$(DEPFILE) $(SOURCES) else ifeq ($(D_COMPILER),dmd) ifeq ($(BUILD_TYPE),lib) - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) -ifeq ($(OS),windows) - move "$(subst /,\,$(OBJDIR))\\$(notdir $(TARGET))" "$(subst /,\,$(TARGETDIR))" > nul -else + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(OBJDIR)/$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) mv "$(OBJDIR)/$(notdir $(TARGET))" "$(TARGETDIR)" -endif else # exe "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) endif diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 14ecc1f..d44fec3 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -1,6 +1,7 @@ module urt.algorithm; -import urt.traits : lvalueOf; +import urt.meta : AliasSeq; +import urt.traits : is_some_function, lvalue_of, Parameters, ReturnType, Unqual; import urt.util : swap; version = SmallSize; @@ -10,36 +11,44 @@ nothrow @nogc: auto compare(T, U)(auto ref T a, auto ref U b) { - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!U))) + static if (__traits(compiles, a.opCmp(b))) return a.opCmp(b); - else static if (__traits(compiles, lvalueOf!U.opCmp(lvalueOf!T))) + else static if (__traits(compiles, b.opCmp(a))) return -b.opCmp(a); - else static if (is(T : A[], A)) + else static if (is(T : A[], A) || is(U : B[], B)) { - import urt.traits : isPrimitive; + import urt.traits : is_primitive; + + static assert(is(T : A[], A) && is(U : B[], B), "TODO: compare an array with a not-array?"); auto ai = a.ptr; auto bi = b.ptr; - size_t len = a.length < b.length ? a.length : b.length; - static if (isPrimitive!A) + + // first compere the pointers... + if (ai !is bi) { - // compare strings - foreach (i; 0 .. len) + size_t len = a.length < b.length ? a.length : b.length; + static if (is_primitive!A) { - if (ai[i] != bi[i]) - return ai[i] < bi[i] ? -1 : 1; + // compare strings + foreach (i; 0 .. len) + { + if (ai[i] != bi[i]) + return ai[i] < bi[i] ? -1 : 1; + } } - } - else - { - // compare arrays - foreach (i; 0 .. len) + else { - auto cmp = compare(ai[i], bi[i]); - if (cmp != 0) - return cmp; + // compare arrays + foreach (i; 0 .. len) + { + if (auto cmp = compare(ai[i], bi[i])) + return cmp; + } } } + + // finally, compare the lengths if (a.length == b.length) return 0; return a.length < b.length ? -1 : 1; @@ -48,8 +57,39 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } -size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) +size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, auto ref Pred pred) + if (is_some_function!Pred && is(ReturnType!Pred == int) && is(Parameters!Pred == AliasSeq!(const void*, const void*))) { + debug assert(arr.length % stride == 0, "array length must be a multiple of stride"); + const count = arr.length / stride; + + const void* p = arr.ptr; + size_t low = 0; + size_t high = count; + while (low < high) + { + size_t mid = low + (high - low) / 2; + + // should we chase the first in a sequence of same values? + int cmp = pred(p + mid*stride, value); + if (cmp < 0) + low = mid + 1; + else + high = mid; + } + if (low == count) + return count; + if (pred(p + low*stride, value) == 0) + return low; + return count; +} + +size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) + if (!is(Unqual!T == void)) +{ + static if (is(pred == void)) + static assert (cmp_args.length == 1, "binary_search without a predicate requires exactly one comparison argument"); + T* p = arr.ptr; size_t low = 0; size_t high = arr.length; @@ -59,7 +99,7 @@ size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) static if (is(pred == void)) { // should we chase the first in a sequence of same values? - if (p[mid] < cmpArgs[0]) + if (p[mid] < cmp_args[0]) low = mid + 1; else high = mid; @@ -67,82 +107,97 @@ size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) else { // should we chase the first in a sequence of same values? - int cmp = pred(p[mid], cmpArgs); + int cmp = pred(p[mid], cmp_args); if (cmp < 0) low = mid + 1; else high = mid; } } + if (low == arr.length) + return arr.length; static if (is(pred == void)) { - if (p[low] == cmpArgs[0]) + if (p[low] == cmp_args[0]) return low; } else { - if (pred(p[low], cmpArgs) == 0) + if (pred(p[low], cmp_args) == 0) return low; } return arr.length; } -void qsort(alias pred = void, T)(T[] arr) +void qsort(alias pred = void, T)(T[] arr) pure { + if (arr.length <= 1) + return; + version (SmallSize) + enum use_small_size_impl = true; + else + enum use_small_size_impl = false; + + if (!__ctfe && use_small_size_impl) { static if (is(pred == void)) - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!T))) - static int compare(const void* a, const void* b) nothrow @nogc + static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!T))) + static int compare(const void* a, const void* b) pure nothrow @nogc => (*cast(const T*)a).opCmp(*cast(const T*)b); else - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => *cast(const T*)a < *cast(const T*)b ? -1 : *cast(const T*)a > *cast(const T*)b ? 1 : 0; else - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => pred(*cast(T*)a, *cast(T*)b); - qsort(arr[], T.sizeof, &compare, (void* a, void* b) nothrow @nogc { + qsort(arr[], T.sizeof, &compare, (void* a, void* b) pure nothrow @nogc { swap(*cast(T*)a, *cast(T*)b); }); } else { T* p = arr.ptr; - if (arr.length > 1) - { - size_t pivotIndex = arr.length / 2; - T* pivot = &p[pivotIndex]; + const n = cast(ptrdiff_t)arr.length; + + size_t pivotIndex = n / 2; + T* pivot = p + pivotIndex; - size_t i = 0; - size_t j = arr.length - 1; + size_t i = 0; + ptrdiff_t j = n - 1; - while (i <= j) + while (i <= j) + { + static if (is(pred == void)) { - static if (is(pred == void)) - { - while (p[i] < *pivot) i++; - while (p[j] > *pivot) j--; - } - else - { - while (pred(*cast(T*)&p[i], *cast(T*)pivot) < 0) i++; - while (pred(*cast(T*)&p[j], *cast(T*)pivot) > 0) j--; - } - if (i <= j) - { - swap(p[i], p[j]); - i++; - j--; - } + while (p[i] < *pivot) ++i; + while (p[j] > *pivot) --j; } + else + { + while (pred(p[i], *pivot) < 0) ++i; + while (pred(p[j], *pivot) > 0) --j; + } + if (i <= j) + { + // track pivot value across swaps + if (p + i == pivot) + pivot = p + j; + else if (p + j == pivot) + pivot = p + i; - if (j > 0) - qsort(p[0 .. j + 1]); - if (i < arr.length) - qsort(p[i .. arr.length]); + swap(p[i], p[j]); + ++i; + --j; + } } + + if (j >= 0) + qsort!pred(p[0 .. j + 1]); + if (i < n) + qsort!pred(p[i .. n]); } } @@ -151,7 +206,7 @@ unittest struct S { int x; - int opCmp(ref const S rh) const nothrow @nogc + int opCmp(ref const S rh) pure const nothrow @nogc => x < rh.x ? -1 : x > rh.x ? 1 : 0; } @@ -159,18 +214,18 @@ unittest qsort(arr); assert(arr == [-1, 3, 17, 30, 100]); - S[5] arr2 = [ S(3), S(100), S(-1), S(17), S(30)]; + S[5] arr2 = [ S(3), S(100), S(-1), S(17), S(30) ]; qsort(arr2); foreach (i, ref s; arr2) assert(s.x == arr[i]); // test binary search, not that they're sorted... - assert(binarySearch(arr, -1) == 0); - assert(binarySearch!(s => s.x < 30 ? -1 : s.x > 30 ? 1 : 0)(arr2) == 3); - assert(binarySearch(arr, 0) == arr.length); + assert(binary_search(arr, -1) == 0); + assert(binary_search!(s => s.x < 30 ? -1 : s.x > 30 ? 1 : 0)(arr2) == 3); + assert(binary_search(arr, 0) == arr.length); int[10] rep = [1, 10, 10, 10, 10, 10, 10, 10, 10, 100]; - assert(binarySearch(rep, 10) == 1); + assert(binary_search(rep, 10) == 1); } @@ -181,34 +236,36 @@ version (SmallSize) // just one generic implementation to minimise the code... // kinda slow though... look at all those multiplies! // maybe there's some way to make this faster :/ - void qsort(void[] arr, size_t elementSize, int function(const void* a, const void* b) nothrow @nogc compare, void function(void* a, void* b) nothrow @nogc swap) + void qsort(void[] arr, size_t element_size, int function(const void* a, const void* b) pure nothrow @nogc compare, void function(void* a, void* b) pure nothrow @nogc swap) pure { void* p = arr.ptr; - size_t length = arr.length / elementSize; - if (length > 1) - { - size_t pivotIndex = length / 2; - void* pivot = p + pivotIndex*elementSize; + size_t length = arr.length / element_size; + if (length <= 1) + return; + + size_t pivot_index = length / 2; + size_t last = length - 1; + swap(p + pivot_index*element_size, p + last*element_size); - size_t i = 0; - size_t j = length - 1; + void* pivot = p + last*element_size; - while (i <= j) + size_t partition = 0; + for (size_t k = 0; k < last; ++k) + { + void* elem = p + k*element_size; + if (compare(elem, pivot) < 0) { - while (compare(p + i*elementSize, pivot) < 0) i++; - while (compare(p + j*elementSize, pivot) > 0) j--; - if (i <= j) - { - swap(p + i*elementSize, p + j*elementSize); - i++; - j--; - } + if (k != partition) + swap(elem, p + partition*element_size); + ++partition; } - - if (j > 0) - qsort(p[0 .. (j + 1)*elementSize], elementSize, compare, swap); - if (i < length) - qsort(p[i*elementSize .. length*elementSize], elementSize, compare, swap); } + + swap(p + partition*element_size, p + last*element_size); + + if (partition > 1) + qsort(p[0 .. partition*element_size], element_size, compare, swap); + if (partition + 1 < length) + qsort(p[(partition + 1)*element_size .. length*element_size], element_size, compare, swap); } } diff --git a/src/urt/array.d b/src/urt/array.d index 8d9858c..4f61066 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -1,7 +1,7 @@ module urt.array; import urt.mem; - +import urt.traits : is_some_char, is_primitive, is_trivial; nothrow @nogc: @@ -79,22 +79,9 @@ ref inout(T)[N] takeBack(size_t N, T)(ref inout(T)[] arr) pure return t[0..N]; } -bool exists(T)(const(T)[] arr, auto ref const T el, size_t *pIndex = null) -{ - foreach (i, ref e; arr) - { - if (e.elCmp(el)) - { - if (pIndex) - *pIndex = i; - return true; - } - } - return false; -} - // TODO: I'd like it if these only had one arg (T) somehow... size_t findFirst(T, U)(const(T)[] arr, auto ref const U el) + if (!is_some_char!T) { size_t i = 0; while (i < arr.length && !arr[i].elCmp(el)) @@ -104,15 +91,17 @@ size_t findFirst(T, U)(const(T)[] arr, auto ref const U el) // TODO: I'd like it if these only had one arg (T) somehow... size_t findLast(T, U)(const(T)[] arr, auto ref const U el) + if (!is_some_char!T) { - ptrdiff_t last = length-1; + ptrdiff_t last = arr.length-1; while (last >= 0 && !arr[last].elCmp(el)) --last; - return last < 0 ? length : last; + return last < 0 ? arr.length : last; } // TODO: I'd like it if these only had one arg (T) somehow... size_t findFirst(T, U)(const(T)[] arr, U[] seq) + if (!is_some_char!T) { if (seq.length == 0) return 0; @@ -129,6 +118,7 @@ size_t findFirst(T, U)(const(T)[] arr, U[] seq) // TODO: I'd like it if these only had one arg (T) somehow... size_t findLast(T, U)(const(T)[] arr, U[] seq) + if (!is_some_char!T) { if (seq.length == 0) return arr.length; @@ -143,7 +133,8 @@ size_t findLast(T, U)(const(T)[] arr, U[] seq) return arr.length; } -size_t findFirst(T)(const(T)[] arr, bool delegate(auto ref const T) nothrow @nogc pred) +size_t findFirst(T)(const(T)[] arr, bool delegate(ref const T) nothrow @nogc pred) + if (!is_some_char!T) { size_t i = 0; while (i < arr.length && !pred(arr[i])) @@ -151,6 +142,39 @@ size_t findFirst(T)(const(T)[] arr, bool delegate(auto ref const T) nothrow @nog return i; } +bool contains(T, U)(const(T)[] arr, auto ref const U el, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, el); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + +bool contains(T, U)(const(T)[] arr, U[] seq, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, seq); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + +bool contains(T)(const(T)[] arr, bool delegate(ref const T) nothrow @nogc pred, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, pred); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + ptrdiff_t indexOfElement(T, U)(const(T)[] arr, const(U)* el) { if (el < arr.ptr || el >= arr.ptr + arr.length) @@ -273,6 +297,8 @@ nothrow @nogc: void opAssign(U)(U[] arr) if (is(U : T)) { + // TODO: WHAT IF arr IS A SLICE OF THIS?!!? + debug assert(arr.length <= uint.max); clear(); reserve(arr.length); @@ -288,7 +314,121 @@ nothrow @nogc: } // manipulation - ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things); + static if (!is_some_char!T) + { + alias concat = append; // TODO: REMOVE THIS ALIAS, we phase out the old name... + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) + { + size_t ext_len = 0; + static foreach (i; 0 .. things.length) + { + static if (is(Things[i] == U[], U) && is(U : T)) + ext_len += things[i].length; + else static if (is(Things[i] == U[N], U, size_t N) && is(U : T)) + ext_len += N; + else static if (is(Things[i] : T)) + ext_len += 1; + else + static assert(false, "Invalid type for concat"); + } + reserve(_length + ext_len); + static foreach (i; 0 .. things.length) + { + static if (is(Things[i] == V[], V) && is(V : T)) + { + static if (copy_elements) + { + foreach (ref el; things[i]) + ptr[_length++] = el; + } + else + { + foreach (ref el; things[i]) + emplace!T(&ptr[_length++], el); + } + } + else static if (is(Things[i] == V[M], V, size_t M) && is(V : T)) + { + static if (copy_elements) + { + foreach (ref el; things[i]) + ptr[_length++] = el; + } + else + { + foreach (ref el; things[i]) + emplace!T(&ptr[_length++], el); + } + } + else static if (is(Things[i] : T)) + { + static if (copy_elements) + ptr[_length++] = things[i]; + else + emplace!T(&ptr[_length++], forward!(things[i])); + } + } + return this; + } + } + else + { + // char arrays are really just a string buffer, and so we'll expand the capability of concat to match what `MutableString` accepts... + static assert(is(T == char), "TODO: wchar and dchar"); // needs buffer length counting helpers + + // TODO: string's have this function `concat` which clears the string first, and that's different than Array + // we need to tighten this up! + ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + { + clear(); + append(forward!things); + return this; + } + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) + { + import urt.string.format : _concat = concat; + + size_t ext_len = _concat(null, things).length; + reserve(_length + ext_len); + _concat(ptr[_length .. _length + ext_len], forward!things); + _length += ext_len; + return this; + } + + ref Array!(T, EmbedCount) append_format(Things...)(const(char)[] format, auto ref Things args) + { + import urt.string.format : _format = format; + + size_t ext_len = _format(null, format, args).length; + reserve(_length + ext_len); + _format(ptr[_length .. _length + ext_len], format, forward!args); + _length += ext_len; + return this; + } + } + + T[] extend(bool do_init = true)(size_t length) + { + assert(_length + length <= uint.max); + + size_t old_len = _length; + reserve(_length + length); + static if (do_init) + { + foreach (i; _length .. _length + length) + { + // TODO: replace with palcement new... + static if (copy_elements) + ptr[i] = T.init; + else + emplace!T(&ptr[i]); + } + } + _length += cast(uint)length; + return ptr[old_len .. _length]; + } bool empty() const => _length == 0; @@ -318,26 +458,20 @@ nothrow @nogc: { static if (is(T == class) || is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + reserve(_length + 1); + for (uint i = _length++; i > 0; --i) ptr[i] = ptr[i-1]; - ptr[0] = item; - return ptr[0]; + return (ptr[0] = item); } else { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + reserve(_length + 1); + for (uint i = _length++; i > 0; --i) { moveEmplace!T(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); } - emplace!T(&ptr[0], forward!item); - return ptr[0]; + return *emplace!T(&ptr[0], forward!item); } } @@ -351,45 +485,30 @@ nothrow @nogc: ref T pushBack(U)(auto ref U item) if (is(U : T)) { + reserve(_length + 1); static if (is(T == class) || is(T == interface)) - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - ptr[len] = item; - return ptr[len]; - } + return (ptr[_length++] = item); else - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - emplace!T(&ptr[len], forward!item); - return ptr[len]; - } + return *emplace!T(&ptr[_length++], forward!item); } ref T emplaceFront(Args...)(auto ref Args args) + if (!is(T == class) && !is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + reserve(_length + 1); + for (uint i = _length++; i > 0; --i) { moveEmplace(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); } - emplace!T(&ptr[0], forward!args); - return ptr[0]; + retirn *emplace!T(&ptr[0], forward!args); } ref T emplaceBack(Args...)(auto ref Args args) + if (!is(T == class) && !is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - emplace!T(&ptr[len], forward!args); - return ptr[len]; + reserve(_length + 1); + return *emplace!T(&ptr[_length++], forward!args); } T popFront() @@ -414,7 +533,7 @@ nothrow @nogc: moveEmplace(ptr[i], ptr[i-1]); } destroy!false(ptr[--_length]); - return copy.move; + return copy; } } @@ -424,19 +543,15 @@ nothrow @nogc: static if (is(T == class) || is(T == interface)) { - uint last = _length-1; - T copy = ptr[last]; - ptr[last] = null; - _length = last; + T copy = ptr[--_length]; + ptr[_length] = null; return copy; } else { - uint last = _length-1; - T copy = ptr[last].move; - destroy!false(ptr[last]); - _length = last; - return copy.move; + T copy = ptr[--_length].move; + destroy!false(ptr[_length]); + return copy; } } @@ -466,8 +581,7 @@ nothrow @nogc: static if (is(T == class) || is(T == interface)) { - for (size_t j = i + 1; j < _length; ++j) - ptr[j-1] = ptr[j]; + ptr[i .. _length - 1] = ptr[i + 1 .. _length]; ptr[--_length] = null; } else @@ -475,13 +589,37 @@ nothrow @nogc: destroy!false(ptr[i]); for (size_t j = i + 1; j < _length; ++j) { - emplace!T(&ptr[j-1], ptr[j].move); + moveEmplace(ptr[j], ptr[j-1]); destroy!false(ptr[j]); } --_length; } } + void remove(size_t i, size_t count) + { + debug assert(i + count <= _length); + + static if (is(T == class) || is(T == interface)) + { + ptr[i .. _length - count] = ptr[i + count .. _length]; + ptr[_length - count .. _length] = null; + _length -= cast(uint)count; + } + else + { + for (size_t j = i; j < i + count; ++j) + destroy!false(ptr[j]); + for (size_t j = i + count; j < _length; ++j) + { + moveEmplace(ptr[j], ptr[j - count]); + destroy!false(ptr[j]); + } + } + _length -= cast(uint)count; + } + + void remove(const(T)* pItem) { remove(ptr[0 .. _length].indexOfElement(pItem)); } void removeFirst(U)(ref const U item) { remove(ptr[0 .. _length].findFirst(item)); } @@ -518,45 +656,48 @@ nothrow @nogc: return ptr ? ptr[0 .. allocCount()] : null; } - bool opCast(T : bool)() const + bool opCast(T : bool)() const pure => _length != 0; - size_t opDollar() const + size_t opDollar() const pure => _length; // full slice: arr[] - inout(T)[] opIndex() inout + inout(T)[] opIndex() inout pure => ptr[0 .. _length]; + void opIndexAssign(U)(U[] rh) + { + debug assert(rh.length == _length, "Range error"); + ptr[0 .. _length] = rh[]; + } + // array indexing: arr[i] - ref inout(T) opIndex(size_t i) inout + ref inout(T) opIndex(size_t i) inout pure { debug assert(i < _length, "Range error"); return ptr[i]; } // array slicing: arr[x .. y] - inout(T)[] opIndex(uint[2] i) inout + inout(T)[] opIndex(size_t[2] i) inout pure => ptr[i[0] .. i[1]]; - uint[2] opSlice(size_t dim : 0)(size_t x, size_t y) + void opIndexAssign(U)(U[] rh, size_t[2] i) { - debug assert(y <= _length, "Range error"); - return [cast(uint)x, cast(uint)y]; + debug assert(i[1] <= _length && i[1] - i[0] == rh.length, "Range error"); + ptr[i[0] .. i[1]] = rh[]; } - void opOpAssign(string op : "~", U)(auto ref U el) - if (is(U : T)) + size_t[2] opSlice(size_t dim : 0)(size_t x, size_t y) const pure { - pushBack(forward!el); + debug assert(y <= _length, "Range error"); + return [x, y]; } - void opOpAssign(string op : "~", U)(U[] arr) - if (is(U : T)) + void opOpAssign(string op : "~", U)(auto ref U rh) { - reserve(_length + arr.length); - foreach (ref e; arr) - pushBack(e); + append(forward!rh); } void reserve(size_t count) @@ -638,21 +779,37 @@ nothrow @nogc: _length = 0; } + import urt.string.format : FormatArg, formatValue; + ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const + { + static if (is(T == class) || is(T == interface)) + assert(false, "TODO: class toString is not @nogc!"); + else + return formatValue(ptr[0 .. _length], buffer, format, formatArgs); + } + + ptrdiff_t fromString()(const(char)[] s) + { + assert(false, "TODO"); + } + private: + enum copy_elements = is(T == class) || is(T == interface) || is_primitive!T || is_trivial!T; + T* ptr; uint _length; static if (EmbedCount > 0) T[EmbedCount] embed = void; - bool hasAllocation() const + bool hasAllocation() const pure { static if (EmbedCount > 0) return ptr && ptr != embed.ptr; else return ptr !is null; } - uint allocCount() const + uint allocCount() const pure => hasAllocation() ? (cast(uint*)ptr)[-1] : EmbedCount; T* allocate(uint count) @@ -673,7 +830,7 @@ private: } pragma(inline, true) - static uint numToAlloc(uint i) + static uint numToAlloc(uint i) pure { // TODO: i'm sure we can imagine a better heuristic... return i > 16 ? i * 2 : 16; diff --git a/src/urt/async.d b/src/urt/async.d index fee575f..557f24a 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -20,7 +20,7 @@ Promise!(ReturnType!Fun)* async(alias Fun, size_t stackSize = DefaultStackSize, // TODO: nice to rework this; maybe make stackSize a not-template-arg, and receive a function call/closure object which stores the args Promise!(ReturnType!Fun)* async(size_t stackSize = DefaultStackSize, Fun, Args...)(Fun fun, auto ref Args args) - if (isSomeFunction!Fun) + if (is_some_function!Fun) { alias Result = ReturnType!Fun; Promise!Result* r = cast(Promise!Result*)defaultAllocator().alloc(Promise!Result.sizeof, Promise!Result.alignof); @@ -60,7 +60,7 @@ Promise!(ReturnType!Fun)* async(size_t stackSize = DefaultStackSize, Fun, Args.. void freePromise(T)(ref Promise!T* promise) { - assert(promise.state() != Promise!T.State.Pending, "Promise still pending!"); + assert(promise.state() != PromiseState.Pending, "Promise still pending!"); defaultAllocator().freeT(promise); promise = null; } @@ -79,20 +79,20 @@ void asyncUpdate() if (!t.event.ready()) continue; } - t.resume(); + t.call.fibre.resume(); } } -struct Promise(Result) +enum PromiseState { - enum State - { - Pending, - Ready, - Failed, - } + Pending, + Ready, + Failed +} +struct Promise(Result) +{ // construct using `async()` functions... this() @disable; this(ref typeof(this)) @disable; // disable copy constructor @@ -117,29 +117,29 @@ struct Promise(Result) } } - State state() const + PromiseState state() const { if (async.fibre.wasAborted()) - return State.Failed; + return PromiseState.Failed; else if (async.fibre.isFinished()) - return State.Ready; + return PromiseState.Ready; else - return State.Pending; + return PromiseState.Pending; } bool finished() const - => state() != State.Pending; + => state() != PromiseState.Pending; ref Result result() { - assert(state() == State.Ready, "Promise not fulfilled!"); + assert(state() == PromiseState.Ready, "Promise not fulfilled!"); static if (!is(Result == void)) return value; } void abort() { - assert(state() == State.Pending, "Promise already fulfilled!"); + assert(state() == PromiseState.Pending, "Promise already fulfilled!"); async.fibre.abort(); } @@ -180,11 +180,11 @@ unittest } auto p = async!fun(1, 2); - assert(p.state() == p.State.Ready); + assert(p.state() == PromiseState.Ready); assert(p.result() == 3); freePromise(p); p = async!fun(10, 20); - assert(p.state() == p.State.Ready); + assert(p.state() == PromiseState.Ready); assert(p.result() == 30); freePromise(p); @@ -201,13 +201,13 @@ unittest } auto p_yield = async(&fun_yield); - assert(p_yield.state() == p_yield.State.Pending); + assert(p_yield.state() == PromiseState.Pending); assert(val == 1); asyncUpdate(); - assert(p_yield.state() == p_yield.State.Pending); + assert(p_yield.state() == PromiseState.Pending); assert(val == 2); asyncUpdate(); - assert(p_yield.state() == p_yield.State.Ready); + assert(p_yield.state() == PromiseState.Ready); assert(val == 3); assert(p_yield.result() == 4); freePromise(p_yield); diff --git a/src/urt/conv.d b/src/urt/conv.d index 91f39ab..f0ab5c1 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -1,54 +1,60 @@ module urt.conv; import urt.meta; +import urt.si.quantity; import urt.string; public import urt.string.format : toString; nothrow @nogc: -// on error or not-a-number cases, bytesTaken will contain 0 -long parseInt(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure -{ - size_t i = 0; - bool neg = false; - - if (str.length > 0) - { - char c = str.ptr[0]; - neg = c == '-'; - if (neg || c == '+') - i++; - } +// on error or not-a-number cases, bytes_taken will contain 0 - ulong value = str.ptr[i .. str.length].parseUint(bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; - return neg ? -cast(long)value : cast(long)value; +long parse_int(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + ulong value = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -long parseIntWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - size_t i = 0; - bool neg = false; + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + uint base = parse_base_prefix(p, e); + ulong i = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(i) : long(i); +} - if (str.length > 0) - { - char c = str.ptr[0]; - neg = c == '-'; - if (neg || c == '+') - i++; - } +long parse_int_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); +} - ulong value = str[i .. str.length].parseUintWithDecimal(fixedPointDivisor, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; - return neg ? -cast(long)value : cast(long)value; +long parse_int_with_exponent_and_base(const(char)[] str, out int exponent, out uint base, size_t* bytes_taken = null) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + base = parse_base_prefix(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure { - assert(base > 1 && base <= 36, "Invalid base"); + debug assert(base > 1 && base <= 36, "Invalid base"); ulong value = 0; @@ -69,99 +75,192 @@ ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pur { for (; s < e; ++s) { - uint digit = getDigit(*s); + uint digit = get_digit(*s); if (digit >= base) break; value = value*base + digit; } } - if (bytesTaken) - *bytesTaken = s - str.ptr; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; } -ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - assert(base > 1 && base <= 36, "Invalid base"); + const(char)* s = str.ptr, e = s + str.length, p = s; + uint base = parse_base_prefix(p, e); + ulong i = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return i; +} - ulong value = 0; - ulong divisor = 1; +ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + debug assert(base > 1 && base <= 36, "Invalid base"); const(char)* s = str.ptr; const(char)* e = s + str.length; - // TODO: we could optimise the common base <= 10 case... + ulong value = 0; + int exp = 0; + uint digits = 0; + uint zero_seq = 0; + char c = void; for (; s < e; ++s) { - char c = *s; + c = *s; if (c == '.') { + if (s == str.ptr) + goto done; ++s; + exp = zero_seq; goto parse_decimal; } + else if (c == '0') + { + ++zero_seq; + continue; + } - uint digit = getDigit(c); + uint digit = get_digit(c); if (digit >= base) break; - value = value*base + digit; + + if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + digits += zero_seq; + } + value += digit; + digits += 1; + zero_seq = 0; } - goto done; + + // number has no decimal point, tail zeroes are positive exp + if (!digits) + goto nothing; + + exp = zero_seq; + goto check_exp; parse_decimal: for (; s < e; ++s) { - uint digit = getDigit(*s); - if (digit >= base) + c = *s; + + if (c == '0') { - // if i == 1, then the first char was a '.' and the next was not numeric, so this is not a number! - if (s == str.ptr + 1) - s = str.ptr; + ++zero_seq; + continue; + } + + uint digit = get_digit(c); + if (digit >= base) break; + + if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + digits += zero_seq; } - value = value*base + digit; - divisor *= base; + value += digit; + digits += 1; + exp -= 1 + zero_seq; + zero_seq = 0; + } + if (!digits) + goto nothing; + +check_exp: + // check for exponent part + if (s + 1 < e && ((*s | 0x20) == 'e')) + { + c = s[1]; + bool exp_neg = c == '-'; + if (exp_neg || c == '+') + { + if (s + 2 >= e || !s[2].is_numeric) + goto done; + s += 2; + } + else + { + if (!c.is_numeric) + goto done; + ++s; + } + + int exp_value = 0; + for (; s < e; ++s) + { + uint digit = *s - '0'; + if (digit > 9) + break; + exp_value = exp_value * 10 + digit; + } + exp += exp_neg ? -exp_value : exp_value; } done: - fixedPointDivisor = divisor; - if (bytesTaken) - *bytesTaken = s - str.ptr; + exponent = exp; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; + +nothing: + exp = 0; + goto done; } -ulong parseUintWithBase(const(char)[] str, size_t* bytesTaken = null) pure +ulong parse_uint_with_exponent_and_base(const(char)[] str, out int exponent, out uint base, size_t* bytes_taken = null) pure { - const(char)* p = str.ptr; - int base = parseBasePrefix(str); - ulong i = parseUint(str, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += str.ptr - p; - return i; + const(char)* s = str.ptr, e = s + str.length, p = s; + base = parse_base_prefix(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (value && *bytes_taken != 0) + *bytes_taken += p - s; + return value; } - unittest { size_t taken; - ulong divisor; - assert(parseUint("123") == 123); - assert(parseInt("+123.456") == 123); - assert(parseInt("-123.456", null, 10) == -123); - assert(parseUintWithDecimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); - assert(parseIntWithDecimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); - assert(parseInt("11001", null, 2) == 25); - assert(parseIntWithDecimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); - assert(parseInt("123abc", &taken, 10) == 123 && taken == 3); - assert(parseInt("!!!", &taken, 10) == 0 && taken == 0); - assert(parseInt("-!!!", &taken, 10) == 0 && taken == 0); - assert(parseInt("Wow", &taken, 36) == 42368 && taken == 3); - assert(parseUintWithBase("0x100", &taken) == 0x100 && taken == 5); + assert(parse_uint("123") == 123); + assert(parse_int("+123.456") == 123); + assert(parse_int("-123.456", null, 10) == -123); + assert(parse_int("11001", null, 2) == 25); + assert(parse_int("123abc", &taken, 10) == 123 && taken == 3); + assert(parse_int("!!!", &taken, 10) == 0 && taken == 0); + assert(parse_int("-!!!", &taken, 10) == 0 && taken == 0); + assert(parse_int("Wow", &taken, 36) == 42368 && taken == 3); + assert(parse_uint_with_base("0x100", &taken) == 0x100 && taken == 5); + assert(parse_int_with_base("-0x100", &taken) == -0x100 && taken == 6); + + int e; + assert("0001023000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == 3 && taken == 10); + assert("0.0012003000".parse_uint_with_exponent(e, &taken, 10) == 12003 && e == -7 && taken == 12); + assert("00010.23000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == -2 && taken == 11); + assert("00012300.0".parse_uint_with_exponent(e, &taken, 10) == 123 && e == 2 && taken == 10); + assert("00100.00230".parse_uint_with_exponent(e, &taken, 10) == 1000023 && e == -4 && taken == 11); + assert("0.0".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 3); + assert(".01".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 0); + assert("10e2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 4); + assert("0.01E+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 0 && taken == 7); + assert("0.01E".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01Ex".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01E-".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01E-x".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); } -int parseIntFast(ref const(char)[] text, out bool success) pure +int parse_int_fast(ref const(char)[] text, out bool success) pure { if (!text.length) return 0; @@ -210,30 +309,38 @@ unittest { bool success; const(char)[] text = "123"; - assert(parseIntFast(text, success) == 123 && success == true && text.empty); + assert(parse_int_fast(text, success) == 123 && success == true && text.empty); text = "-2147483648abc"; - assert(parseIntFast(text, success) == -2147483648 && success == true && text.length == 3); + assert(parse_int_fast(text, success) == -2147483648 && success == true && text.length == 3); text = "2147483648"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); text = "-2147483649"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); text = "2147483650"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); } -// on error or not-a-number, result will be nan and bytesTaken will contain 0 -double parseFloat(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +// on error or not-a-number, result will be nan and bytes_taken will contain 0 +double parse_float(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure { - // TODO: E-notation... - size_t taken = void; - ulong div = void; - long value = str.parseIntWithDecimal(div, &taken, base); - if (bytesTaken) - *bytesTaken = taken; + import urt.math : pow; + + int e; + size_t taken; + long mantissa = str.parse_int_with_exponent(e, &taken, base); + if (bytes_taken) + *bytes_taken = taken; if (taken == 0) return double.nan; - return cast(double)value / div; + + // TODO: the real work needs to happen here! + // we want all the bits of precision! + + if (__ctfe) + return mantissa * double(base)^^e; + else + return mantissa * pow(base, e); } unittest @@ -245,26 +352,159 @@ unittest } size_t taken; - assert(fcmp(parseFloat("123.456"), 123.456)); - assert(fcmp(parseFloat("+123.456"), 123.456)); - assert(fcmp(parseFloat("-123.456.789"), -123.456)); - assert(fcmp(parseFloat("1101.11", &taken, 2), 13.75) && taken == 7); - assert(parseFloat("xyz", &taken) is double.nan && taken == 0); + assert(fcmp(parse_float("123.456"), 123.456)); + assert(fcmp(parse_float("+123.456"), 123.456)); + assert(fcmp(parse_float("-123.456.789"), -123.456)); + assert(fcmp(parse_float("-123.456e10"), -1.23456e+12)); + assert(fcmp(parse_float("1101.11", &taken, 2), 13.75) && taken == 7); + assert(parse_float("xyz", &taken) is double.nan && taken == 0); } +VarQuantity parse_quantity(const(char)[] text, size_t* bytes_taken = null) nothrow +{ + import urt.si.unit; -ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure + int e; + uint base; + size_t taken; + long raw_value = text.parse_int_with_exponent_and_base(e, base, &taken); + if (taken == 0) + { + if (bytes_taken) + *bytes_taken = 0; + return VarQuantity(double.nan); + } + + // we parsed a number! + auto r = VarQuantity(e == 0 ? raw_value : raw_value * double(base)^^e); + + if (taken < text.length) + { + // try and parse a unit... + ScaledUnit su; + float pre_scale; + ptrdiff_t unit_taken = su.parse_unit(text[taken .. $], pre_scale, false); + if (unit_taken > 0) + { + taken += unit_taken; + r = VarQuantity(r.value * pre_scale, su); + } + } + if (bytes_taken) + *bytes_taken = taken; + return r; +} + +unittest +{ + import urt.si.unit; + + size_t taken; + assert("10V".parse_quantity(&taken) == Volts(10) && taken == 3); + assert("10.2e+2Wh".parse_quantity(&taken) == WattHours(1020) && taken == 9); +} + + +ptrdiff_t parse(T)(const char[] text, out T result) +{ + import urt.array : beginsWith; + import urt.traits; + + alias UT = Unqual!T; + + static if (is(UT == bool)) + { + if (text.beginsWith("true")) + { + result = true; + return 4; + } + result = false; + if (text.beginsWith("false")) + return 5; + return -1; + } + else static if (is_some_int!T) + { + size_t taken; + static if (is_signed_int!T) + long r = text.parse_int(&taken); + else + ulong r = text.parse_uint(&taken); + if (!taken) + return -1; + if (r >= T.min && r <= T.max) + { + result = cast(T)r; + return taken; + } + return -2; + } + else static if (is_some_float!T) + { + size_t taken; + double f = text.parse_float(&taken); + if (!taken) + return -1; + result = cast(T)f; + return taken; + } + else static if (is_enum!T) + { + static assert(false, "TODO: do we want to parse from enum keys?"); + // case-sensitive? + } + else static if (is(T == struct) && __traits(compiles, { result.fromString(text); })) + { + return result.fromString(text); + } + else + static assert(false, "Cannot parse " ~ T.stringof ~ " from string"); +} + +unittest +{ + { + bool r; + assert("true".parse(r) == 4 && r == true); + assert("false".parse(r) == 5 && r == false); + assert("wow".parse(r) == -1); + } + { + int r; + assert("-10".parse(r) == 3 && r == -10); + } + { + ubyte r; + assert("10".parse(r) == 2 && r == 10); + assert("-10".parse(r) == -1); + assert("257".parse(r) == -2); + } + { + float r; + assert("10".parse(r) == 2 && r == 10.0f); + assert("-2.5".parse(r) == 4 && r == -2.5f); + } + { + import urt.inet; + IPAddr r; + assert("10.0.0.1".parse(r) == 8 && r == IPAddr(10,0,0,1)); + } +} + + +ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool show_sign = false) pure { const bool neg = value < 0; - showSign |= neg; + show_sign |= neg; - if (buffer.ptr && buffer.length < showSign) + if (buffer.ptr && buffer.length < show_sign) return -1; ulong i = neg ? -value : value; - ptrdiff_t r = formatUint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); - if (r < 0 || !showSign) + ptrdiff_t r = format_uint(i, buffer.ptr ? buffer.ptr[(width == 0 ? show_sign : 0) .. buffer.length] : null, base, width, fill); + if (r < 0 || !show_sign) return r; if (buffer.ptr) @@ -286,10 +526,10 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c if (buffer.ptr[0] == fill) { // we don't need to shift it left... - size_t sgnOffset = 0; - while (buffer.ptr[sgnOffset + 1] == fill) - ++sgnOffset; - buffer.ptr[sgnOffset] = sgn; + size_t sgn_offset = 0; + while (buffer.ptr[sgn_offset + 1] == fill) + ++sgn_offset; + buffer.ptr[sgn_offset] = sgn; return r; } @@ -309,20 +549,20 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c return r + 1; } -ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ') pure +ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ') pure { import urt.util : max; assert(base >= 2 && base <= 36, "Invalid base"); ulong i = value; - uint numLen = 0; + uint num_len = 0; char[64] t = void; if (i == 0) { if (buffer.length > 0) t.ptr[0] = '0'; - numLen = 1; + num_len = 1; } else { @@ -334,14 +574,14 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, if (buffer.ptr) { int d = cast(int)(i % base); - t.ptr[numLen] = cast(char)((d < 10 ? '0' : 'A' - 10) + d); + t.ptr[num_len] = cast(char)((d < 10 ? '0' : 'A' - 10) + d); } - ++numLen; + ++num_len; } } - uint len = max(numLen, width); - uint padding = width > numLen ? width - numLen : 0; + uint len = max(num_len, width); + uint padding = width > num_len ? width - num_len : 0; if (buffer.ptr) { @@ -351,7 +591,7 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, size_t offset = 0; while (padding--) buffer.ptr[offset++] = fill; - for (uint j = numLen; j > 0; ) + for (uint j = num_len; j > 0; ) buffer.ptr[offset++] = t[--j]; } return len; @@ -360,36 +600,36 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, unittest { char[64] buffer; - assert(formatInt(0, null) == 1); - assert(formatInt(14, null) == 2); - assert(formatInt(14, null, 16) == 1); - assert(formatInt(-14, null) == 3); - assert(formatInt(-14, null, 16) == 2); - assert(formatInt(-14, null, 16, 3, '0') == 3); - assert(formatInt(-123, null, 10, 6) == 6); - assert(formatInt(-123, null, 10, 3) == 4); - assert(formatInt(-123, null, 10, 2) == 4); - - size_t len = formatInt(0, buffer); + assert(format_int(0, null) == 1); + assert(format_int(14, null) == 2); + assert(format_int(14, null, 16) == 1); + assert(format_int(-14, null) == 3); + assert(format_int(-14, null, 16) == 2); + assert(format_int(-14, null, 16, 3, '0') == 3); + assert(format_int(-123, null, 10, 6) == 6); + assert(format_int(-123, null, 10, 3) == 4); + assert(format_int(-123, null, 10, 2) == 4); + + size_t len = format_int(0, buffer); assert(buffer[0 .. len] == "0"); - len = formatInt(14, buffer); + len = format_int(14, buffer); assert(buffer[0 .. len] == "14"); - len = formatInt(14, buffer, 2); + len = format_int(14, buffer, 2); assert(buffer[0 .. len] == "1110"); - len = formatInt(14, buffer, 8, 3); + len = format_int(14, buffer, 8, 3); assert(buffer[0 .. len] == " 16"); - len = formatInt(14, buffer, 16, 4, '0'); + len = format_int(14, buffer, 16, 4, '0'); assert(buffer[0 .. len] == "000E"); - len = formatInt(-14, buffer, 16, 3, '0'); + len = format_int(-14, buffer, 16, 3, '0'); assert(buffer[0 .. len] == "-0E"); - len = formatInt(12345, buffer, 10, 3); + len = format_int(12345, buffer, 10, 3); assert(buffer[0 .. len] == "12345"); - len = formatInt(-123, buffer, 10, 6); + len = format_int(-123, buffer, 10, 6); assert(buffer[0 .. len] == " -123"); } -ptrdiff_t formatFloat(double value, char[] buffer, const(char)[] format = null) // pure +ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) // pure { // TODO: this function should be oblitereated and implemented natively... // CRT call can't CTFE, which is a shame @@ -424,9 +664,9 @@ template to(T) { long to(const(char)[] str) { - int base = parseBasePrefix(str); + uint base = parse_base_prefix(str); size_t taken; - long r = parseInt(str, &taken, base); + long r = parse_int(str, &taken, base); assert(taken == str.length, "String is not numeric"); return r; } @@ -435,19 +675,19 @@ template to(T) { double to(const(char)[] str) { - int base = parseBasePrefix(str); + uint base = parse_base_prefix(str); size_t taken; - double r = parseFloat(str, &taken, base); + double r = parse_float(str, &taken, base); assert(taken == str.length, "String is not numeric"); return r; } } - else static if (isSomeInt!T) // call-through for other int types; reduce instantiation bloat + else static if (is_some_int!T) // call-through for other int types; reduce instantiation bloat { T to(const(char)[] str) => cast(T)to!long(str); } - else static if (isSomeFloat!T) // call-through for other float types; reduce instantiation bloat + else static if (is_some_float!T) // call-through for other float types; reduce instantiation bloat { T to(const(char)[] str) => cast(T)to!double(str); @@ -479,38 +719,46 @@ template to(T) private: -uint getDigit(char c) pure +// valid result is 0 .. 35; result is garbage outside that bound +uint get_digit(char c) pure { - uint zeroBase = c - '0'; - if (zeroBase < 10) - return zeroBase; - uint ABase = c - 'A'; - if (ABase < 26) - return ABase + 10; - uint aBase = c - 'a'; - if (aBase < 26) - return aBase + 10; - return -1; + uint zero_base = c - '0'; + if (zero_base < 10) + return zero_base; + uint a_base = (c | 0x20) - 'a'; + return 10 + (a_base & 0xFF); } -int parseBasePrefix(ref const(char)[] str) pure +uint parse_base_prefix(ref const(char)* str, const(char)* end) pure { - int base = 10; - if (str.length >= 2) + uint base = 10; + if (str + 2 <= end && str[0] == '0') { - if (str[0..2] == "0x") - base = 16, str = str[2..$]; - else if (str[0..2] == "0b") - base = 2, str = str[2..$]; - else if (str[0..2] == "0o") - base = 8, str = str[2..$]; + if (str[1] == 'x') + base = 16, str += 2; + else if (str[1] == 'b') + base = 2, str += 2; + else if (str[1] == 'o') + base = 8, str += 2; } return base; } +uint parse_sign(ref const(char)* str, const(char)* end) pure +{ + if (str == end) + return 0; + // NOTE: ascii is '+' = 43, '-' = 45 + uint neg = *str - '+'; + if (neg > 2 || neg == 1) + return 0; + ++str; + return neg; // neg is 0 (+) or 2 (-) +} + /+ -size_t formatStruct(T)(ref T value, char[] buffer) nothrow @nogc +size_t format_struct(T)(ref T value, char[] buffer) nothrow @nogc { import urt.string.format; @@ -518,7 +766,7 @@ size_t formatStruct(T)(ref T value, char[] buffer) nothrow @nogc alias args = value.tupleof; // alias args = AliasSeq!(value.tupleof); -// alias args = InterleaveSeparator!(", ", value.tupleof); +// alias args = INTERLEAVE_SEPARATOR!(", ", value.tupleof); // pragma(msg, args); return concat(buffer, args).length; } @@ -530,7 +778,7 @@ unittest Packet p; char[1024] buffer; - size_t len = formatStruct(p, buffer); + size_t len = format_struct(p, buffer); assert(buffer[0 .. len] == "Packet()"); } diff --git a/src/urt/crc.d b/src/urt/crc.d index 5010e3f..b26b6c9 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -1,51 +1,51 @@ module urt.crc; -import urt.meta : intForWidth; -import urt.traits : isUnsignedInt; +import urt.meta : IntForWidth; +import urt.traits : is_unsigned_int; nothrow @nogc: enum Algorithm : ubyte { - CRC16_USB, - CRC16_MODBUS, - CRC16_KERMIT, - CRC16_XMODEM, - CRC16_CCITT_FALSE, - CRC16_ISO_HDLC, - CRC16_DNP, - CRC32_ISO_HDLC, - CRC32_CASTAGNOLI, - - CRC16_Default_ShortPacket = CRC16_KERMIT, // good default choice for small packets - CRC32_Default = CRC32_CASTAGNOLI, // has SSE4.2 hardware implementation - - // aliases - CRC16_BLUETOOTH = CRC16_KERMIT, - CRC16_CCITT_TRUE = CRC16_KERMIT, - CRC16_CCITT = CRC16_KERMIT, - CRC16_EZSP = CRC16_CCITT_FALSE, - CRC16_IBM_SDLC = CRC16_ISO_HDLC, - CRC32_NVME = CRC32_CASTAGNOLI, + crc16_usb, + crc16_modbus, + crc16_kermit, + crc16_xmodem, + crc16_ccitt_false, + crc16_iso_hdlc, + crc16_dnp, + crc32_iso_hdlc, + crc32_castagnoli, + + crc16_default_short_packet = crc16_kermit, // good default choice for small packets + crc32_default = crc32_castagnoli, // has SSE4.2 hardware implementation + + // aliases + crc16_bluetooth = crc16_kermit, + crc16_ccitt_true = crc16_kermit, + crc16_ccitt = crc16_kermit, + crc16_ezsp = crc16_ccitt_false, + crc16_ibm_sdlc = crc16_iso_hdlc, + crc32_nvme = crc32_castagnoli, } -struct CRCParams +struct crc_params { ubyte width; bool reflect; uint poly; uint initial; - uint finalXor; + uint final_xor; uint check; } -alias CRCTable(Algorithm algo) = CRCTable!(paramTable[algo].width, paramTable[algo].poly, paramTable[algo].reflect); -alias CRCType(Algorithm algo) = intForWidth!(paramTable[algo].width); +alias CRCType(Algorithm algo) = IntForWidth!(param_table[algo].width); +alias crc_table(Algorithm algo) = crc_table!(param_table[algo].width, param_table[algo].poly, param_table[algo].reflect); // compute a CRC with runtime parameters -T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure - if (isUnsignedInt!T) +T calculate_crc(T = uint)(const void[] data, ref const crc_params params, ref const T[256] table) pure + if (is_unsigned_int!T) { assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); @@ -64,22 +64,22 @@ T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref cons crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); } - return crc ^ cast(T)params.finalXor; + return crc ^ cast(T)params.final_xor; } // compute a CRC with hard-coded parameters -T calculateCRC(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)paramTable[algo].initial^paramTable[algo].finalXor) pure - if (isUnsignedInt!T) +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)param_table[algo].initial^param_table[algo].final_xor) pure + if (is_unsigned_int!T) { - enum CRCParams params = paramTable[algo]; + enum crc_params params = param_table[algo]; static assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); - alias table = CRCTable!algo; + alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - static if (params.finalXor) - T crc = initial ^ params.finalXor; + static if (params.final_xor) + T crc = initial ^ params.final_xor; else T crc = initial; @@ -91,32 +91,32 @@ T calculateCRC(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); } - static if (params.finalXor) - return T(crc ^ params.finalXor); + static if (params.final_xor) + return T(crc ^ params.final_xor); else return crc; } // computes 2 CRC's for 2 points in the data stream... -T calculateCRC_2(Algorithm algo, T = intForWidth!(paramTable[algo].width*2))(const void[] data, uint earlyOffset) pure - if (isUnsignedInt!T) +T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(const void[] data, uint early_offset) pure + if (is_unsigned_int!T) { - enum CRCParams params = paramTable[algo]; + enum crc_params params = param_table[algo]; static assert(params.width * 2 <= T.sizeof*8, "T is too small for the CRC width"); - alias table = CRCTable!algo; + alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - T highCRC = 0; + T high_crc = 0; T crc = cast(T)params.initial; size_t i = 0; for (; i < bytes.length; ++i) { - if (i == earlyOffset) + if (i == early_offset) { - highCRC = crc; + high_crc = crc; goto fast_loop; // skips a redundant loop entry check } static if (params.reflect) @@ -136,27 +136,27 @@ T calculateCRC_2(Algorithm algo, T = intForWidth!(paramTable[algo].width*2))(con } done: - static if (params.finalXor) + static if (params.final_xor) { - crc ^= cast(T)params.finalXor; - highCRC ^= cast(T)params.finalXor; + crc ^= cast(T)params.final_xor; + high_crc ^= cast(T)params.final_xor; } static if (params.width <= 8) - return ushort(crc | highCRC << 8); + return ushort(crc | high_crc << 8); else static if (params.width <= 16) - return uint(crc | highCRC << 16); + return uint(crc | high_crc << 16); else if (params.width <= 32) - return ulong(crc | highCRC << 32); + return ulong(crc | high_crc << 32); } -T[256] generateCRCTable(T)(ref const CRCParams params) pure - if (isUnsignedInt!T) +T[256] generate_crc_table(T)(ref const crc_params params) pure + if (is_unsigned_int!T) { - enum typeWidth = T.sizeof * 8; - assert(params.width <= typeWidth && params.width > typeWidth/2, "CRC width must match the size of the type"); - T topBit = cast(T)(1 << (params.width - 1)); + enum type_width = T.sizeof * 8; + assert(params.width <= type_width && params.width > type_width/2, "CRC width must match the size of the type"); + T top_bit = cast(T)(1 << (params.width - 1)); T[256] table = void; @@ -169,7 +169,7 @@ T[256] generateCRCTable(T)(ref const CRCParams params) pure crc <<= (params.width - 8); // Shift to align with the polynomial width foreach (_; 0..8) { - if ((crc & topBit) != 0) + if ((crc & top_bit) != 0) crc = cast(T)((crc << 1) ^ params.poly); else crc <<= 1; @@ -189,38 +189,38 @@ unittest { immutable ubyte[9] checkData = ['1','2','3','4','5','6','7','8','9']; - assert(calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[]) == paramTable[Algorithm.CRC16_MODBUS].check); - assert(calculateCRC!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); - assert(calculateCRC!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); - assert(calculateCRC!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); - assert(calculateCRC!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); - assert(calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - assert(calculateCRC!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); - assert(calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); - assert(calculateCRC!(Algorithm.CRC32_CASTAGNOLI)(checkData[]) == paramTable[Algorithm.CRC32_CASTAGNOLI].check); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[]) == param_table[Algorithm.crc16_modbus].check); + assert(calculate_crc!(Algorithm.crc16_ezsp)(checkData[]) == param_table[Algorithm.crc16_ezsp].check); + assert(calculate_crc!(Algorithm.crc16_kermit)(checkData[]) == param_table[Algorithm.crc16_kermit].check); + assert(calculate_crc!(Algorithm.crc16_usb)(checkData[]) == param_table[Algorithm.crc16_usb].check); + assert(calculate_crc!(Algorithm.crc16_xmodem)(checkData[]) == param_table[Algorithm.crc16_xmodem].check); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[]) == param_table[Algorithm.crc16_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc16_dnp)(checkData[]) == param_table[Algorithm.crc16_dnp].check); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[]) == param_table[Algorithm.crc32_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc32_castagnoli)(checkData[]) == param_table[Algorithm.crc32_castagnoli].check); // check that rolling CRC works... - ushort crc = calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_MODBUS].check); - crc = calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - uint crc32 = calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[5 .. 9], crc32) == paramTable[Algorithm.CRC32_ISO_HDLC].check); + ushort crc = calculate_crc!(Algorithm.crc16_modbus)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[5 .. 9], crc) == param_table[Algorithm.crc16_modbus].check); + crc = calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[5 .. 9], crc) == param_table[Algorithm.crc16_iso_hdlc].check); + uint crc32 = calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[5 .. 9], crc32) == param_table[Algorithm.crc32_iso_hdlc].check); } private: -enum CRCParams[] paramTable = [ - CRCParams(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // CRC16_USB - CRCParams(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // CRC16_MODBUS - CRCParams(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // CRC16_KERMIT - CRCParams(16, false, 0x1021, 0x0000, 0x0000, 0x31C3), // CRC16_XMODEM - CRCParams(16, false, 0x1021, 0xFFFF, 0x0000, 0x29B1), // CRC16_CCITT_FALSE - CRCParams(16, true, 0x1021, 0xFFFF, 0xFFFF, 0x906E), // CRC16_ISO_HDLC - CRCParams(16, true, 0x3D65, 0x0000, 0xFFFF, 0xEA82), // CRC16_DNP - CRCParams(32, true, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 0xCBF43926), // CRC32_ISO_HDLC - CRCParams(32, true, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, 0xE3069283), // CRC32_CASTAGNOLI +enum crc_params[] param_table = [ + crc_params(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // crc16_usb + crc_params(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // crc16_modbus + crc_params(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // crc16_kermit + crc_params(16, false, 0x1021, 0x0000, 0x0000, 0x31C3), // crc16_xmodem + crc_params(16, false, 0x1021, 0xFFFF, 0x0000, 0x29B1), // crc16_ccitt_false + crc_params(16, true, 0x1021, 0xFFFF, 0xFFFF, 0x906E), // crc16_iso_hdlc + crc_params(16, true, 0x3D65, 0x0000, 0xFFFF, 0xEA82), // crc16_dnp + crc_params(32, true, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 0xCBF43926), // crc32_iso_hdlc + crc_params(32, true, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, 0xE3069283), // crc32_castagnoli ]; // helper function to reflect bits (reverse bit order) @@ -236,7 +236,7 @@ T reflect(T)(T value, ubyte bits) } // this minimises the number of table instantiations -template CRCTable(uint width, uint poly, bool reflect) +template crc_table(uint width, uint poly, bool reflect) { - __gshared immutable CRCTable = generateCRCTable!(intForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); + __gshared immutable crc_table = generate_crc_table!(IntForWidth!width)(crc_params(width, reflect, poly, 0, 0, 0)); } diff --git a/src/urt/crypto/chacha.d b/src/urt/crypto/chacha.d new file mode 100644 index 0000000..2f263db --- /dev/null +++ b/src/urt/crypto/chacha.d @@ -0,0 +1,299 @@ +module urt.crypto.chacha; + +import urt.endian : littleEndianToNative, nativeToLittleEndian; +import urt.mem; + +nothrow @nogc: + + +struct ChaChaContext +{ + uint[16] state; + union { + uint[16] keystream32; + ubyte[64] keystream8; + } + uint nonceLow; + ushort position; + ushort rounds; +} + + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[16] key, ulong nonce, ulong counter = 0, uint rounds = 20) pure +{ + chacha_init_context(ctx, key, expandNonce(nonce), counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[32] key, ulong nonce, ulong counter = 0, uint rounds = 20) pure +{ + chacha_init_context(ctx, key, expandNonce(nonce), counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[16] key, ref const ubyte[12] nonce, ulong counter = 0, uint rounds = 20) pure +{ + ubyte[32] key256 = void; + key256[0 .. 16] = key[]; + key256[16 .. 32] = key[]; + chacha_init_context(ctx, key256, nonce, counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[32] key, ref const ubyte[12] nonce, ulong counter = 0, uint rounds = 20) pure +{ + // the number of rounds must be 8, 12 or 20 + assert (rounds == 8 || rounds == 12 || rounds == 20); + ctx.rounds = cast(ushort)rounds / 2; + + ctx.nonceLow = nonce[0..4].littleEndianToNative!uint; + + // the string "expand 32-byte k" in little-endian + enum uint[4] magic_constant = [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 ]; + + ctx.state[0] = magic_constant[0]; + ctx.state[1] = magic_constant[1]; + ctx.state[2] = magic_constant[2]; + ctx.state[3] = magic_constant[3]; + ctx.state[4] = key[0..4].littleEndianToNative!uint; + ctx.state[5] = key[4..8].littleEndianToNative!uint; + ctx.state[6] = key[8..12].littleEndianToNative!uint; + ctx.state[7] = key[12..16].littleEndianToNative!uint; + ctx.state[8] = key[16..20].littleEndianToNative!uint; + ctx.state[9] = key[20..24].littleEndianToNative!uint; + ctx.state[10] = key[24..28].littleEndianToNative!uint; + ctx.state[11] = key[28..32].littleEndianToNative!uint; + ctx.state[12] = cast(uint)counter; + ctx.state[13] = ctx.nonceLow | (counter >> 32); + ctx.state[14] = nonce[4..8].littleEndianToNative!uint; + ctx.state[15] = nonce[8..12].littleEndianToNative!uint; + + ctx.keystream32[] = 0; + + // set starting position to the end of the key buffer; there are no bytes to consume yet + ctx.position = 64; +} + +static void chacha_set_counter(ref ChaChaContext ctx, ulong counter) pure +{ + ctx.state[12] = cast(uint)counter; + ctx.state[13] = ctx.nonceLow | (counter >> 32); +} + +void chacha_free_context(ref ChaChaContext ctx) pure +{ + ctx.state[] = 0; + ctx.keystream32[] = 0; + ctx.position = 0; + ctx.nonceLow = 0; + ctx.rounds = 0; +} + +size_t chacha_update(ref ChaChaContext ctx, const ubyte[] input, ubyte[] output) pure +{ + import urt.util : min; + + size_t size = input.length; + assert(output.length >= size); + + size_t offset = 0; + if (ctx.position < 64) + { + size_t startBytes = min(size, 64 - ctx.position); + output[0 .. startBytes] = input[0 .. startBytes] ^ ctx.keystream8[ctx.position .. 64]; + ctx.position += startBytes; + size -= startBytes; + offset = startBytes; + } + while (size >= 64) + { + chacha_block_next(ctx); + output[offset .. offset + 64] = input[offset .. offset + 64] ^ ctx.keystream8[]; + offset += 64; + size -= 64; + } + if (size > 0) + { + chacha_block_next(ctx); + output[offset .. offset + size] = input[offset .. offset + size] ^ ctx.keystream8[0 .. size]; + ctx.position = cast(ushort)size; + } + + return input.length; +} + + +size_t chacha_crypt(const ubyte[] input, ubyte[] output, ref const ubyte[32] key, ulong nonce, ulong counter = 0) pure +{ + return chacha_crypt(input, output, key, expandNonce(nonce), counter); +} + +size_t chacha_crypt(const ubyte[] input, ubyte[] output, ref const ubyte[32] key, ref const ubyte[12] nonce, ulong counter = 0) pure +{ + assert(output.length >= input.length); + + ChaChaContext ctx; + ctx.chacha_init_context(key, nonce, counter); + size_t r = ctx.chacha_update(input, output); + ctx.chacha_free_context(); + return r; +} + + +private: + +ubyte[12] expandNonce(ulong nonce) pure +{ + ubyte[12] nonceBytes = void; + nonceBytes[0 .. 4] = 0; + nonceBytes[4 .. 12] = nativeToLittleEndian(nonce); + return nonceBytes; +} + +pragma(inline, true) +uint rotl32(int n)(uint x) pure + => (x << n) | (x >> (32 - n)); + +pragma(inline, true) +void chacha_quarter_round(uint a, uint b, uint c, uint d)(ref uint[16] state) pure +{ + state[a] += state[b]; state[d] = rotl32!16(state[d] ^ state[a]); + state[c] += state[d]; state[b] = rotl32!12(state[b] ^ state[c]); + state[a] += state[b]; state[d] = rotl32!8(state[d] ^ state[a]); + state[c] += state[d]; state[b] = rotl32!7(state[b] ^ state[c]); +} + +void chacha_block_next(ref ChaChaContext ctx) pure +{ + // this is where the crazy voodoo magic happens. + // mix the bytes a lot and hope that nobody finds out how to undo it. + ctx.keystream32 = ctx.state; + + // TODO: we might like to unroll this...? + foreach (i; 0 .. ctx.rounds) + { + chacha_quarter_round!(0, 4, 8, 12)(ctx.keystream32); + chacha_quarter_round!(1, 5, 9, 13)(ctx.keystream32); + chacha_quarter_round!(2, 6, 10, 14)(ctx.keystream32); + chacha_quarter_round!(3, 7, 11, 15)(ctx.keystream32); + + chacha_quarter_round!(0, 5, 10, 15)(ctx.keystream32); + chacha_quarter_round!(1, 6, 11, 12)(ctx.keystream32); + chacha_quarter_round!(2, 7, 8, 13)(ctx.keystream32); + chacha_quarter_round!(3, 4, 9, 14)(ctx.keystream32); + } + + ctx.keystream32[] += ctx.state[]; + + // increment counter + uint* counter = &ctx.state[12]; + counter[0]++; + if (counter[0] == 0) + { + // wrap around occured, increment higher 32 bits of counter + counter[1]++; + // limited to 2^64 blocks of 64 bytes each. + // if you want to process more than 1180591620717411303424 bytes, you have other problems. + // we could keep counting with counter[2] and counter[3] (nonce), but then we risk reusing the nonce which is very bad! + assert(counter[1] != 0); + } +} + + +unittest +{ + import urt.encoding; + + immutable ubyte[32] test1_key = HexDecode!"0000000000000000000000000000000000000000000000000000000000000000"; + immutable ubyte[12] test1_nonce = HexDecode!"000000000000000000000000"; + immutable ubyte[64] test1_input = 0; + immutable ubyte[64] test1_output = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86 + ]; + + immutable ubyte[32] test2_key = HexDecode!"0000000000000000000000000000000000000000000000000000000000000001"; + immutable ubyte[12] test2_nonce = HexDecode!"000000000000000000000002"; + immutable ubyte[375] test2_input = [ + 0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d,0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x45,0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74,0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20, 0x66,0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61,0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6f, 0x72, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66,0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x20, + 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61,0x6e, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74,0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x49,0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20,0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, + 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x22, 0x49,0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e,0x22, 0x2e, 0x20, 0x53, 0x75, 0x63, 0x68, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e,0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20,0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45,0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x61, 0x73, 0x20,0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, 0x20, + 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20,0x61, 0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x63, 0x20, 0x63,0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61,0x64, 0x65, 0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x6f,0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c, + 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61,0x72, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f + ]; + immutable ubyte[375] test2_output = [ + 0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde,0x4f, 0x37, 0x6c, 0xa2, 0x3e, 0x82, 0x73, 0x70, + 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f, 0x57, 0xbd,0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec, + 0x2a, 0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15,0xc8, 0xf3, 0xd3, 0x37, 0xf7, 0xd3, 0x70, 0x05, + 0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3, 0x9f,0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d, + 0x40, 0x42, 0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa,0x4b, 0x4b, 0xb5, 0xe8, 0xea, 0xd0, 0x44, 0x0e, + 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7,0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50, + 0x42, 0xbd, 0xfa, 0x77, 0x73, 0xd8, 0xa9, 0x05,0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1, 0x41, 0x1c, + 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05,0xb7, 0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a, + 0xd0, 0x0f, 0x84, 0x49, 0xed, 0x8f, 0x72, 0xd0,0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca, 0x66, + 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4,0x1f, 0x43, 0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d, + 0xc4, 0xb2, 0xd0, 0xdf, 0xb4, 0x8a, 0x6c, 0x91,0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28, + 0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87,0x9d, 0x7b, 0x35, 0xd4, 0x9e, 0xb2, 0xe6, 0x2b, + 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89, 0x39, 0xe2,0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f, + 0xa8, 0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76,0x59, 0x89, 0xcb, 0xcf, 0x3d, 0xaa, 0x8b, 0x6c, + 0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9, 0x2b,0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84, + 0xa1, 0xbe, 0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd,0xa2, 0x36, 0xe7, 0xe8, 0x18, 0xb0, 0x4b, 0x0b, + 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe,0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0, + 0x8a, 0xaa, 0x9b, 0x63, 0xd1, 0xa1, 0x6f, 0x80,0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c, 0x41, 0x1f, + 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3,0x6f, 0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62, + 0xbe, 0xbc, 0xfd, 0x2d, 0xc5, 0xbc, 0xe0, 0x91,0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6, 0xe6, + 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64,0x77, 0x33, 0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85, + 0x14, 0xea, 0x99, 0x82, 0xcc, 0xaf, 0xb3, 0x41,0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab, + 0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba,0x5b, 0x86, 0x2f, 0x37, 0x30, 0xe3, 0x7c, 0xfd, + 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2, 0x21 + ]; + + immutable ubyte[32] test3_key = HexDecode!"c46ec1b18ce8a878725a37e780dfb7351f68ed2e194c79fbc6aebee1a667975d"; + enum ulong test3_nonce = 0x218268cfd531da1a; + immutable ubyte[127] test3_input = 0; + immutable ubyte[127] test3_output = [ + 0xf6, 0x3a, 0x89, 0xb7, 0x5c, 0x22, 0x71, 0xf9,0x36, 0x88, 0x16, 0x54, 0x2b, 0xa5, 0x2f, 0x06, + 0xed, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2b, 0x00,0xb5, 0xe8, 0xf8, 0x0a, 0xe9, 0xa4, 0x73, 0xaf, + 0xc2, 0x5b, 0x21, 0x8f, 0x51, 0x9a, 0xf0, 0xfd,0xd4, 0x06, 0x36, 0x2e, 0x8d, 0x69, 0xde, 0x7f, + 0x54, 0xc6, 0x04, 0xa6, 0xe0, 0x0f, 0x35, 0x3f,0x11, 0x0f, 0x77, 0x1b, 0xdc, 0xa8, 0xab, 0x92, + 0xe5, 0xfb, 0xc3, 0x4e, 0x60, 0xa1, 0xd9, 0xa9,0xdb, 0x17, 0x34, 0x5b, 0x0a, 0x40, 0x27, 0x36, + 0x85, 0x3b, 0xf9, 0x10, 0xb0, 0x60, 0xbd, 0xf1,0xf8, 0x97, 0xb6, 0x29, 0x0f, 0x01, 0xd1, 0x38, + 0xae, 0x2c, 0x4c, 0x90, 0x22, 0x5b, 0xa9, 0xea,0x14, 0xd5, 0x18, 0xf5, 0x59, 0x29, 0xde, 0xa0, + 0x98, 0xca, 0x7a, 0x6c, 0xcf, 0xe6, 0x12, 0x27,0x05, 0x3c, 0x84, 0xe4, 0x9a, 0x4a, 0x33 + ]; + + ubyte[375] output = void; + + size_t ret = test1_input.chacha_crypt(output, test1_key, test1_nonce); + assert(ret == test1_input.length); + assert(output[0 .. test1_output.length] == test1_output[]); + + ChaChaContext ctx; + ctx.chacha_init_context(test2_key, test2_nonce, 1); + ret = ctx.chacha_update(test2_input[0 .. 17], output[0 .. 17]); + ret += ctx.chacha_update(test2_input[17 .. $], output[17 .. $]); + assert(ret == test2_input.length); + assert(output[0 .. test2_output.length] == test2_output[]); + + ret = test3_input.chacha_crypt(output, test3_key, test3_nonce); + assert(ret == test3_input.length); + assert(output[0 .. test3_output.length] == test3_output[]); +} diff --git a/src/urt/dbg.d b/src/urt/dbg.d index 8cc13d1..189100d 100644 --- a/src/urt/dbg.d +++ b/src/urt/dbg.d @@ -61,7 +61,7 @@ else private: -package(urt) void setupAssertHandler() +package(urt) void setup_assert_handler() { import core.exception : assertHandler; assertHandler = &urt_assert; diff --git a/src/urt/digest/md5.d b/src/urt/digest/md5.d index ef75d98..e0a59d6 100644 --- a/src/urt/digest/md5.d +++ b/src/urt/digest/md5.d @@ -14,13 +14,13 @@ struct MD5Context enum uint[4] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]; } -void md5Init(ref MD5Context ctx) +void md5_init(ref MD5Context ctx) { ctx.size = 0; ctx.buffer = MD5Context.initState; } -void md5Update(ref MD5Context ctx, const void[] input) +void md5_update(ref MD5Context ctx, const void[] input) { size_t offset = ctx.size % 64; ctx.size += input.length; @@ -41,7 +41,7 @@ void md5Update(ref MD5Context ctx, const void[] input) uint[16] tmp = void; foreach (uint j; 0 .. 16) tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); - md5Step(ctx.buffer, tmp); + md5_step(ctx.buffer, tmp); size_t tail = input.length - i; if (tail < 64) @@ -54,7 +54,7 @@ void md5Update(ref MD5Context ctx, const void[] input) } } -ubyte[16] md5Finalise(ref MD5Context ctx) +ubyte[16] md5_finalise(ref MD5Context ctx) { uint[16] tmp = void; uint offset = ctx.size % 64; @@ -67,7 +67,7 @@ ubyte[16] md5Finalise(ref MD5Context ctx) PADDING[1 .. padding_length] = 0; // Fill in the padding and undo the changes to size that resulted from the update - md5Update(ctx, PADDING[0 .. padding_length]); + md5_update(ctx, PADDING[0 .. padding_length]); ctx.size -= cast(ulong)padding_length; // Do a final update (internal to this function) @@ -78,7 +78,7 @@ ubyte[16] md5Finalise(ref MD5Context ctx) tmp[14] = cast(uint)(ctx.size*8); tmp[15] = (ctx.size*8) >> 32; - md5Step(ctx.buffer, tmp); + md5_step(ctx.buffer, tmp); uint[4] digest = void; foreach (uint k; 0 .. 4) @@ -91,14 +91,14 @@ unittest import urt.encoding; MD5Context ctx; - md5Init(ctx); - auto digest = md5Finalise(ctx); - assert(digest == Hex!"d41d8cd98f00b204e9800998ecf8427e"); - - md5Init(ctx); - md5Update(ctx, "Hello, World!"); - digest = md5Finalise(ctx); - assert(digest == Hex!"65a8e27d8879283831b664bd8b7f0ad4"); + md5_init(ctx); + auto digest = md5_finalise(ctx); + assert(digest == HexDecode!"d41d8cd98f00b204e9800998ecf8427e"); + + md5_init(ctx); + md5_update(ctx, "Hello, World!"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); } @@ -131,11 +131,11 @@ __gshared immutable uint[] K = [ ]; // rotates a 32-bit word left by n bits -uint rotateLeft(uint x, uint n) +uint rotate_left(uint x, uint n) => (x << n) | (x >> (32 - n)); // step on 512 bits of input with the main MD5 algorithm -void md5Step(ref uint[4] buffer, ref const uint[16] input) +void md5_step(ref uint[4] buffer, ref const uint[16] input) { uint AA = buffer[0]; uint BB = buffer[1]; @@ -171,7 +171,7 @@ void md5Step(ref uint[4] buffer, ref const uint[16] input) uint temp = DD; DD = CC; CC = BB; - BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); + BB = BB + rotate_left(AA + E + K[i] + input[j], S[i]); AA = temp; } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 196c835..671a6fa 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -17,7 +17,7 @@ struct SHA1Context uint datalen; uint[DigestElements] state; - alias transform = sha1Transform; + alias transform = sha1_transform; enum uint[DigestElements] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; @@ -36,7 +36,7 @@ struct SHA256Context uint datalen; uint[DigestElements] state; - alias transform = sha256Transform; + alias transform = sha256_transform; enum uint[DigestElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; @@ -54,14 +54,14 @@ struct SHA256Context } -void shaInit(Context)(ref Context ctx) +void sha_init(Context)(ref Context ctx) { ctx.datalen = 0; ctx.bitlen = 0; ctx.state = Context.initState; } -void shaUpdate(Context)(ref Context ctx, const void[] input) +void sha_update(Context)(ref Context ctx, const void[] input) { const(ubyte)[] data = cast(ubyte[])input; @@ -82,7 +82,7 @@ void shaUpdate(Context)(ref Context ctx, const void[] input) } } -ubyte[Context.DigestLen] shaFinalise(Context)(ref Context ctx) +ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) { uint i = ctx.datalen; @@ -105,7 +105,7 @@ ubyte[Context.DigestLen] shaFinalise(Context)(ref Context ctx) // reverse all the bytes when copying the final state to the output hash. uint[Context.DigestElements] digest = void; foreach (uint j; 0 .. Context.DigestElements) - digest[j] = byteReverse(ctx.state[j]); + digest[j] = byte_reverse(ctx.state[j]); return cast(ubyte[Context.DigestLen])digest; } @@ -115,24 +115,24 @@ unittest import urt.encoding; SHA1Context ctx; - shaInit(ctx); - auto digest = shaFinalise(ctx); - assert(digest == Hex!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + sha_init(ctx); + auto digest = sha_finalise(ctx); + assert(digest == HexDecode!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); - shaInit(ctx); - shaUpdate(ctx, "Hello, World!"); - digest = shaFinalise(ctx); - assert(digest == Hex!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + sha_init(ctx); + sha_update(ctx, "Hello, World!"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); SHA256Context ctx2; - shaInit(ctx2); - auto digest2 = shaFinalise(ctx2); - assert(digest2 == Hex!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - - shaInit(ctx2); - shaUpdate(ctx2, "Hello, World!"); - digest2 = shaFinalise(ctx2); - assert(digest2 == Hex!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + sha_init(ctx2); + auto digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + + sha_init(ctx2); + sha_update(ctx2, "Hello, World!"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); } @@ -141,7 +141,7 @@ private: uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); -void sha1Transform(ref SHA1Context ctx, const ubyte[] data) +void sha1_transform(ref SHA1Context ctx, const ubyte[] data) { uint a, b, c, d, e, i, j, t; uint[80] m = void; @@ -204,7 +204,7 @@ void sha1Transform(ref SHA1Context ctx, const ubyte[] data) ctx.state[4] += e; } -void sha256Transform(ref SHA256Context ctx, const ubyte[] data) +void sha256_transform(ref SHA256Context ctx, const ubyte[] data) { static uint CH(uint x, uint y, uint z) => (x & y) ^ (~x & z); static uint MAJ(uint x, uint y, uint z) => (x & y) ^ (x & z) ^ (y & z); diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 36ced1b..9f76aa0 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -3,12 +3,16 @@ module urt.encoding; nothrow @nogc: -enum Hex(const char[] s) = (){ ubyte[s.length / 2] r; ptrdiff_t len = hex_decode(s, r); assert(len == r.sizeof, "Not a hex string!"); return r; }(); -enum Base64(const char[] s) = (){ ubyte[base64_decode_length(s)] r; ptrdiff_t len = base64_decode(s, r); assert(len == r.sizeof, "Not a base64 string!"); return r; }(); +enum Base64Decode(string str) = () { ubyte[base64_decode_length(str.length)] r; size_t len = base64_decode(str, r[]); assert(len == r.sizeof, "Not a base64 string: " ~ str); return r; }(); +enum HexDecode(string str) = () { ubyte[hex_decode_length(str.length)] r; size_t len = hex_decode(str, r[]); assert(len == r.sizeof, "Not a hex string: " ~ str); return r; }(); +enum URLDecode(string str) = () { char[url_decode_length(str)] r; size_t len = url_decode(str, r[]); assert(len == r.sizeof, "Not a URL encoded string: " ~ str); return r; }(); -ptrdiff_t base64_encode_length(size_t sourceLength) pure - => (sourceLength + 2) / 3 * 4; +size_t base64_encode_length(size_t source_length) pure + => (source_length + 2) / 3 * 4; + +size_t base64_encode_length(const void[] data) pure + => base64_encode_length(data.length); ptrdiff_t base64_encode(const void[] data, char[] result) pure { @@ -54,8 +58,11 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure return out_len; } -ptrdiff_t base64_decode_length(size_t sourceLength) pure -=> sourceLength / 4 * 3; +size_t base64_decode_length(size_t source_length) pure + => source_length / 4 * 3; + +size_t base64_decode_length(const char[] data) pure + => base64_decode_length(data.length); ptrdiff_t base64_decode(const char[] data, void[] result) pure { @@ -74,6 +81,9 @@ ptrdiff_t base64_decode(const char[] data, void[] result) pure size_t j = 0; while (i < len) { + if (i > len - 4) + return -1; + // TODO: this could be faster by using more memory, store a full 256-byte table and no comparisons... uint b0 = data[i++] - 43; uint b1 = data[i++] - 43; @@ -88,10 +98,10 @@ ptrdiff_t base64_decode(const char[] data, void[] result) pure if (b3 >= 80) return -1; - b0 = base64_map.ptr[b0]; - b1 = base64_map.ptr[b1]; - b2 = base64_map.ptr[b2]; - b3 = base64_map.ptr[b3]; + b0 = base64_map[b0]; + b1 = base64_map[b1]; + b2 = base64_map[b2]; + b3 = base64_map[b3]; dest[j++] = cast(ubyte)((b0 << 2) | (b1 >> 4)); if (b2 != 64) @@ -109,6 +119,8 @@ unittest char[16] encoded = void; ubyte[12] decoded = void; + static assert(Base64Decode!"AQIDBAUGBwgJCgsM" == data[]); + size_t len = base64_encode(data, encoded); assert(len == 16); assert(encoded == "AQIDBAUGBwgJCgsM"); @@ -129,10 +141,13 @@ unittest len = base64_decode(encoded, decoded); assert(len == 10); assert(data[0..10] == decoded[0..10]); - -// static assert(Base64!"012345" == [0x01, 0x23, 0x45]); } +size_t hex_encode_length(size_t sourceLength) pure + => sourceLength * 2; + +size_t hex_encode_length(const void[] data) pure + => data.length * 2; ptrdiff_t hex_encode(const void[] data, char[] result) pure { @@ -142,9 +157,15 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure return toHexString(data, result).length; } +size_t hex_decode_length(size_t sourceLength) pure + => sourceLength / 2; + +size_t hex_decode_length(const char[] data) pure + => data.length / 2; + ptrdiff_t hex_decode(const char[] data, void[] result) pure { - import urt.string.ascii : isHex; + import urt.string.ascii : is_hex; if (data.length & 1) return -1; @@ -157,7 +178,7 @@ ptrdiff_t hex_decode(const char[] data, void[] result) pure { ubyte c0 = data[i]; ubyte c1 = data[i + 1]; - if (!c0.isHex || !c1.isHex) + if (!c0.is_hex || !c1.is_hex) return -1; if ((c0 | 0x20) >= 'a') @@ -180,25 +201,25 @@ unittest char[24] encoded = void; ubyte[12] decoded = void; + static assert(HexDecode!"0102030405060708090A0B0C" == data); + size_t len = hex_encode(data, encoded); assert(len == 24); assert(encoded == "0102030405060708090A0B0C"); len = hex_decode(encoded, decoded); assert(len == 12); assert(data == decoded); - - static assert(Hex!"012345" == [0x01, 0x23, 0x45]); } -ptrdiff_t url_encode_length(const char[] data) pure +size_t url_encode_length(const char[] data) pure { - import urt.string.ascii : isURL; + import urt.string.ascii : is_url; size_t len = 0; foreach (c; data) { - if (c.isURL || c == ' ') + if (c.is_url || c == ' ') ++len; else len += 3; @@ -208,14 +229,14 @@ ptrdiff_t url_encode_length(const char[] data) pure ptrdiff_t url_encode(const char[] data, char[] result) pure { - import urt.string.ascii : isURL, hexDigits; + import urt.string.ascii : is_url, hex_digits; size_t j = 0; for (size_t i = 0; i < data.length; ++i) { char c = data[i]; - if (c.isURL || c == ' ') + if (c.is_url || c == ' ') { if (j == result.length) return -1; @@ -226,15 +247,15 @@ ptrdiff_t url_encode(const char[] data, char[] result) pure if (j + 2 == result.length) return -1; result[j++] = '%'; - result[j++] = hexDigits[c >> 4]; - result[j++] = hexDigits[c & 0xF]; + result[j++] = hex_digits[c >> 4]; + result[j++] = hex_digits[c & 0xF]; } } return j; } -ptrdiff_t url_decode_length(const char[] data) pure +size_t url_decode_length(const char[] data) pure { size_t len = 0; for (size_t i = 0; i < data.length;) @@ -250,7 +271,7 @@ ptrdiff_t url_decode_length(const char[] data) pure ptrdiff_t url_decode(const char[] data, char[] result) pure { - import urt.string.ascii : isHex; + import urt.string.ascii : is_hex; size_t j = 0; for (size_t i = 0; i < data.length; ++i) @@ -268,7 +289,7 @@ ptrdiff_t url_decode(const char[] data, char[] result) pure ubyte c0 = data[i + 1]; ubyte c1 = data[i + 2]; - if (!c0.isHex || !c1.isHex) + if (!c0.is_hex || !c1.is_hex) return -1; i += 2; @@ -290,6 +311,8 @@ ptrdiff_t url_decode(const char[] data, char[] result) pure unittest { + static assert(URLDecode!"Hello%2C+World%21" == "Hello, World!"); + char[13] data = "Hello, World!"; char[17] encoded = void; char[13] decoded = void; diff --git a/src/urt/endian.d b/src/urt/endian.d index e76f671..2ee3da5 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -4,20 +4,20 @@ import urt.processor; import urt.traits; public import urt.processor : LittleEndian; -public import urt.util : byteReverse; +public import urt.util : byte_reverse; pure nothrow @nogc: // load from byte arrays pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[1] bytes) - if (T.sizeof == 1 && isIntegral!T) + if (T.sizeof == 1 && is_integral!T) { return cast(T)bytes[0]; } ushort endianToNative(T, bool little)(ref const ubyte[2] bytes) - if (T.sizeof == 2 && isIntegral!T) + if (T.sizeof == 2 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -33,12 +33,12 @@ ushort endianToNative(T, bool little)(ref const ubyte[2] bytes) static if (LittleEndian == little) return *cast(ushort*)bytes.ptr; else - return byteReverse(*cast(ushort*)bytes.ptr); + return byte_reverse(*cast(ushort*)bytes.ptr); } } uint endianToNative(T, bool little)(ref const ubyte[4] bytes) - if (T.sizeof == 4 && isIntegral!T) + if (T.sizeof == 4 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -54,12 +54,12 @@ uint endianToNative(T, bool little)(ref const ubyte[4] bytes) static if (LittleEndian == little) return *cast(uint*)bytes.ptr; else - return byteReverse(*cast(uint*)bytes.ptr); + return byte_reverse(*cast(uint*)bytes.ptr); } } ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) - if (T.sizeof == 8 && isIntegral!T) + if (T.sizeof == 8 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -75,15 +75,15 @@ ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) static if (LittleEndian == little) return *cast(ulong*)bytes.ptr; else - return byteReverse(*cast(ulong*)bytes.ptr); + return byte_reverse(*cast(ulong*)bytes.ptr); } } pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) - if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) + if (!is_integral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U u = endianToNative!(U, little)(bytes); return *cast(T*)&u; } @@ -96,12 +96,12 @@ T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) static assert(!is(U == class) && !is(U == interface) && !is(U == V*, V), T.stringof ~ " is not POD"); static if (U.sizeof == 1) - r = *cast(T*)&bytes; + return *cast(T*)&bytes; else { T r; - for (size_t i = 0, j = 0; i < N; ++i, j += T.sizeof) - r[i] = endianToNative!(U, little)(bytes.ptr[j .. j + T.sizeof][0 .. T.sizeof]); + for (size_t i = 0, j = 0; i < N; ++i, j += U.sizeof) + r[i] = endianToNative!(U, little)(bytes.ptr[j .. j + U.sizeof][0 .. U.sizeof]); return r; } } @@ -156,7 +156,7 @@ ubyte[2] nativeToEndian(bool little)(ushort u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[2]*)&u; @@ -176,7 +176,7 @@ ubyte[4] nativeToEndian(bool little)(uint u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[4]*)&u; @@ -196,7 +196,7 @@ ubyte[8] nativeToEndian(bool little)(ulong u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[8]*)&u; @@ -204,12 +204,11 @@ ubyte[8] nativeToEndian(bool little)(ulong u) } pragma(inline, true) auto nativeToEndian(bool little, T)(T val) - if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) + if (!is_integral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); - U r = nativeToEndian!little(*cast(U*)&val); - return *cast(T*)&r; + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); + return nativeToEndian!little(*cast(U*)&val); } ubyte[T.sizeof] nativeToEndian(bool little, T)(auto ref const T data) @@ -261,36 +260,36 @@ ubyte[T.sizeof] nativeToLittleEndian(T)(auto ref const T data) // load/store from/to memory void storeBigEndian(T)(T* target, const T val) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) *target = val; else - *target = byteReverse(val); + *target = byte_reverse(val); } void storeLittleEndian(T)(T* target, const T val) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) *target = val; else - *target = byteReverse(val); + *target = byte_reverse(val); } T loadBigEndian(T)(const(T)* src) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) return *src; else - return byteReverse(*src); + return byte_reverse(*src); } T loadLittleEndian(T)(const(T)* src) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) return *src; else - return byteReverse(*src); + return byte_reverse(*src); } diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 574e551..38a5579 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -2,7 +2,7 @@ module urt.fibre; import urt.mem; import urt.time; -import urt.util : isAligned, max; +import urt.util : is_aligned, max; version (Windows) version = UseWindowsFibreAPI; @@ -49,7 +49,7 @@ struct Fibre mainFibre = co_active(); // TODO: i think it's a bug that this stuff isn't initialised! - isDelegate = false; + is_delegate = false; abortRequested = false; finished = true; // init in a state ready to be recycled... aborted = false; @@ -62,7 +62,7 @@ struct Fibre while (true) { try { - if (thisFibre.isDelegate) + if (thisFibre.is_delegate) { FibreEntryDelegate dg; dg.ptr = thisFibre.userData; @@ -84,6 +84,8 @@ struct Fibre } catch (Throwable e) { + import urt.log; + writeDebugf("fibre abort: {0}:{1} - {2}", e.file, e.line, e.msg); abort(); } @@ -104,7 +106,7 @@ struct Fibre { this(cast(FibreEntryFunc)fibreEntry.funcptr, yieldHandler, fibreEntry.ptr, stackSize); - isDelegate = true; + is_delegate = true; } this(FibreEntryFunc fibreEntry, YieldHandler yieldHandler, void* userData = null, size_t stackSize = DefaultStackSize) nothrow @@ -151,7 +153,7 @@ struct Fibre this.fibreEntry = cast(FibreEntryFunc)fibreEntry.funcptr; userData = fibreEntry.ptr; - isDelegate = true; + is_delegate = true; abortRequested = false; finished = false; aborted = false; @@ -163,7 +165,7 @@ struct Fibre this.fibreEntry = fibreEntry; this.userData = userData; - isDelegate = false; + is_delegate = false; abortRequested = false; finished = false; aborted = false; @@ -198,7 +200,7 @@ private: YieldHandler yieldHandler; cothread_t fibre; - bool isDelegate; + bool is_delegate; bool abortRequested; bool finished; bool aborted; @@ -570,7 +572,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** sp = cast(void**)top; // seek to top of stack *--sp = &crash; // crash if entrypoint returns @@ -741,7 +743,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** p = cast(void**)base; p[8] = cast(void*)top; // starting sp @@ -804,7 +806,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** p = cast(void**)base; p[0] = cast(void*)top; // x16 (stack pointer) diff --git a/src/urt/file.d b/src/urt/file.d index e7ec4d3..37dfad0 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -133,17 +133,17 @@ Result delete_file(const(char)[] path) version (Windows) { if (!DeleteFileW(path.twstringz)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { if (unlink(path.tstringz) == -1) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result rename_file(const(char)[] oldPath, const(char)[] newPath) @@ -151,18 +151,18 @@ Result rename_file(const(char)[] oldPath, const(char)[] newPath) version (Windows) { if (!MoveFileW(oldPath.twstringz, newPath.twstringz)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { import core.sys.posix.stdio; if (int result = rename(oldPath.tstringz, newPath.tstringz)!= 0) - return PosixResult(result); + return posix_result(result); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExisting = false) @@ -170,7 +170,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi version (Windows) { if (!CopyFileW(oldPath.twstringz, newPath.twstringz, !overwriteExisting)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { @@ -180,7 +180,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result get_path(ref const File file, ref char[] buffer) @@ -193,11 +193,11 @@ Result get_path(ref const File file, ref char[] buffer) DWORD dwPathLen = tmp.length - 1; DWORD result = GetFinalPathNameByHandleW(cast(HANDLE)file.handle, tmp.ptr, dwPathLen, FILE_NAME_OPENED); if (result == 0 || result > dwPathLen) - return Win32Result(GetLastError()); + return getlasterror_result(); - size_t pathLen = tmp[0..result].uniConvert(buffer); + size_t pathLen = tmp[0..result].uni_convert(buffer); if (!pathLen) - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; if (buffer.length >= 4 && buffer[0..4] == `\\?\`) buffer = buffer[4..pathLen]; else @@ -210,10 +210,10 @@ Result get_path(ref const File file, ref char[] buffer) char[PATH_MAX] src = void; int r = fcntl(file.fd, F_GETPATH, src.ptr); if (r == -1) - return PosixResult(errno); + return errno_result(); size_t l = strlen(src.ptr); if (l > buffer.length) - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; buffer[0..l] = src[0..l]; buffer = buffer[0..l]; } @@ -221,18 +221,18 @@ Result get_path(ref const File file, ref char[] buffer) { ptrdiff_t r = readlink(tconcat("/proc/self/fd/", file.fd, '\0').ptr, buffer.ptr, buffer.length); if (r == -1) - return PosixResult(errno); + return errno_result(); if (r == buffer.length) { // TODO: if r == buffer.length, truncation MAY have occurred, but also maybe not... // is there any way to fix this? for now, we'll just assume it did and return an error - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; } buffer = buffer[0..r]; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result set_file_times(ref File file, const SystemTime* createTime, const SystemTime* accessTime, const SystemTime* writeTime); @@ -243,7 +243,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) { WIN32_FILE_ATTRIBUTE_DATA attrData = void; if (!GetFileAttributesExW(path.twstringz, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &attrData)) - return Win32Result(GetLastError()); + return getlasterror_result(); outAttributes.attributes = FileAttributeFlag.None; if ((attrData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) @@ -270,7 +270,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result get_attributes(ref const File file, out FileAttributes outAttributes) @@ -282,9 +282,9 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) FILE_BASIC_INFO basicInfo = void; FILE_STANDARD_INFO standardInfo = void; if (!GetFileInformationByHandleEx(cast(HANDLE)file.handle, FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo, &basicInfo, FILE_BASIC_INFO.sizeof)) - return Win32Result(GetLastError()); + return getlasterror_result(); if (!GetFileInformationByHandleEx(cast(HANDLE)file.handle, FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, &standardInfo, FILE_STANDARD_INFO.sizeof)) - return Win32Result(GetLastError()); + return getlasterror_result(); outAttributes.attributes = FileAttributeFlag.None; if ((basicInfo.FileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) @@ -303,7 +303,7 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) outAttributes.accessTime = SysTime(basicInfo.LastAccessTime.QuadPart); outAttributes.writeTime = SysTime(basicInfo.LastWriteTime.QuadPart); - return Result.Success; + return Result.success; +/ } else version (Posix) @@ -314,14 +314,14 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) else static assert(0, "Not implemented"); - return InternalResult(InternalCode.Unsupported); + return InternalResult.unsupported; } void[] load_file(const(char)[] path, NoGCAllocator allocator = defaultAllocator()) { File f; Result r = f.open(path, FileOpenMode.ReadExisting); - if (!r && r.get_FileResult == FileResult.NotFound) + if (!r && r.file_result == FileResult.NotFound) return null; assert(r, "TODO: handle error"); ulong size = f.get_size(); @@ -379,7 +379,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags dwCreationDisposition = OPEN_ALWAYS; break; default: - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; } uint dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; @@ -392,7 +392,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags file.handle = CreateFileW(path.twstringz, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, null); if (file.handle == INVALID_HANDLE_VALUE) - return Win32Result(GetLastError()); + return getlasterror_result(); if (mode == FileOpenMode.WriteAppend || mode == FileOpenMode.ReadWriteAppend) SetFilePointer(file.handle, 0, null, FILE_END); @@ -429,7 +429,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags flags = O_RDWR | O_APPEND | O_CREAT; break; default: - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; } flags |= O_CLOEXEC; @@ -441,7 +441,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags int fd = core.sys.posix.fcntl.open(path.tstringz, flags, 0b110_110_110); if (fd < 0) - return PosixResult(errno); + return errno_result(); file.fd = fd; version (Darwin) { @@ -467,7 +467,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } bool is_open(ref const File file) @@ -532,7 +532,7 @@ Result set_size(ref File file, ulong size) if (size > curFileSize) { if (!file.set_pos(curFileSize)) - return Win32Result(GetLastError()); + return getlasterror_result(); // zero-fill char[4096] buf = void; @@ -555,19 +555,19 @@ Result set_size(ref File file, ulong size) else { if (!file.set_pos(size)) - return Win32Result(GetLastError()); + return getlasterror_result(); if (!SetEndOfFile(file.handle)) - return Win32Result(GetLastError()); + return getlasterror_result(); } } else version (Posix) { if (ftruncate(file.fd, size)) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } ulong get_pos(ref const File file) @@ -593,17 +593,17 @@ Result set_pos(ref File file, ulong offset) LARGE_INTEGER liDistanceToMove = void; liDistanceToMove.QuadPart = offset; if (!SetFilePointerEx(file.handle, liDistanceToMove, null, FILE_BEGIN)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { off_t rc = lseek(file.fd, offset, SEEK_SET); if (rc < 0) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result read(ref File file, void[] buffer, out size_t bytesRead) @@ -616,7 +616,7 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) if (!ReadFile(file.handle, buffer.ptr, cast(uint)min(buffer.length, uint.max), &dwBytesRead, null)) { DWORD lastError = GetLastError(); - return (lastError == ERROR_BROKEN_PIPE) ? Result.Success : Win32Result(lastError); + return (lastError == ERROR_BROKEN_PIPE) ? Result.success : win32_result(lastError); } bytesRead = dwBytesRead; } @@ -624,12 +624,12 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) { ptrdiff_t n = core.sys.posix.unistd.read(file.fd, buffer.ptr, buffer.length); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesRead = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) @@ -637,7 +637,7 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) version (Windows) { if (buffer.length > DWORD.max) - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; OVERLAPPED o; o.Offset = cast(DWORD)offset; @@ -646,8 +646,9 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) DWORD dwBytesRead; if (!ReadFile(file.handle, buffer.ptr, cast(DWORD)buffer.length, &dwBytesRead, &o)) { - if (GetLastError() != ERROR_HANDLE_EOF) - return Win32Result(GetLastError()); + Result error = getlasterror_result(); + if (error.systemCode != ERROR_HANDLE_EOF) + return error; } bytesRead = dwBytesRead; } @@ -655,12 +656,12 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) { ssize_t n = pread(file.fd, buffer.ptr, buffer.length, offset); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesRead = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result write(ref File file, const(void)[] data, out size_t bytesWritten) @@ -669,19 +670,19 @@ Result write(ref File file, const(void)[] data, out size_t bytesWritten) { DWORD dwBytesWritten; if (!WriteFile(file.handle, data.ptr, cast(uint)data.length, &dwBytesWritten, null)) - return Win32Result(GetLastError()); + return getlasterror_result(); bytesWritten = dwBytesWritten; } else version (Posix) { ptrdiff_t n = core.sys.posix.unistd.write(file.fd, data.ptr, data.length); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesWritten = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result write_at(ref File file, const(void)[] data, ulong offset, out size_t bytesWritten) @@ -689,7 +690,7 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte version (Windows) { if (data.length > DWORD.max) - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; OVERLAPPED o; o.Offset = cast(DWORD)offset; @@ -697,19 +698,19 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte DWORD dwBytesWritten; if (!WriteFile(file.handle, data.ptr, cast(DWORD)data.length, &dwBytesWritten, &o)) - return Win32Result(GetLastError()); + return getlasterror_result(); bytesWritten = dwBytesWritten; } else version (Posix) { ptrdiff_t n = pwrite(file.fd, data.ptr, data.length, offset); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesWritten = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result flush(ref File file) @@ -717,19 +718,19 @@ Result flush(ref File file) version (Windows) { if (!FlushFileBuffers(file.handle)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { if (fsync(file.fd)) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } -FileResult get_FileResult(Result result) +FileResult file_result(Result result) { version (Windows) { @@ -771,13 +772,13 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] wchar[MAX_PATH] tmp = void; if (!GetTempFileNameW(dstDir.twstringz, prefix.twstringz, 0, tmp.ptr)) - return Win32Result(GetLastError()); + return getlasterror_result(); size_t resLen = wcslen(tmp.ptr); - resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uniConvert(buffer); + resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uni_convert(buffer); if (resLen == 0) { DeleteFileW(tmp.ptr); - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; } buffer = buffer[0 .. resLen]; } @@ -788,25 +789,14 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] File file; file.fd = mkstemp(fn.ptr); if (file.fd == -1) - return PosixResult(errno); + return errno_result(); Result r = get_path(file, buffer); core.sys.posix.unistd.close(file.fd); return r; } else static assert(0, "Not implemented"); - return Result.Success; -} - -version (Windows) -{ - Result Win32Result(uint err) - => Result(err); -} -else version (Posix) -{ - Result PosixResult(int err) - => Result(err); + return Result.success; } diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 544fbdf..5866a3c 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -13,38 +13,19 @@ public import urt.variant; nothrow @nogc: -Variant parseJson(const(char)[] text) +Variant parse_json(const(char)[] text) { - return parseNode(text); + return parse_node(text); } -ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, uint level = 0, uint indent = 2) +ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, uint level = 0, uint indent = 2) { final switch (val.type) { case Variant.Type.Null: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - - case Variant.Type.False: - if (!buffer.ptr) - return 5; - if (buffer.length < 5) - return -1; - buffer[0 .. 5] = "false"; - return 5; - case Variant.Type.True: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "true"; - return 4; + case Variant.Type.False: + return val.toString(buffer, null, null); case Variant.Type.Map: case Variant.Type.Array: @@ -76,9 +57,9 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui int inc = val.type == Variant.Type.Map ? 2 : 1; for (uint i = 0; i < val.count; i += inc) { - len += writeJson(val.value.n[i], null, dense, level + indent, indent); + len += write_json(val.value.n[i], null, dense, level + indent, indent); if (val.type == Variant.Type.Map) - len += writeJson(val.value.n[i + 1], null, dense, level + indent, indent); + len += write_json(val.value.n[i + 1], null, dense, level + indent, indent); } return len; } @@ -99,7 +80,7 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui if (!buffer.newline(written, level + indent)) return -1; } - ptrdiff_t len = writeJson(val.value.n[i], buffer[written .. $], dense, level + indent, indent); + ptrdiff_t len = write_json(val.value.n[i], buffer[written .. $], dense, level + indent, indent); if (len < 0) return -1; written += len; @@ -107,7 +88,7 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui { if (!buffer.append(written, ':') || (!dense && !buffer.append(written, ' '))) return -1; - len = writeJson(val.value.n[i + 1], buffer[written .. $], dense, level + indent, indent); + len = write_json(val.value.n[i + 1], buffer[written .. $], dense, level + indent, indent); if (len < 0) return -1; written += len; @@ -119,17 +100,99 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui return -1; return written; - case Variant.Type.String: + case Variant.Type.Buffer: + if (!val.isString) + { + import urt.encoding; + + // emit raw buffer as base64 + const data = val.asBuffer(); + size_t enc_len = base64_encode_length(data.length); + if (buffer.ptr) + { + if (buffer.length < 2 + enc_len) + return -1; + buffer[0] = '"'; + ptrdiff_t r = data.base64_encode(buffer[1 .. 1 + enc_len]); + if (r != enc_len) + return -2; + buffer[1 + enc_len] = '"'; + } + return 2 + enc_len; + } + const char[] s = val.asString(); - if (buffer.ptr) + + if (!buffer.ptr) + { + size_t len = 0; + foreach (c; s) + { + if (c < 0x20) + { + if (c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') + len += 2; + else + len += 6; + } + else if (c == '"' || c == '\\') + len += 2; + else + len += 1; + } + return len + 2; + } + + if (buffer.length < s.length + 2) + return -1; + + buffer[0] = '"'; + // escape strings + size_t offset = 1; + foreach (c; s) { - if (buffer.length < s.length + 2) + char sub = void; + if (c < 0x20) + { + if (c == '\n') + sub = 'n'; + else if (c == '\r') + sub = 'r'; + else if (c == '\t') + sub = 't'; + else if (c == '\b') + sub = 'b'; + else if (c == '\f') + sub = 'f'; + else + { + if (buffer.length < offset + 7) + return -1; + buffer[offset .. offset + 4] = "\\u00"; + offset += 4; + buffer[offset++] = hex_digits[c >> 4]; + buffer[offset++] = hex_digits[c & 0xF]; + continue; + } + } + else if (c == '"' || c == '\\') + sub = c; + else + { + if (buffer.length < offset + 2) + return -1; + buffer[offset++] = c; + continue; + } + + // write escape sequence + if (buffer.length < offset + 3) return -1; - buffer[0] = '"'; - buffer[1 .. 1 + s.length] = s[]; - buffer[1 + s.length] = '"'; + buffer[offset++] = '\\'; + buffer[offset++] = sub; } - return s.length + 2; + buffer[offset++] = '"'; + return offset; case Variant.Type.Number: import urt.conv; @@ -138,21 +201,29 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui assert(false, "TODO: implement quantity formatting for JSON"); if (val.isDouble()) - return val.asDouble().formatFloat(buffer); + return val.asDouble().format_float(buffer); // TODO: parse args? //format if (val.isUlong()) - return val.asUlong().formatUint(buffer); - return val.asLong().formatInt(buffer); + return val.asUlong().format_uint(buffer); + return val.asLong().format_int(buffer); case Variant.Type.User: - // in order to text-ify a user type, we probably need a hash table of text-ify functions, which - // we can lookup by the typeId... - // the tricky bit is, we need to init the table based on instantiations of constructors for each T... - // maybe an object with static constructor for each instantiation, which will hook it into the table? - assert(false, "TODO..."); + // for custom types, we'll use the type's regular string format into a json string + if (!buffer.ptr) + return val.toString(null, null, null) + 2; + if (buffer.length < 1) + return -1; + buffer[0] = '\"'; + ptrdiff_t len = val.toString(buffer[1 .. $], null, null); + if (len < 0) + return len; + if (buffer.length < len + 2) + return -1; + buffer[1 + len] = '\"'; + return len + 2; } } @@ -179,7 +250,7 @@ unittest ] }`; - Variant root = parseJson(doc); + Variant root = parse_json(doc); // check the data was parsed correctly... assert(root["nothing"].isNull); @@ -197,18 +268,18 @@ unittest char[1024] buffer = void; // check the dense writer... - assert(root["children"].writeJson(null, true) == 61); - assert(root["children"].writeJson(buffer, true) == 61); + assert(root["children"].write_json(null, true) == 61); + assert(root["children"].write_json(buffer, true) == 61); assert(buffer[0 .. 61] == `[{"name":"Jane Doe", "age":12}, {"name":"Jack Doe", "age":8}]`); // check the expanded writer - assert(root["children"].writeJson(null, false, 0, 1) == 83); - assert(root["children"].writeJson(buffer, false, 0, 1) == 83); + assert(root["children"].write_json(null, false, 0, 1) == 83); + assert(root["children"].write_json(buffer, false, 0, 1) == 83); assert(buffer[0 .. 83] == "[\n {\n \"name\": \"Jane Doe\",\n \"age\": 12\n },\n {\n \"name\": \"Jack Doe\",\n \"age\": 8\n }\n]"); // check indentation works properly - assert(root["children"].writeJson(null, false, 0, 2) == 95); - assert(root["children"].writeJson(buffer, false, 0, 2) == 95); + assert(root["children"].write_json(null, false, 0, 2) == 95); + assert(root["children"].write_json(buffer, false, 0, 2) == 95); assert(buffer[0 .. 95] == "[\n {\n \"name\": \"Jane Doe\",\n \"age\": 12\n },\n {\n \"name\": \"Jack Doe\",\n \"age\": 8\n }\n]"); // fabricate a JSON object @@ -220,7 +291,7 @@ unittest assert(write[0].asInt == 42); assert(write[1]["wow"].isTrue); assert(write[1]["bogus"].asBool == false); - assert(write.writeJson(buffer, true) == 33); + assert(write.write_json(buffer, true) == 33); assert(buffer[0 .. 33] == "[42, {\"wow\":true, \"bogus\":false}]"); } @@ -244,7 +315,7 @@ ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) return true; } -Variant parseNode(ref const(char)[] text) +Variant parse_node(ref const(char)[] text) { text = text.trimFront(); @@ -307,7 +378,7 @@ Variant parseNode(ref const(char)[] text) else expectComma = true; - tmp ~= parseNode(text); + tmp ~= parse_node(text); if (!isArray) { assert(tmp.back().isString()); @@ -315,7 +386,7 @@ Variant parseNode(ref const(char)[] text) text = text.trimFront; assert(text.length > 0 && text[0] == ':'); text = text[1 .. $].trimFront; - tmp ~= parseNode(text); + tmp ~= parse_node(text); } } assert(text.length > 0); @@ -326,33 +397,30 @@ Variant parseNode(ref const(char)[] text) r.flags = Variant.Flags.Map; return r; } - else if (text[0].isNumeric || (text[0] == '-' && text.length > 1 && text[1].isNumeric)) + else if (text[0].is_numeric || (text[0] == '-' && text.length > 1 && text[1].is_numeric)) { - bool neg = text[0] == '-'; size_t taken = void; - ulong div = void; - ulong value = text[neg .. $].parseUintWithDecimal(div, &taken, 10); + int e = void; + long value = text.parse_int_with_exponent(e, &taken, 10); assert(taken > 0); - text = text[taken + neg .. $]; + text = text[taken .. $]; - if (div > 1) - { - double d = cast(double)value; - if (neg) - d = -d; - d /= div; - return Variant(d); - } - else + // let's work out if value*10^^e is an integer? + bool is_integer = e >= 0; + for (; e > 0; --e) { - if (neg) + if (value < 0 ? (value < long.min / 10) : (value > long.max / 10)) { - assert(value <= long.max + 1); - return Variant(-cast(long)value); + is_integer = false; + break; } - else - return Variant(value); + value *= 10; } + + if (is_integer) + return Variant(value); + else + return Variant(value * 10.0^^e); } else assert(false, "Invalid JSON!"); diff --git a/src/urt/hash.d b/src/urt/hash.d index 7c068e6..0068baa 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -6,10 +6,10 @@ version = BranchIsFasterThanMod; nothrow @nogc: -alias fnv1aHash = fnv1Hash!(uint, true); -alias fnv1aHash64 = fnv1Hash!(ulong, true); +alias fnv1a = fnv1!(uint, true); +alias fnv1a64 = fnv1!(ulong, true); -T fnv1Hash(T, bool alternate)(const ubyte[] s) pure nothrow @nogc +T fnv1(T, bool alternate)(const ubyte[] s) pure nothrow @nogc if (is(T == ushort) || is(T == uint) || is(T == ulong)) { static if (is(T == ushort)) @@ -47,7 +47,7 @@ T fnv1Hash(T, bool alternate)(const ubyte[] s) pure nothrow @nogc unittest { - enum hash = fnv1aHash(cast(ubyte[])"hello world"); + enum hash = fnv1a(cast(ubyte[])"hello world"); static assert(hash == 0xD58B3FA7); } diff --git a/src/urt/inet.d b/src/urt/inet.d index 969def7..be1bddc 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -11,11 +11,11 @@ nothrow @nogc: enum AddressFamily : byte { - Unknown = -1, - Unspecified = 0, - Unix, - IPv4, - IPv6, + unknown = -1, + unspecified = 0, + unix, + ipv4, + ipv6, } enum WellKnownPort : ushort @@ -34,8 +34,8 @@ enum WellKnownPort : ushort MDNS = 5353, } -enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv4 address"); return a; }(); -//enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv6 address"); return a; }(); +enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an ipv4 address"); return a; }(); +enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an ipv6 address"); return a; }(); struct IPAddr { @@ -56,13 +56,13 @@ nothrow @nogc: this.b = b; } - bool isMulticast() const pure + bool is_multicast() const pure => (b[0] & 0xF0) == 224; - bool isLoopback() const pure + bool is_loopback() const pure => b[0] == 127; - bool isLinkLocal() const pure + bool is_link_local() const pure => (b[0] == 169 && b[1] == 254); - bool isPrivate() const pure + bool is_private() const pure => (b[0] == 192 && b[1] == 168) || b[0] == 10 || (b[0] == 172 && (b[1] & 0xF) == 16); bool opCast(T : bool)() const pure @@ -74,6 +74,16 @@ nothrow @nogc: bool opEquals(const(ubyte)[4] bytes) const pure => b == bytes; + int opCmp(ref const IPAddr rhs) const pure + { + uint a = loadBigEndian(&address), b = loadBigEndian(&rhs.address); + if (a < b) + return -1; + else if (a > b) + return 1; + return 0; + } + IPAddr opUnary(string op : "~")() const pure { IPAddr r; @@ -97,26 +107,26 @@ nothrow @nogc: size_t toHash() const pure { - import urt.hash : fnv1aHash, fnv1aHash64; + import urt.hash : fnv1a, fnv1a64; static if (size_t.sizeof > 4) - return fnv1aHash64(b[]); + return fnv1a64(b[]); else - return fnv1aHash(b[]); + return fnv1a(b[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { - char[15] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[15] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = 0; for (int i = 0; i < 4; i++) { if (i > 0) tmp[offset++] = '.'; - offset += b[i].formatInt(tmp[offset..$]); + offset += b[i].format_uint(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -129,22 +139,22 @@ nothrow @nogc: { ubyte[4] t; size_t offset = 0, len; - ulong i = s[offset..$].parseInt(&len); + ulong i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[0] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[1] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[2] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255) return -1; @@ -180,13 +190,13 @@ nothrow @nogc: this.s = s; } - bool isGlobal() const pure + bool is_global() const pure => (s[0] & 0xE000) == 0x2000; - bool isLinkLocal() const pure + bool is_link_local() const pure => (s[0] & 0xFFC0) == 0xFE80; - bool isMulticast() const pure + bool is_multicast() const pure => (s[0] & 0xFF00) == 0xFF00; - bool isUniqueLocal() const pure + bool is_unique_local() const pure => (s[0] & 0xFE00) == 0xFC00; bool opCast(T : bool)() const pure @@ -198,6 +208,18 @@ nothrow @nogc: bool opEquals(const(ushort)[8] words) const pure => s == words; + int opCmp(ref const IPv6Addr rhs) const pure + { + for (int i = 0; i < 8; i++) + { + if (s[i] < rhs.s[i]) + return -1; + else if (s[i] > rhs.s[i]) + return 1; + } + return 0; + } + IPv6Addr opUnary(string op : "~")() const pure { IPv6Addr r; @@ -206,7 +228,7 @@ nothrow @nogc: return r; } - IPv6Addr opBinary(string op)(const IPv6Addr rhs) pure + IPv6Addr opBinary(string op)(const IPv6Addr rhs) const pure if (op == "&" || op == "|" || op == "^") { IPv6Addr t; @@ -224,19 +246,19 @@ nothrow @nogc: size_t toHash() const pure { - import urt.hash : fnv1aHash, fnv1aHash64; + import urt.hash : fnv1a, fnv1a64; static if (size_t.sizeof > 4) - return fnv1aHash64(cast(ubyte[])s[]); + return fnv1a64(cast(ubyte[])s[]); else - return fnv1aHash(cast(ubyte[])s[]); + return fnv1a(cast(ubyte[])s[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { import urt.string.ascii; // find consecutive zeroes... - int skipFrom = 0; + int skip_from = 0; int[8] z; for (int i = 0; i < 8; i++) { @@ -247,15 +269,15 @@ nothrow @nogc: if (z[j] != 0) { ++z[j]; - if (z[j] > z[skipFrom]) - skipFrom = j; + if (z[j] > z[skip_from]) + skip_from = j; } else break; } z[i] = 1; - if (z[i] > z[skipFrom]) - skipFrom = i; + if (z[i] > z[skip_from]) + skip_from = i; } } @@ -266,16 +288,16 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = ':'; - if (z[skipFrom] > 1 && i == skipFrom) + if (z[skip_from] > 1 && i == skip_from) { if (i == 0) tmp[offset++] = ':'; - i += z[skipFrom]; + i += z[skip_from]; if (i == 8) tmp[offset++] = ':'; continue; } - offset += s[i].formatInt(tmp[offset..$], 16); + offset += s[i].format_uint(tmp[offset..$], 16); ++i; } @@ -284,16 +306,52 @@ nothrow @nogc: if (buffer.length < offset) return -1; foreach (i, c; tmp[0 .. offset]) - buffer[i] = c.toLower; + buffer[i] = c.to_lower; } return offset; } ptrdiff_t fromString(const(char)[] str) { - ushort[8] t; - size_t offset = 0; - assert(false); + ushort[8][2] t = void; + ubyte[2] count; + int part = 0; + + size_t offset = 0, len; + while (offset < str.length) + { + if (offset < str.length - 1 && str[offset] == ':' && str[offset + 1] == ':') + { + if (part != 0) + return -1; + part = 1; + offset += 2; + if (offset == str.length) + break; + } + else if (count[part] > 0) + { + if (str[offset] != ':') + break; + if (++offset == str.length) + return -1; + } + if (str[offset] == ':') + return -1; + ulong i = str[offset..$].parse_uint(&len, 16); + if (len == 0) + break; + if (i > ushort.max || count[0] + count[1] == 8) + return -1; + t[part][count[part]++] = cast(ushort)i; + offset += len; + } + if (part == 0 && count[0] != 8) + return -1; + + s[0 .. count[0]] = t[0][0 .. count[0]]; + s[count[0] .. 8 - count[1]] = 0; + s[8 - count[1] .. 8] = t[1][0 .. count[1]]; return offset; } @@ -307,25 +365,25 @@ nothrow @nogc: auto __debugExpanded() => s[]; } -struct IPSubnet +struct IPNetworkAddress { nothrow @nogc: - enum multicast = IPSubnet(IPAddr(224, 0, 0, 0), 4); - enum loopback = IPSubnet(IPAddr(127, 0, 0, 0), 8); - enum linkLocal = IPSubnet(IPAddr(169, 254, 0, 0), 16); - enum privateA = IPSubnet(IPAddr(10, 0, 0, 0), 8); - enum privateB = IPSubnet(IPAddr(172, 16, 0, 0), 12); - enum privateC = IPSubnet(IPAddr(192, 168, 0, 0), 16); + enum multicast = IPNetworkAddress(IPAddr(224, 0, 0, 0), 4); + enum loopback = IPNetworkAddress(IPAddr(127, 0, 0, 0), 8); + enum linklocal = IPNetworkAddress(IPAddr(169, 254, 0, 0), 16); + enum private_a = IPNetworkAddress(IPAddr(10, 0, 0, 0), 8); + enum private_b = IPNetworkAddress(IPAddr(172, 16, 0, 0), 12); + enum private_c = IPNetworkAddress(IPAddr(192, 168, 0, 0), 16); // TODO: ya know, this is gonna align to 4-bytes anyway... // we could store the actual mask in the native endian, and then clz to recover the prefix len in one opcode IPAddr addr; IPAddr mask; - ubyte prefixLen() @property const pure + ubyte prefix_len() @property const pure => clz(~loadBigEndian(&mask.address)); - void prefixLen(ubyte len) @property pure + void prefix_len(ubyte len) @property pure { if (len == 0) mask.address = 0; @@ -333,36 +391,36 @@ nothrow @nogc: storeBigEndian(&mask.address, 0xFFFFFFFF << (32 - len)); } - this(IPAddr addr, ubyte prefixLen) + this(IPAddr addr, ubyte prefix_len) { this.addr = addr; - this.prefixLen = prefixLen; + this.prefix_len = prefix_len; } - IPAddr netMask() const pure + IPAddr net_mask() const pure => mask; bool contains(IPAddr ip) const pure - => (ip & netMask()) == addr; + => (ip & mask) == get_network(); - IPAddr getNetwork(IPAddr ip) const pure - => ip & mask; - IPAddr getLocal(IPAddr ip) const pure - => ip & ~mask; + IPAddr get_network() const pure + => addr & mask; + IPAddr get_local() const pure + => addr & ~mask; size_t toHash() const pure - => addr.toHash() ^ prefixLen; + => addr.toHash() ^ prefix_len; - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[18] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[18] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -378,11 +436,11 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parseInt(&t); + ulong plen = s[taken..$].parse_uint(&t); if (t == 0 || plen > 32) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } @@ -395,63 +453,71 @@ nothrow @nogc: } } -struct IPv6Subnet +struct IPv6NetworkAddress { nothrow @nogc: - enum global = IPv6Subnet(IPv6Addr(0x2000, 0, 0, 0, 0, 0, 0, 0), 3); - enum linkLocal = IPv6Subnet(IPv6Addr(0xFE80, 0, 0, 0, 0, 0, 0, 0), 10); - enum multicast = IPv6Subnet(IPv6Addr(0xFF00, 0, 0, 0, 0, 0, 0, 0), 8); - enum uniqueLocal = IPv6Subnet(IPv6Addr(0xFC00, 0, 0, 0, 0, 0, 0, 0), 7); + enum global = IPv6NetworkAddress(IPv6Addr(0x2000, 0, 0, 0, 0, 0, 0, 0), 3); + enum linklocal = IPv6NetworkAddress(IPv6Addr(0xFE80, 0, 0, 0, 0, 0, 0, 0), 10); + enum multicast = IPv6NetworkAddress(IPv6Addr(0xFF00, 0, 0, 0, 0, 0, 0, 0), 8); + enum uniquelocal = IPv6NetworkAddress(IPv6Addr(0xFC00, 0, 0, 0, 0, 0, 0, 0), 7); IPv6Addr addr; - ubyte prefixLen; + ubyte prefix_len; - this(IPv6Addr addr, ubyte prefixLen) + this(IPv6Addr addr, ubyte prefix_len) { this.addr = addr; - this.prefixLen = prefixLen; + this.prefix_len = prefix_len; } - IPv6Addr netMask() const pure + IPv6Addr net_mask() const pure { IPv6Addr r; - int i, j = prefixLen / 16; + int i, j = prefix_len / 16; while (i < j) r.s[i++] = 0xFFFF; - r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefixLen % 16))); - while (i < 8) r.s[i++] = 0; + if (j < 8) + { + r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefix_len % 16))); + while (i < 8) r.s[i++] = 0; + } return r; } bool contains(IPv6Addr ip) const pure { - uint n = prefixLen / 16; + uint n = prefix_len / 16; uint i = 0; for (; i < n; ++i) if (ip.s[i] != addr.s[i]) return false; - if (prefixLen % 16) + if (prefix_len % 16) { - uint s = 16 - (prefixLen % 16); + uint s = 16 - (prefix_len % 16); if (ip.s[i] >> s != addr.s[i] >> s) return false; } return true; } + IPv6Addr get_network() const pure + => addr & net_mask(); + IPv6Addr get_local() const pure + => addr & ~net_mask(); + size_t toHash() const pure - => addr.toHash() ^ prefixLen; + => addr.toHash() ^ prefix_len; - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[42] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[42] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -467,11 +533,11 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parseInt(&t); - if (t == 0 || plen > 32) + ulong plen = s[taken..$].parse_uint(&t); + if (t == 0 || plen > 128) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } @@ -503,7 +569,7 @@ nothrow @nogc: { IPv6Addr addr; ushort port; - uint flowInfo; + uint flow_info; uint scopeId; } struct Addr @@ -517,37 +583,89 @@ nothrow @nogc: this(IPAddr addr, ushort port) { - family = AddressFamily.IPv4; + family = AddressFamily.ipv4; this._a.ipv4.addr = addr; this._a.ipv4.port = port; } - this(IPv6Addr addr, ushort port) + this(IPv6Addr addr, ushort port, int flow_info = 0, uint scopeId = 0) { - family = AddressFamily.IPv6; + family = AddressFamily.ipv6; this._a.ipv6.addr = addr; this._a.ipv6.port = port; + this._a.ipv6.flow_info = flow_info; + this._a.ipv6.scopeId = scopeId; + } + + inout(IPv4)* as_ipv4() inout pure + => family == AddressFamily.ipv4 ? &_a.ipv4 : null; + + inout(IPv6)* as_ipv6() inout pure + => family == AddressFamily.ipv6 ? &_a.ipv6 : null; + + bool opCast(T : bool)() const pure + => family > AddressFamily.Unspecified; + + bool opEquals(ref const InetAddress rhs) const pure + { + if (family != rhs.family) + return false; + switch (family) + { + case AddressFamily.ipv4: + return _a.ipv4 == rhs._a.ipv4; + case AddressFamily.ipv6: + return _a.ipv6 == rhs._a.ipv6; + default: + return true; + } + } + + int opCmp(ref const InetAddress rhs) const pure + { + if (family != rhs.family) + return family < rhs.family ? -1 : 1; + switch (family) + { + case AddressFamily.ipv4: + int c = _a.ipv4.addr.opCmp(rhs._a.ipv4.addr); + return c != 0 ? c : _a.ipv4.port - rhs._a.ipv4.port; + case AddressFamily.ipv6: + int c = _a.ipv6.addr.opCmp(rhs._a.ipv6.addr); + if (c != 0) + return c; + if (_a.ipv6.port == rhs._a.ipv6.port) + { + if (_a.ipv6.flow_info == rhs._a.ipv6.flow_info) + return _a.ipv6.scopeId - rhs._a.ipv6.scopeId; + return _a.ipv6.flow_info - rhs._a.ipv6.flow_info; + } + return _a.ipv6.port - rhs._a.ipv6.port; + default: + return 0; + } + return 0; } size_t toHash() const pure { - if (family == AddressFamily.IPv4) + if (family == AddressFamily.ipv4) return _a.ipv4.addr.toHash() ^ _a.ipv4.port; else return _a.ipv6.addr.toHash() ^ _a.ipv6.port; } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[47] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[47] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = void; - if (family == AddressFamily.IPv4) + if (family == AddressFamily.ipv4) { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; - offset += _a.ipv4.port.formatInt(tmp[offset..$]); + offset += _a.ipv4.port.format_uint(tmp[offset..$]); } else { @@ -555,10 +673,10 @@ nothrow @nogc: offset = 1 + _a.ipv6.addr.toString(tmp[1 .. $], null, null); tmp[offset++] = ']'; tmp[offset++] = ':'; - offset += _a.ipv6.port.formatInt(tmp[offset..$]); + offset += _a.ipv6.port.format_uint(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -577,10 +695,10 @@ nothrow @nogc: // take address if (s.length >= 4 && (s[1] == '.' || s[2] == '.' || s[3] == '.')) - af = AddressFamily.IPv4; + af = AddressFamily.ipv4; else - af = AddressFamily.IPv6; - if (af == AddressFamily.IPv4) + af = AddressFamily.ipv6; + if (af == AddressFamily.ipv4) { taken = a4.fromString(s); if (taken < 0) @@ -602,8 +720,8 @@ nothrow @nogc: if (s.length > taken && s[taken] == ':') { size_t t; - ulong p = s[++taken..$].parseInt(&t); - if (t == 0 || p > 0xFFFF) + ulong p = s[++taken..$].parse_uint(&t); + if (t == 0 || p > ushort.max) return -1; taken += t; port = cast(ushort)p; @@ -611,7 +729,7 @@ nothrow @nogc: // success! store results.. family = af; - if (af == AddressFamily.IPv4) + if (af == AddressFamily.ipv4) { _a.ipv4.addr = a4; _a.ipv4.port = port; @@ -620,7 +738,7 @@ nothrow @nogc: { _a.ipv6.addr = a6; _a.ipv6.port = port; - _a.ipv6.flowInfo = 0; + _a.ipv6.flow_info = 0; _a.ipv6.scopeId = 0; } return taken; @@ -641,8 +759,12 @@ unittest char[64] tmp; assert(~IPAddr(255, 255, 248, 0) == IPAddr(0, 0, 7, 255)); + assert((IPAddr(255, 255, 248, 0) & IPAddr(255, 0, 255, 255)) == IPAddr(255, 0, 248, 0)); + assert((IPAddr(255, 255, 248, 0) | IPAddr(255, 0, 255, 255)) == IPAddr(255, 255, 255, 255)); assert((IPAddr(255, 255, 248, 0) ^ IPAddr(255, 0, 255, 255)) == IPAddr(0, 255, 7, 255)); - assert(IPSubnet(IPAddr(), 21).netMask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); + assert(IPNetworkAddress(IPAddr(), 21).net_mask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); + assert(IPNetworkAddress(IPAddr(192, 168, 0, 10), 24).get_network() == IPAddr(192, 168, 0, 0)); + assert(IPNetworkAddress(IPAddr(192, 168, 0, 10), 24).get_local() == IPAddr(0, 0, 0, 10)); assert(tmp[0 .. IPAddr(192, 168, 0, 1).toString(tmp, null, null)] == "192.168.0.1"); assert(tmp[0 .. IPAddr(0, 0, 0, 0).toString(tmp, null, null)] == "0.0.0.0"); @@ -653,16 +775,24 @@ unittest addr |= IPAddr(1, 2, 3, 4); assert(addr == IPAddr(1, 2, 3, 4)); - assert(tmp[0 .. IPSubnet(IPAddr(192, 168, 0, 0), 24).toString(tmp, null, null)] == "192.168.0.0/24"); - assert(tmp[0 .. IPSubnet(IPAddr(0, 0, 0, 0), 0).toString(tmp, null, null)] == "0.0.0.0/0"); + assert(tmp[0 .. IPNetworkAddress(IPAddr(192, 168, 0, 0), 24).toString(tmp, null, null)] == "192.168.0.0/24"); + assert(tmp[0 .. IPNetworkAddress(IPAddr(0, 0, 0, 0), 0).toString(tmp, null, null)] == "0.0.0.0/0"); - IPSubnet subnet; - assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPSubnet(IPAddr(192, 168, 0, 0), 24)); - assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPSubnet(IPAddr(0, 0, 0, 0), 0)); + IPNetworkAddress subnet; + assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPNetworkAddress(IPAddr(192, 168, 0, 0), 24)); + assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPNetworkAddress(IPAddr(0, 0, 0, 0), 0)); + assert(subnet.fromString("1.2.3.4") == -1); + assert(subnet.fromString("1.2.3.4/33") == -1); + assert(subnet.fromString("1.2.3.4/a") == -1); assert(~IPv6Addr(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0, 0, 0) == IPv6Addr(0, 0, 0, 0, 0xF, 0xFFFF, 0xFFFF, 0xFFFF)); + assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) & IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF00, 0, 1, 0, 0, 0, 0, 2)); + assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) | IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFFFF, 0, 3, 2, 3, 4, 5, 6)); assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) ^ IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF, 0, 2, 2, 3, 4, 5, 4)); - assert(IPv6Subnet(IPv6Addr(), 21).netMask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr(), 21).net_mask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr.loopback, 64).get_network() == IPv6Addr.any); + assert(IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 32).get_network() == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 32).get_local() == IPv6Addr(0, 0, 0, 1, 0, 0, 0, 1)); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1).toString(tmp, null, null)] == "2001:db8:0:1::1"); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1).toString(tmp, null, null)] == "2001:db8::1:0:0:1"); @@ -670,17 +800,40 @@ unittest assert(tmp[0 .. IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1).toString(tmp, null, null)] == "::1"); assert(tmp[0 .. IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0).toString(tmp, null, null)] == "::"); -// IPv6Addr addr6; -// assert(addr6.fromString("::2") == 3 && addr6 == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 2)); -// assert(addr6.fromString("1::2") == 3 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); -// assert(addr6.fromString("2001:db8::1/24") == 14 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); - - assert(tmp[0 .. IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); - assert(tmp[0 .. IPv6Subnet(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); - -// IPv6Subnet subnet6; -// assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); -// assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6Subnet(IPv6Addr(), 0)); + IPv6Addr addr6; + assert(addr6.fromString("::") == 2 && addr6.s[] == [0,0,0,0,0,0,0,0]); + assert(addr6.fromString("1::") == 3 && addr6.s[] == [1,0,0,0,0,0,0,0]); + assert(addr6.fromString("::2") == 3 && addr6.s[] == [0,0,0,0,0,0,0,2]); + assert(addr6.fromString("1::2") == 4 && addr6.s[] == [1,0,0,0,0,0,0,2]); + assert(addr6.fromString("1:FFFF::2") == 9 && addr6.s[] == [1,0xFFFF,0,0,0,0,0,2]); + assert(addr6.fromString("1:2:3:4:5:6:7:8") == 15 && addr6.s[] == [1,2,3,4,5,6,7,8]); + assert(addr6.fromString("1:2:3:4") == -1); + assert(addr6.fromString("1:2:3:4:5:6:7:8:9") == -1); + assert(addr6.fromString("1:2:3:4:5:6:7:8:") == -1); + assert(addr6.fromString("10000::2") == -1); + assert(addr6.fromString(":2") == -1); + assert(addr6.fromString("2:") == -1); + assert(addr6.fromString(":2:") == -1); + assert(addr6.fromString(":2::") == -1); + assert(addr6.fromString(":2::1") == -1); + assert(addr6.fromString("::2:") == -1); + assert(addr6.fromString("1::2:") == -1); + assert(addr6.fromString("1:::2") == -1); + assert(addr6.fromString("::G") == 2 && addr6 == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0)); + assert(addr6.fromString("1::2.3") == 4 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); + assert(addr6.fromString("1::2 4") == 4 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); + assert(addr6.fromString("2001:db8::1/24") == 11 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + + + assert(tmp[0 .. IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); + assert(tmp[0 .. IPv6NetworkAddress(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); + + IPv6NetworkAddress subnet6; + assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); + assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6NetworkAddress(IPv6Addr(), 0)); + assert(subnet6.fromString("1::2") == -1); + assert(subnet6.fromString("1::2/129") == -1); + assert(subnet6.fromString("1::2/a") == -1); assert(tmp[0 .. InetAddress(IPAddr(192, 168, 0, 1), 12345).toString(tmp, null, null)] == "192.168.0.1:12345"); assert(tmp[0 .. InetAddress(IPAddr(10, 0, 0, 0), 21).toString(tmp, null, null)] == "10.0.0.0:21"); @@ -692,6 +845,82 @@ unittest assert(address.fromString("192.168.0.1:21") == 14 && address == InetAddress(IPAddr(192, 168, 0, 1), 21)); assert(address.fromString("10.0.0.1:12345") == 14 && address == InetAddress(IPAddr(10, 0, 0, 1), 12345)); -// assert(address.fromString("[2001:db8:0:1::1]:12345") == 14 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); -// assert(address.fromString("[::]:21") == 14 && address == InetAddress(IPv6Addr(), 21)); + assert(address.fromString("[2001:db8:0:1::1]:12345") == 23 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); + assert(address.fromString("[::]:21") == 7 && address == InetAddress(IPv6Addr(), 21)); + assert(address.fromString("[::]:a") == -1); + assert(address.fromString("[::]:65536") == -1); + + // IPAddr sorting tests + { + IPAddr[8] expected = [ + IPAddr(0, 0, 0, 0), + IPAddr(1, 2, 3, 4), + IPAddr(1, 2, 3, 5), + IPAddr(1, 2, 4, 4), + IPAddr(10, 0, 0, 1), + IPAddr(127, 0, 0, 1), + IPAddr(192, 168, 1, 1), + IPAddr(255, 255, 255, 255), + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "IPAddr self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "IPAddr sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "IPAddr sorting is incorrect"); + } + } + + // IPv6Addr sorting tests + { + IPv6Addr[14] expected = [ + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0), // :: + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1), // ::1 + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 2), // ::2 + IPv6Addr(0, 0, 0, 0, 0, 0, 9, 0), // ::9:0 + IPv6Addr(0, 0, 0, 0, 0, 8, 0, 0), // ::8:0:0 + IPv6Addr(0, 0, 0, 0, 7, 0, 0, 0), // ::7:0:0:0 + IPv6Addr(0, 0, 0, 6, 0, 0, 0, 0), // 0:0:0:6:: + IPv6Addr(0, 0, 5, 0, 0, 0, 0, 0), // 0:0:5:: + IPv6Addr(0, 4, 0, 0, 0, 0, 0, 0), // 0:4:: + IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), // 1:: + IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), + IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2), + IPv6Addr(0xfe80, 0, 0, 0, 0, 0, 0, 1), + IPv6Addr(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff), + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "IPv6Addr self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "IPv6Addr sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "IPv6Addr sorting is incorrect"); + } + } + + // InetAddress sorting tests + { + InetAddress[10] expected = [ + // ipv4 sorted first + InetAddress(IPAddr(10, 0, 0, 1), 80), + InetAddress(IPAddr(127, 0, 0, 1), 8080), + InetAddress(IPAddr(192, 168, 1, 1), 80), + InetAddress(IPAddr(192, 168, 1, 1), 443), + + // ipv6 sorted next + InetAddress(IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), 1024), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 80, 0, 0), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 433, 1, 1), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0), // flow=0, scope=0 + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 1), // flow=0, scope=1 + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 1, 0), // flow=1, scope=0 + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "InetAddress self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "InetAddress sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "InetAddress sorting is incorrect"); + } + } } diff --git a/src/urt/internal/lifetime.d b/src/urt/internal/lifetime.d new file mode 100644 index 0000000..314164f --- /dev/null +++ b/src/urt/internal/lifetime.d @@ -0,0 +1,121 @@ +module urt.internal.lifetime; + +import urt.lifetime : forward; + +/+ +emplaceRef is a package function for druntime internal use. It works like +emplace, but takes its argument by ref (as opposed to "by pointer"). +This makes it easier to use, easier to be safe, and faster in a non-inline +build. +Furthermore, emplaceRef optionally takes a type parameter, which specifies +the type we want to build. This helps to build qualified objects on mutable +buffer, without breaking the type system with unsafe casts. ++/ +void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args) +{ + static if (args.length == 0) + { + static assert(is(typeof({static T i;})), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this() is annotated with @disable."); + static if (is(T == class)) static assert(!__traits(isAbstractClass, T), + T.stringof ~ " is abstract and it can't be emplaced"); + emplaceInitializer(chunk); + } + else static if ( + !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */ + || + Args.length == 1 && is(typeof({T t = forward!(args[0]);})) /* conversions */ + || + is(typeof(T(forward!args))) /* general constructors */) + { + static struct S + { + T payload; + this()(auto ref Args args) + { + static if (__traits(compiles, payload = forward!args)) + payload = forward!args; + else + payload = T(forward!args); + } + } + if (__ctfe) + { + static if (__traits(compiles, chunk = T(forward!args))) + chunk = T(forward!args); + else static if (args.length == 1 && __traits(compiles, chunk = forward!(args[0]))) + chunk = forward!(args[0]); + else assert(0, "CTFE emplace doesn't support " + ~ T.stringof ~ " from " ~ Args.stringof); + } + else + { + S* p = () @trusted { return cast(S*) &chunk; }(); + static if (UT.sizeof > 0) + emplaceInitializer(*p); + p.__ctor(forward!args); + } + } + else static if (is(typeof(chunk.__ctor(forward!args)))) + { + // This catches the rare case of local types that keep a frame pointer + emplaceInitializer(chunk); + chunk.__ctor(forward!args); + } + else + { + //We can't emplace. Try to diagnose a disabled postblit. + static assert(!(Args.length == 1 && is(Args[0] : T)), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this(this) is annotated with @disable."); + + //We can't emplace. + static assert(false, + T.stringof ~ " cannot be emplaced from " ~ Args[].stringof ~ "."); + } +} + +// ditto +static import urt.traits; +void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) + if (is(UT == urt.traits.Unqual!UT)) +{ + emplaceRef!(UT, UT)(chunk, forward!args); +} + +/+ +Emplaces T.init. +In contrast to `emplaceRef(chunk)`, there are no checks for disabled default +constructors etc. ++/ +void emplaceInitializer(T)(scope ref T chunk) nothrow pure @trusted + if (!is(T == const) && !is(T == immutable) && !is(T == inout)) +{ + import core.internal.traits : hasElaborateAssign; + + static if (__traits(isZeroInit, T)) + { + import urt.mem : memset; + memset(cast(void*) &chunk, 0, T.sizeof); + } + else static if (__traits(isScalar, T) || + T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, (){ T chunk; chunk = T.init; })) + { + chunk = T.init; + } + else static if (__traits(isStaticArray, T)) + { + // For static arrays there is no initializer symbol created. Instead, we emplace elements one-by-one. + foreach (i; 0 .. T.length) + { + emplaceInitializer(chunk[i]); + } + } + else + { + import urt.mem : memcpy; + const initializer = __traits(initSymbol, T); + memcpy(cast(void*)&chunk, initializer.ptr, initializer.length); + } +} diff --git a/src/urt/lifetime.d b/src/urt/lifetime.d index acb04d1..586d9db 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -1,10 +1,9 @@ module urt.lifetime; +import urt.internal.lifetime : emplaceRef; // TODO: DESTROY THIS! T* emplace(T)(T* chunk) @safe pure { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk); return chunk; } @@ -12,8 +11,6 @@ T* emplace(T)(T* chunk) @safe pure T* emplace(T, Args...)(T* chunk, auto ref Args args) if (is(T == struct) || Args.length == 1) { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk, forward!args); return chunk; } @@ -73,8 +70,7 @@ T emplace(T, Args...)(void[] chunk, auto ref Args args) T* emplace(T, Args...)(void[] chunk, auto ref Args args) if (!is(T == class)) { - import core.internal.traits : Unqual; - import core.internal.lifetime : emplaceRef; + import urt.traits : Unqual; assert(chunk.length >= T.sizeof, "chunk size too small."); assert((cast(size_t) chunk.ptr) % T.alignof == 0, "emplace: Chunk is not aligned."); @@ -83,7 +79,6 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) return cast(T*) chunk.ptr; } - /+ void copyEmplace(S, T)(ref S source, ref T target) @system if (is(immutable S == immutable T)) @@ -269,7 +264,7 @@ private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) @ static if (hasElaborateAssign!T || !isAssignable!T) { - import core.stdc.string : memcpy; + import urt.mem : memcpy; () @trusted { memcpy(&target, &source, T.sizeof); }(); } else @@ -327,20 +322,11 @@ void moveEmplace(T)(ref T source, ref T target) @system @nogc -//debug = PRINTF; - -debug(PRINTF) -{ - import core.stdc.stdio; -} - /// Implementation of `_d_delstruct` and `_d_delstructTrace` template _d_delstructImpl(T) { private void _d_delstructImpure(ref T p) { - debug(PRINTF) printf("_d_delstruct(%p)\n", p); - destroy(*p); p = null; } @@ -478,27 +464,11 @@ T _d_newclassT(T)() @trusted attr |= BlkAttr.NO_SCAN; p = GC.malloc(init.length, attr, typeid(T)); - debug(PRINTF) printf(" p = %p\n", p); - } - - debug(PRINTF) - { - printf("p = %p\n", p); - printf("init.ptr = %p, len = %llu\n", init.ptr, cast(ulong)init.length); - printf("vptr = %p\n", *cast(void**) init); - printf("vtbl[0] = %p\n", (*cast(void***) init)[0]); - printf("vtbl[1] = %p\n", (*cast(void***) init)[1]); - printf("init[0] = %x\n", (cast(uint*) init)[0]); - printf("init[1] = %x\n", (cast(uint*) init)[1]); - printf("init[2] = %x\n", (cast(uint*) init)[2]); - printf("init[3] = %x\n", (cast(uint*) init)[3]); - printf("init[4] = %x\n", (cast(uint*) init)[4]); } // initialize it p[0 .. init.length] = init[]; - debug(PRINTF) printf("initialization done\n"); return cast(T) p; } diff --git a/src/urt/log.d b/src/urt/log.d index 71af7fd..9a08f0a 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -1,8 +1,11 @@ module urt.log; -import urt.io; +import urt.mem.temp; -enum Level +nothrow @nogc: + + +enum Level : ubyte { Error = 0, Warning, @@ -12,8 +15,19 @@ enum Level immutable string[] levelNames = [ "Error", "Warning", "Info", "Debug" ]; +alias LogSink = void function(Level level, const(char)[] message) nothrow @nogc; + __gshared Level logLevel = Level.Info; +void register_log_sink(LogSink sink) nothrow @nogc +{ + if (g_log_sink_count < g_log_sinks.length) + { + g_log_sinks[g_log_sink_count] = sink; + g_log_sink_count++; + } +} + void writeDebug(T...)(ref T things) { writeLog(Level.Debug, things); } void writeInfo(T...)(ref T things) { writeLog(Level.Info, things); } void writeWarning(T...)(ref T things) { writeLog(Level.Warning, things); } @@ -28,12 +42,26 @@ void writeLog(T...)(Level level, ref T things) { if (level > logLevel) return; - writeln(levelNames[level], ": ", things); + + const(char)[] message = tconcat(levelNames[level], ": ", things); + for (size_t i = 0; i < g_log_sink_count; i++) + g_log_sinks[i](level, message); } void writeLogf(T...)(Level level, const(char)[] format, ref T things) { if (level > logLevel) return; - writelnf("{-2}: {@-1}", things, levelNames[level], format); + + const(char)[] message = tformat("{-2}: {@-1}", things, levelNames[level], format); + + for (size_t i = 0; i < g_log_sink_count; i++) + g_log_sinks[i](level, message); } + + +private: + +// HACK: temp until we have a proper registration process... +__gshared LogSink[8] g_log_sinks; +__gshared size_t g_log_sink_count = 0; diff --git a/src/urt/map.d b/src/urt/map.d index 732fc79..3a624ac 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -292,13 +292,76 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) => Range!(IterateBy.Keys, true)(pRoot); auto values() nothrow => Range!(IterateBy.Values)(pRoot); -// auto values() const nothrow -// => Range!(IterateBy.Values, true)(pRoot); + auto values() const nothrow + => Range!(IterateBy.Values, true)(pRoot); auto opIndex() nothrow => Range!(IterateBy.KVP)(pRoot); -// auto opIndex() const nothrow -// => Range!(IterateBy.KVP, true)(pRoot); + auto opIndex() const nothrow + => Range!(IterateBy.KVP, true)(pRoot); + + import urt.string.format : FormatArg, formatValue; + ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const + { + if (buffer.ptr is null) + { + // count the buffer size + size_t size = 2, comma = 0; + foreach (kvp; this) + { + size += comma; + comma = 1; + ptrdiff_t len = formatValue(kvp.key, buffer, format, formatArgs); + if (len < 0) + return len; + size += len + 1; + len = formatValue(kvp.value, buffer, format, formatArgs); + if (len < 0) + return len; + size += len; + } + return size; + } + + if (buffer.length < 2) + return -1; + buffer[0] = '{'; + + size_t offset = 1; + bool add_comma = false; + foreach (kvp; this) + { + if (add_comma) + { + if (offset >= buffer.length) + return -1; + buffer[offset++] = ','; + } + else + add_comma = true; + ptrdiff_t len = formatValue(kvp.key, buffer[offset .. $], format, formatArgs); + if (len < 0) + return len; + offset += len; + if (offset >= buffer.length) + return -1; + buffer[offset++] = ':'; + len = formatValue(kvp.value, buffer[offset .. $], format, formatArgs); + if (len < 0) + return len; + offset += len; + } + + if (offset >= buffer.length) + return -1; + buffer[offset++] = '}'; + return offset; + } + + ptrdiff_t fromString()(const(char)[] s) + { + assert(false, "TODO"); + } private: nothrow: @@ -657,9 +720,7 @@ public: PN n; ref const(K) key() @property const pure => n.kvp.key; -// ref const(V) value() @property const pure -// => n.kvp.value; - ref V value() @property pure + ref inout(V) value() @property inout pure => n.kvp.value; } return KV(n); @@ -900,7 +961,7 @@ unittest assert(map.get(2) is null); } - // Iteration (opApply) + // Iteration (range) { TestAVLTree map; map.insert(3, 30); @@ -908,25 +969,43 @@ unittest map.insert(2, 20); map.insert(4, 40); - int sumKeys = 0; - int sumValues = 0; - int count = 0; - // Iterate key-value pairs + int sumKeys = 0, sumValues = 0, count = 0; foreach (kv; map) { sumKeys += kv.key; sumValues += kv.value; count++; } + assert(count == 4); + assert(sumKeys == 1 + 2 + 3 + 4); + assert(sumValues == 10 + 20 + 30 + 40); + // Iterate const key-value pairs + ref const cmap = map; + sumKeys = sumValues = count = 0; + foreach (kv; cmap) + { + sumKeys += kv.key; + sumValues += kv.value; + count++; + } assert(count == 4); assert(sumKeys == 1 + 2 + 3 + 4); assert(sumValues == 10 + 20 + 30 + 40); - sumValues = 0; - count = 0; + // Iterate keys only + sumKeys = 0, count = 0; + foreach (v; map.keys) + { + sumKeys += v; + count++; + } + assert(count == 4); + assert(sumKeys == 1 + 2 + 3 + 4); + // Iterate values only + sumValues = 0, count = 0; foreach (v; map.values) { sumValues += v; @@ -935,6 +1014,16 @@ unittest assert(count == 4); assert(sumValues == 10 + 20 + 30 + 40); + // Iterate const values only + sumValues = 0, count = 0; + foreach (v; cmap.values) + { + sumValues += v; + count++; + } + assert(count == 4); + assert(sumValues == 10 + 20 + 30 + 40); + // Test stopping iteration count = 0; foreach (k; map.keys) @@ -946,27 +1035,6 @@ unittest assert(count == 2); // Should stop after 1 and 2 } - // Iteration (Iterator struct) - { - TestAVLTree map; - map.insert(3, 30); - map.insert(1, 10); - map.insert(2, 20); - map.insert(4, 40); - - foreach (kvp; map) // Uses Iterator internally - { - // Note: D's foreach over structs with opApply might not directly use the Iterator struct - // but opApply tests cover the iteration logic. - // This loop tests if the basic range primitives work. - // A direct Iterator test: - } - - // Test empty map iteration - TestAVLTree emptyMap; - assert(emptyMap[].empty); - } - // Test with string keys { alias StringMap = AVLTree!(const(char)[], int); diff --git a/src/urt/math.d b/src/urt/math.d index 236c867..ff63a20 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -1,8 +1,9 @@ module urt.math; import urt.intrinsic; -import core.stdc.stdio; // For writeDebugf -import std.format; // For format + +// for arch where using FPU for int<->float conversions is preferred +//version = PreferFPUIntConv; version (LDC) version = GDC_OR_LDC; version (GNU) version = GDC_OR_LDC; @@ -62,8 +63,88 @@ extern(C) double exp(double x); double log(double x); double acos(double x); + double pow(double x, double e); +} + +int float_is_integer(double f, out ulong i) +{ + version (PreferFPUIntConv) + { + if (!(f == f)) + return 0; // NaN + if (f < 0) + { + if (f < long.min) + return 0; // out of range + long t = cast(long)f; + if (cast(double)t != f) + return 0; // not an integer + i = cast(ulong)t; + return -1; + } + if (f >= ulong.max) + return 0; // out of range + ulong t = cast(ulong)f; + if (cast(double)t != f) + return 0; // not an integer + i = t; + return 1; + } + else + { + import urt.meta : bit_mask; + enum M = 52, E = 11, B = 1023; + + ulong u = *cast(const(ulong)*)&f; + int e = (u >> M) & bit_mask!E; + ulong m = u & bit_mask!M; + + if (e == bit_mask!E) + return 0; // NaN/Inf + if (e == 0) + { + if (m) + return 0; // denormal + i = 0; + return 1; // +/- 0 + } + int shift = e - B; + if (shift < 0) + return 0; // |f| < 1 + bool integral = shift >= M || (m & bit_mask(M - shift)) == 0; + if (!integral) + return 0; // not an integer + if (f < 0) + { + if (f < long.min) + return 0; // out of range + i = cast(ulong)cast(long)f; + return -1; + } + if (f >= ulong.max) + return 0; // out of range + i = cast(ulong)f; + return 1; + } } +unittest +{ + // this covers all the branches, but maybe test some extreme cases? + ulong i; + assert(float_is_integer(double.nan, i) == 0); + assert(float_is_integer(double.infinity, i) == 0); + assert(float_is_integer(-double.infinity, i) == 0); + assert(float_is_integer(double.max, i) == 0); + assert(float_is_integer(-double.max, i) == 0); + assert(float_is_integer(0.5, i) == 0); + assert(float_is_integer(1.5, i) == 0); + assert(float_is_integer(cast(double)ulong.max, i) == 0); + assert(float_is_integer(0.0, i) == 1 && i == 0); + assert(float_is_integer(-0.0, i) == 1 && i == 0); + assert(float_is_integer(200, i) == 1 && i == 200); + assert(float_is_integer(-200, i) == -1 && cast(long)i == -200); +} pragma(inline, true) bool addc(T = uint)(T a, T b, out T r, bool c_in) diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 7408808..a87c8dd 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -11,15 +11,15 @@ void[] alloc(size_t size) nothrow @nogc return malloc(size)[0 .. size]; } -void[] allocAligned(size_t size, size_t alignment) nothrow @nogc +void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc { - import urt.util : isPowerOf2, max; + import urt.util : is_power_of_2, max; alignment = max(alignment, (void*).sizeof); - assert(isPowerOf2(alignment), "Alignment must be a power of two!"); + assert(is_power_of_2(alignment), "Alignment must be a power of two!"); version (Windows) { - import urt.util : alignDown; + import urt.util : align_down; // This is how Visual Studio's _aligned_malloc works... // see C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt\heap\align.cpp @@ -34,7 +34,7 @@ void[] allocAligned(size_t size, size_t alignment) nothrow @nogc return null; size_t ptr = cast(size_t)mem; - size_t allocptr = alignDown(ptr + header_size, alignment); + size_t allocptr = align_down(ptr + header_size, alignment); (cast(void**)allocptr)[-1] = mem; return (cast(void*)allocptr)[0 .. size]; @@ -60,24 +60,24 @@ void[] realloc(void[] mem, size_t newSize) nothrow @nogc return core.stdc.stdlib.realloc(mem.ptr, newSize)[0 .. newSize]; } -void[] reallocAligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc { - import urt.util : isPowerOf2, min, max; + import urt.util : is_power_of_2, min, max; alignment = max(alignment, (void*).sizeof); - assert(isPowerOf2(alignment), "Alignment must be a power of two!"); + assert(is_power_of_2(alignment), "Alignment must be a power of two!"); - void[] newAlloc = newSize > 0 ? allocAligned(newSize, alignment) : null; + void[] newAlloc = newSize > 0 ? alloc_aligned(newSize, alignment) : null; if (newAlloc !is null && mem !is null) { size_t toCopy = min(mem.length, newSize); newAlloc[0 .. toCopy] = mem[0 .. toCopy]; } - freeAligned(mem); + free_aligned(mem); return newAlloc; } -// NOTE: This function is only compatible with allocAligned! +// NOTE: This function is only compatible with alloc_aligned! void[] expand(void[] mem, size_t newSize) nothrow @nogc { version (Windows) @@ -107,7 +107,7 @@ void free(void[] mem) nothrow @nogc core.stdc.stdlib.free(mem.ptr); } -void freeAligned(void[] mem) nothrow @nogc +void free_aligned(void[] mem) nothrow @nogc { version (Windows) { @@ -140,11 +140,11 @@ size_t memsize(void* ptr) nothrow @nogc unittest { - void[] mem = allocAligned(16, 8); + void[] mem = alloc_aligned(16, 8); size_t s = memsize(mem.ptr); mem = expand(mem, 8); mem = expand(mem, 16); - freeAligned(mem); + free_aligned(mem); } diff --git a/src/urt/mem/allocator.d b/src/urt/mem/allocator.d index 75cadff..2d3f295 100644 --- a/src/urt/mem/allocator.d +++ b/src/urt/mem/allocator.d @@ -356,12 +356,12 @@ class Mallocator : NoGCAllocator override void[] alloc(size_t size, size_t alignment = DefaultAlign) nothrow @nogc { - return urt.mem.alloc.allocAligned(size, alignment); + return urt.mem.alloc.alloc_aligned(size, alignment); } override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc { - return urt.mem.alloc.reallocAligned(mem, newSize, alignment); + return urt.mem.alloc.realloc_aligned(mem, newSize, alignment); } override void[] expand(void[] mem, size_t newSize) nothrow @@ -371,7 +371,7 @@ class Mallocator : NoGCAllocator override void free(void[] mem) nothrow @nogc { - urt.mem.alloc.freeAligned(mem); + urt.mem.alloc.free_aligned(mem); } private: diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index 795a4e3..9a903c8 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -11,19 +11,19 @@ extern(C) nothrow @nogc: void* alloca(size_t size); - void* memcpy(void* dest, const void* src, size_t n); - void* memmove(void* dest, const void* src, size_t n); - void* memset(void* s, int c, size_t n); - void* memzero(void* s, size_t n) => memset(s, 0, n); + void* memcpy(void* dest, const void* src, size_t n) pure; + void* memmove(void* dest, const void* src, size_t n) pure; + void* memset(void* s, int c, size_t n) pure; + void* memzero(void* s, size_t n) pure => memset(s, 0, n); - size_t strlen(const char* s); - int strcmp(const char* s1, const char* s2); - char* strcpy(char* dest, const char* src); + size_t strlen(const char* s) pure; + int strcmp(const char* s1, const char* s2) pure; + char* strcpy(char* dest, const char* src) pure; char* strcat(char* dest, const char* src); - size_t wcslen(const wchar_t* s); -// wchar_t* wcscpy(wchar_t* dest, const wchar_t* src); + size_t wcslen(const wchar_t* s) pure; +// wchar_t* wcscpy(wchar_t* dest, const wchar_t* src) pure; // wchar_t* wcscat(wchar_t* dest, const wchar_t* src); -// wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n); +// wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n) pure; // wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t n); } diff --git a/src/urt/mem/region.d b/src/urt/mem/region.d index 9a4a228..a657ace 100644 --- a/src/urt/mem/region.d +++ b/src/urt/mem/region.d @@ -6,7 +6,7 @@ import urt.util; static Region* makeRegion(void[] mem) pure nothrow @nogc { assert(mem.length >= Region.sizeof, "Memory block too small"); - Region* region = cast(Region*)mem.ptr.alignUp(Region.alignof); + Region* region = cast(Region*)mem.ptr.align_up(Region.alignof); size_t alignBytes = cast(void*)region - mem.ptr; if (size_t.sizeof > 4 && mem.length > uint.max + alignBytes + Region.sizeof) region.length = uint.max; @@ -21,7 +21,7 @@ struct Region void[] alloc(size_t size, size_t alignment = size_t.sizeof) pure nothrow @nogc { size_t ptr = cast(size_t)&this + Region.sizeof + offset; - size_t alignedPtr = ptr.alignUp(alignment); + size_t alignedPtr = ptr.align_up(alignment); size_t alignBytes = alignedPtr - ptr; if (offset + alignBytes + size > length) return null; diff --git a/src/urt/mem/scratchpad.d b/src/urt/mem/scratchpad.d index 83a3abb..2a20c30 100644 --- a/src/urt/mem/scratchpad.d +++ b/src/urt/mem/scratchpad.d @@ -8,10 +8,10 @@ nothrow @nogc: enum size_t MaxScratchpadSize = 2048; enum size_t NumScratchBuffers = 4; -static assert(MaxScratchpadSize.isPowerOf2, "Scratchpad size must be a power of 2"); +static assert(MaxScratchpadSize.is_power_of_2, "Scratchpad size must be a power of 2"); -void[] allocScratchpad(size_t size = MaxScratchpadSize) +void[] alloc_scratchpad(size_t size = MaxScratchpadSize) { if (size > MaxScratchpadSize) { @@ -19,17 +19,17 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) return null; } - size = max(size.nextPowerOf2, WindowSize); + size = max(size.next_power_of_2, WindowSize); size_t maskBits = size / WindowSize; size_t mask = (1 << maskBits) - 1; - for (size_t page = 0; page < scratchpadAlloc.length; ++page) + for (size_t page = 0; page < scratchpad_alloc.length; ++page) { for (size_t window = 0; window < 8; window += maskBits) { - if ((scratchpadAlloc[page] & (mask << window)) == 0) + if ((scratchpad_alloc[page] & (mask << window)) == 0) { - scratchpadAlloc[page] |= mask << window; + scratchpad_alloc[page] |= mask << window; return scratchpad[page*MaxScratchpadSize + window*WindowSize .. page*MaxScratchpadSize + window*WindowSize + size]; } } @@ -40,7 +40,7 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) return null; } -void freeScratchpad(void[] mem) +void free_scratchpad(void[] mem) { size_t page = (cast(size_t)mem.ptr - cast(size_t)scratchpad.ptr) / MaxScratchpadSize; size_t window = (cast(size_t)mem.ptr - cast(size_t)scratchpad.ptr) % MaxScratchpadSize / WindowSize; @@ -48,8 +48,8 @@ void freeScratchpad(void[] mem) size_t maskBits = mem.length / WindowSize; size_t mask = (1 << maskBits) - 1; - assert((scratchpadAlloc[page] & (mask << window)) == (mask << window), "Freeing unallocated scratchpad memory!"); - scratchpadAlloc[page] &= ~(mask << window); + assert((scratchpad_alloc[page] & (mask << window)) == (mask << window), "Freeing unallocated scratchpad memory!"); + scratchpad_alloc[page] &= ~(mask << window); } private: @@ -57,31 +57,31 @@ private: enum WindowSize = MaxScratchpadSize / 8; __gshared ubyte[MaxScratchpadSize*NumScratchBuffers] scratchpad; -__gshared ubyte[NumScratchBuffers] scratchpadAlloc; +__gshared ubyte[NumScratchBuffers] scratchpad_alloc; unittest { - void[] t = allocScratchpad(MaxScratchpadSize); + void[] t = alloc_scratchpad(MaxScratchpadSize); assert(t.length == MaxScratchpadSize); - void[] t2 = allocScratchpad(MaxScratchpadSize / 2); + void[] t2 = alloc_scratchpad(MaxScratchpadSize / 2); assert(t2.length == MaxScratchpadSize / 2); - void[] t3 = allocScratchpad(MaxScratchpadSize / 2); + void[] t3 = alloc_scratchpad(MaxScratchpadSize / 2); assert(t3.length == MaxScratchpadSize / 2); - void[] t4 = allocScratchpad(MaxScratchpadSize / 4); + void[] t4 = alloc_scratchpad(MaxScratchpadSize / 4); assert(t4.length == MaxScratchpadSize / 4); - void[] t5 = allocScratchpad(MaxScratchpadSize / 8); + void[] t5 = alloc_scratchpad(MaxScratchpadSize / 8); assert(t5.length == MaxScratchpadSize / 8); - void[] t6 = allocScratchpad(MaxScratchpadSize / 4); + void[] t6 = alloc_scratchpad(MaxScratchpadSize / 4); assert(t6.length == MaxScratchpadSize / 4); - void[] t7 = allocScratchpad(MaxScratchpadSize / 8); + void[] t7 = alloc_scratchpad(MaxScratchpadSize / 8); assert(t7.length == MaxScratchpadSize / 8); - freeScratchpad(t); - freeScratchpad(t7); - freeScratchpad(t5); - freeScratchpad(t4); - freeScratchpad(t6); - freeScratchpad(t2); - freeScratchpad(t3); + free_scratchpad(t); + free_scratchpad(t7); + free_scratchpad(t5); + free_scratchpad(t4); + free_scratchpad(t6); + free_scratchpad(t2); + free_scratchpad(t3); } diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index a04f5e9..a3adc22 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -11,54 +11,57 @@ shared static this() } -struct StringCache -{ -} - - struct CacheString { +nothrow @nogc: alias toString this; - this(typeof(null)) pure nothrow @nogc + this(typeof(null)) pure { offset = 0; } - string toString() const nothrow @nogc + string toString() const pure { - ushort len = *cast(ushort*)(stringHeap.ptr + offset); - return cast(string)stringHeap[offset + 2 .. offset + 2 + len]; + // HACK: deploy the pure hack! + static char[] pureHack() nothrow @nogc => stringHeap; + string heap = (cast(immutable(char[]) function() pure nothrow @nogc)&pureHack)(); + + ushort len = *cast(ushort*)(heap.ptr + offset); + return heap[offset + 2 .. offset + 2 + len]; } - immutable(char)* ptr() const nothrow @nogc - => cast(immutable(char)*)(stringHeap.ptr + offset + 2); + immutable(char)* ptr() const pure + => toString().ptr; + + size_t length() const pure + => toString().length; - size_t length() const nothrow @nogc - => *cast(ushort*)(stringHeap.ptr + offset); + string opIndex() const pure + => toString(); - bool opCast(T : bool)() const pure nothrow @nogc + bool opCast(T : bool)() const pure => offset != 0; - void opAssign(typeof(null)) pure nothrow @nogc + void opAssign(typeof(null)) pure { offset = 0; } - bool opEquals(const(char)[] rhs) const nothrow @nogc + bool opEquals(const(char)[] rhs) const pure { string s = toString(); - return s.length == rhs.length && (s.ptr == rhs.ptr || s[] == rhs[]); + return s.length == rhs.length && (s.ptr is rhs.ptr || s[] == rhs[]); } - size_t toHash() const nothrow @nogc + size_t toHash() const pure { import urt.hash; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])toString()); + return fnv1a(cast(ubyte[])toString()); else - return fnv1aHash64(cast(ubyte[])toString()); + return fnv1a64(cast(ubyte[])toString()); } private: @@ -79,13 +82,11 @@ void initStringHeap(uint stringHeapSize) nothrow assert(stringHeapInitialised == false, "String heap already initialised!"); assert(stringHeapSize <= ushort.max, "String heap too large!"); - stringHeap = new char[stringHeapSize]; + stringHeap = defaultAllocator.allocArray!char(stringHeapSize); // write the null string to the start - stringHeapCursor = 0; - stringHeap[stringHeapCursor++] = 0; - stringHeap[stringHeapCursor++] = 0; - numStrings = 1; + stringHeap[0..2] = 0; + stringHeapCursor = 2; stringHeapInitialised = true; } @@ -104,44 +105,46 @@ uint getStringHeapRemaining() nothrow @nogc return cast(uint)stringHeap.length - stringHeapCursor; } -CacheString addString(const(char)[] str, bool dedup = true) nothrow @nogc +CacheString addString(const(char)[] str) pure nothrow @nogc { - // null string - if (str.length == 0) - return CacheString(0); + // HACK: even though this mutates global state, the string cache is immutable after it's emplaced + // so, multiple calls with the same source string will always return the same result! + static CacheString impl(const(char)[] str) nothrow @nogc + { + // null string + if (str.length == 0) + return CacheString(0); - assert(str.length < 2^^14, "String longer than max string len (32768 chars)"); + assert(str.length < 2^^14, "String longer than max string len (32768 chars)"); - if (dedup) - { // first we scan to see if it's already in here... - for (ushort i = 1; i < stringHeapCursor;) + for (ushort i = 2; i < stringHeapCursor;) { ushort offset = i; - ushort len = stringHeap[i++]; - if (len >= 128) - len = (len & 0x7F) | ((stringHeap[i++] << 7) & 0x7F); + ushort len = *cast(ushort*)(stringHeap.ptr + i); + i += 2; if (len == str.length && stringHeap[i .. i + len] == str[]) return CacheString(offset); - i += len; + i += len + (len & 1); } - } - if (stringHeapCursor & 1) - stringHeap[stringHeapCursor++] = '\0'; + // add the string to the heap... + assert(stringHeapCursor + str.length < stringHeap.length, "String heap overflow!"); - // add the string to the heap... - assert(stringHeapCursor + str.length < stringHeap.length, "String heap overflow!"); + char[] heap = stringHeap[stringHeapCursor .. $]; + ushort offset = stringHeapCursor; - ushort offset = stringHeapCursor; - str.makeString(stringHeap[stringHeapCursor .. $]); - stringHeapCursor += str.length + 2; - ++numStrings; + *cast(ushort*)heap.ptr = cast(ushort)str.length; + heap[2 .. 2 + str.length] = str[]; + stringHeapCursor += str.length + 2; + if (stringHeapCursor & 1) + stringHeap[stringHeapCursor++] = '\0'; - return CacheString(offset); + return CacheString(offset); + } + return (cast(CacheString function(const(char)[]) pure nothrow @nogc)&impl)(str); } - void* allocWithStringCache(size_t bytes, String[] cachedStrings, const(char[])[] strings) nothrow @nogc { import urt.mem.alloc; @@ -191,7 +194,6 @@ private: __gshared bool stringHeapInitialised = false; __gshared char[] stringHeap = null; __gshared ushort stringHeapCursor = 0; -__gshared uint numStrings = 0; unittest diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index 6f2d28b..f470dff 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -16,22 +16,18 @@ void[] talloc(size_t size) nothrow @nogc assert(InFormatFunction == false, "It is illegal to use the temp allocator inside string conversion functions. Consider using stack or scratchpad."); } - if (size >= TempMemSize / 2) - { - assert(false, "Requested temp memory size is too large"); - return null; - } + assert(size <= TempMemSize / 2, "Requested temp memory size is too large"); - if (allocOffset + size > TempMemSize) - allocOffset = 0; + if (alloc_offset + size > TempMemSize) + alloc_offset = 0; - void[] mem = tempMem[allocOffset .. allocOffset + size]; - allocOffset += size; + void[] mem = tempMem[alloc_offset .. alloc_offset + size]; + alloc_offset += size; return mem; } -void[] tallocAligned(size_t size, size_t alignment) nothrow @nogc +void[] talloc_aligned(size_t size, size_t alignment) nothrow @nogc { assert(false); } @@ -50,19 +46,19 @@ void[] trealloc(void[] mem, size_t newSize) nothrow @nogc return r; } -void[] treallocAligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] trealloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc { assert(false); } void[] texpand(void[] mem, size_t newSize) nothrow @nogc { - if (mem.ptr + mem.length != tempMem.ptr + allocOffset) + if (mem.ptr + mem.length != tempMem.ptr + alloc_offset) return null; ptrdiff_t grow = newSize - mem.length; - if (cast(size_t)(allocOffset + grow) > TempMemSize) + if (cast(size_t)(alloc_offset + grow) > TempMemSize) return null; - allocOffset += grow; + alloc_offset += grow; return mem.ptr[0 .. newSize]; } @@ -73,27 +69,43 @@ void tfree(void[] mem) nothrow @nogc char* tstringz(const(char)[] str) nothrow @nogc { - if (str.length > TempMemSize / 2) - return null; - - size_t len = str.length; - if (allocOffset + len + 1 > TempMemSize) - allocOffset = 0; + char* r = cast(char*)talloc(str.length + 1).ptr; + r[0 .. str.length] = str[]; + r[str.length] = '\0'; + return r; +} +char* tstringz(const(wchar)[] str) nothrow @nogc +{ + import urt.string.uni : uni_convert; + char* r = cast(char*)talloc(str.length*3 + 1).ptr; + size_t len = uni_convert(str, r[0 .. str.length*3]); + r[len] = '\0'; + return r; +} - char* r = cast(char*)tempMem.ptr + allocOffset; - r[0 .. len] = str[]; +wchar* twstringz(const(char)[] str) nothrow @nogc +{ + import urt.string.uni : uni_convert; + wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; + size_t len = uni_convert(str, r[0 .. str.length]); r[len] = '\0'; - allocOffset += len + 1; + return r; +} +wchar* twstringz(const(wchar)[] str) nothrow @nogc +{ + wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; + r[0 .. str.length] = str[]; + r[str.length] = '\0'; return r; } char[] tstring(T)(auto ref T value) { import urt.string.format : toString; - ptrdiff_t r = toString(value, cast(char[])tempMem[allocOffset..$]); + ptrdiff_t r = toString(value, cast(char[])tempMem[alloc_offset..$]); if (r < 0) { - allocOffset = 0; + alloc_offset = 0; r = toString(value, cast(char[])tempMem[0..TempMemSize / 2]); if (r < 0) { @@ -101,34 +113,46 @@ char[] tstring(T)(auto ref T value) return null; } } - char[] result = cast(char[])tempMem[allocOffset .. allocOffset + r]; - allocOffset += r; + char[] result = cast(char[])tempMem[alloc_offset .. alloc_offset + r]; + alloc_offset += r; return result; } +dchar[] tdstring(T)(auto ref T value) nothrow @nogc +{ + static if (is(T : const(char)[]) || is(T : const(wchar)[]) || is(T : const(dchar)[])) + alias s = value; + else + char[] s = tstring(value); + import urt.string.uni : uni_convert; + dchar* r = cast(dchar*)talloc(s[].length*4).ptr; + size_t len = uni_convert(s[], r[0 .. s.length]); + return r[0 .. len]; +} + char[] tconcat(Args...)(ref Args args) { import urt.string.format : concat; - char[] r = concat(cast(char[])tempMem[allocOffset..$], args); + char[] r = concat(cast(char[])tempMem[alloc_offset..$], args); if (!r) { - allocOffset = 0; + alloc_offset = 0; r = concat(cast(char[])tempMem[0..TempMemSize / 2], args); } - allocOffset += r.length; + alloc_offset += r.length; return r; } char[] tformat(Args...)(const(char)[] fmt, ref Args args) { import urt.string.format : format; - char[] r = format(cast(char[])tempMem[allocOffset..$], fmt, args); + char[] r = format(cast(char[])tempMem[alloc_offset..$], fmt, args); if (!r) { - allocOffset = 0; + alloc_offset = 0; r = format(cast(char[])tempMem[0..TempMemSize / 2], fmt, args); } - allocOffset += r.length; + alloc_offset += r.length; return r; } @@ -170,4 +194,4 @@ private: private: static void[TempMemSize] tempMem; -static ushort allocOffset = 0; +static ushort alloc_offset = 0; diff --git a/src/urt/meta/nullable.d b/src/urt/meta/nullable.d index 3d981cc..98f29fd 100644 --- a/src/urt/meta/nullable.d +++ b/src/urt/meta/nullable.d @@ -9,8 +9,8 @@ template Nullable(T) { struct Nullable { - enum T NullValue = null; - T value = NullValue; + enum T null_value = null; + T value = null_value; this(T v) { @@ -18,7 +18,7 @@ template Nullable(T) } bool opCast(T : bool)() const - => value !is NullValue; + => value !is null_value; bool opEquals(typeof(null)) const => value is null; @@ -42,16 +42,16 @@ template Nullable(T) } template Nullable(T) - if (isBoolean!T) + if (is_boolean!T) { struct Nullable { - enum ubyte NullValue = 0xFF; - private ubyte _value = NullValue; + enum ubyte null_value = 0xFF; + private ubyte _value = null_value; this(typeof(null)) { - _value = NullValue; + _value = null_value; } this(T v) { @@ -62,27 +62,27 @@ template Nullable(T) => _value == 1; bool opCast(T : bool)() const - => _value != NullValue; + => _value != null_value; bool opEquals(typeof(null)) const - => _value == NullValue; + => _value == null_value; bool opEquals(T v) const => _value == cast(ubyte)v; void opAssign(typeof(null)) { - _value = NullValue; + _value = null_value; } void opAssign(U)(U v) if (is(U : T)) { - assert(v != NullValue); + assert(v != null_value); _value = cast(ubyte)v; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value == NullValue) + if (value == null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -91,16 +91,16 @@ template Nullable(T) } template Nullable(T) - if (isSomeInt!T) + if (is_some_int!T) { struct Nullable { - enum T NullValue = isSignedInt!T ? T.min : T.max; - T value = NullValue; + enum T null_value = is_signed_int!T ? T.min : T.max; + T value = null_value; this(typeof(null)) { - value = NullValue; + value = null_value; } this(T v) { @@ -108,27 +108,27 @@ template Nullable(T) } bool opCast(T : bool)() const - => value != NullValue; + => value != null_value; bool opEquals(typeof(null)) const - => value == NullValue; + => value == null_value; bool opEquals(T v) const - => value != NullValue && value == v; + => value != null_value && value == v; void opAssign(typeof(null)) { - value = NullValue; + value = null_value; } void opAssign(U)(U v) if (is(U : T)) { - assert(v != NullValue); + assert(v != null_value); value = v; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value == NullValue) + if (value == null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -137,16 +137,16 @@ template Nullable(T) } template Nullable(T) - if (isSomeFloat!T) + if (is_some_float!T) { struct Nullable { - enum T NullValue = T.nan; - T value = NullValue; + enum T null_value = T.nan; + T value = null_value; this(typeof(null)) { - value = NullValue; + value = null_value; } this(T v) { @@ -154,16 +154,16 @@ template Nullable(T) } bool opCast(T : bool)() const - => value !is NullValue; + => value !is null_value; bool opEquals(typeof(null)) const - => value is NullValue; + => value is null_value; bool opEquals(T v) const => value == v; // because nan doesn't compare with anything void opAssign(typeof(null)) { - value = NullValue; + value = null_value; } void opAssign(U)(U v) if (is(U : T)) @@ -173,7 +173,7 @@ template Nullable(T) ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value is NullValue) + if (value is null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -189,42 +189,42 @@ template Nullable(T) struct Nullable { T value = void; - bool isValue = false; + bool is_value = false; this(typeof(null)) { - isValue = false; + is_value = false; } this(T v) { moveEmplace(v, value); - isValue = true; + is_value = true; } ~this() { - if (isValue) + if (is_value) value.destroy(); } bool opCast(T : bool)() const - => isValue; + => is_value; bool opEquals(typeof(null)) const - => !isValue; + => !is_value; bool opEquals(T v) const - => isValue && value == v; + => is_value && value == v; void opAssign(typeof(null)) { - if (isValue) + if (is_value) value.destroy(); - isValue = false; + is_value = false; } void opAssign(U)(U v) if (is(U : T)) { - if (!isValue) + if (!is_value) moveEmplace(v, value); else value = v; @@ -232,7 +232,7 @@ template Nullable(T) ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!isValue) + if (!is_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -246,39 +246,39 @@ template Nullable(T) struct Nullable { T value; - bool isValue; + bool is_value; this(typeof(null)) { - isValue = false; + is_value = false; } this(T v) { value = v; - isValue = true; + is_value = true; } bool opCast(T : bool)() const - => isValue; + => is_value; bool opEquals(typeof(null)) const - => !isValue; + => !is_value; bool opEquals(T v) const - => isValue && value == v; + => is_value && value == v; void opAssign(typeof(null)) { - isValue = false; + is_value = false; } void opAssign(T v) { value = v; - isValue = true; + is_value = true; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!isValue) + if (!is_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index faa2941..c703fc5 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -1,90 +1,525 @@ module urt.meta; +import urt.traits : is_callable, is_enum, EnumType, Unqual; + +nothrow @nogc: alias Alias(alias a) = a; alias Alias(T) = T; alias AliasSeq(TList...) = TList; -template intForWidth(size_t width, bool signed = false) -{ - static if (width <= 8 && !signed) - alias intForWidth = ubyte; - else static if (width <= 8 && signed) - alias intForWidth = byte; - else static if (width <= 16 && !signed) - alias intForWidth = ushort; - else static if (width <= 16 && signed) - alias intForWidth = short; - else static if (width <= 32 && !signed) - alias intForWidth = uint; - else static if (width <= 32 && signed) - alias intForWidth = int; - else static if (width <= 64 && !signed) - alias intForWidth = ulong; - else static if (width <= 64 && signed) - alias intForWidth = long; -} - -template staticMap(alias fun, args...) -{ - alias staticMap = AliasSeq!(); +template Iota(ptrdiff_t start, ptrdiff_t end) +{ + static assert(start <= end, "start must be less than or equal to end"); + alias Iota = AliasSeq!(); + static foreach (i; start .. end) + Iota = AliasSeq!(Iota, i); +} +alias Iota(size_t end) = Iota!(0, end); + +auto make_delegate(Fun)(Fun fun) pure + if (is_callable!Fun) +{ + import urt.traits : is_function_pointer, ReturnType, Parameters; + + static if (is_function_pointer!fun) + { + struct Hack + { + static if (is(Fun : R function(Args) pure nothrow @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure nothrow @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) pure nothrow @nogc)&this)(args); + else static if (is(Fun : R function(Args) nothrow @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) nothrow @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) nothrow @nogc)&this)(args); + else static if (is(Fun : R function(Args) pure @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) pure @nogc)&this)(args); + else static if (is(Fun : R function(Args) pure nothrow, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure nothrow => (cast(ReturnType!Fun function(Parameters!Fun args) pure nothrow)&this)(args); + else static if (is(Fun : R function(Args) pure, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure => (cast(ReturnType!Fun function(Parameters!Fun args) pure)&this)(args); + else static if (is(Fun : R function(Args) nothrow, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) nothrow => (cast(ReturnType!Fun function(Parameters!Fun args) nothrow)&this)(args); + else static if (is(Fun : R function(Args) @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) @nogc)&this)(args); + else static if (is(Fun : R function(Args), R, Args...)) + ReturnType!Fun call(Parameters!Fun args) => (cast(ReturnType!Fun function(Parameters!Fun args))&this)(args); + } + Hack hack; + auto dg = &hack.call; + dg.ptr = fun; + return dg; + } + else static if (is(Fun == delegate)) + return fun; + else + static assert(false, "Unsupported type for make_delegate"); +} + +ulong bit_mask(size_t bits) pure +{ + return (1UL << bits) - 1; +} + +template bit_mask(size_t bits, bool signed = false) +{ + static assert(bits <= 64, "bit_mask only supports up to 64 bits"); + static if (bits == 64) + enum IntForWidth!(64, signed) bit_mask = ~0UL; + else + enum IntForWidth!(bits, signed) bit_mask = (1UL << bits) - 1; +} + +template IntForWidth(size_t bits, bool signed = false) +{ + static if (bits <= 8 && !signed) + alias IntForWidth = ubyte; + else static if (bits <= 8 && signed) + alias IntForWidth = byte; + else static if (bits <= 16 && !signed) + alias IntForWidth = ushort; + else static if (bits <= 16 && signed) + alias IntForWidth = short; + else static if (bits <= 32 && !signed) + alias IntForWidth = uint; + else static if (bits <= 32 && signed) + alias IntForWidth = int; + else static if (bits <= 64 && !signed) + alias IntForWidth = ulong; + else static if (bits <= 64 && signed) + alias IntForWidth = long; +} + +alias TypeForOp(string op, U) = typeof(mixin(op ~ "U()")); +alias TypeForOp(string op, A, B) = typeof(mixin("A()" ~ op ~ "B()")); + +template STATIC_MAP(alias fun, args...) +{ + alias STATIC_MAP = AliasSeq!(); + static foreach (arg; args) + STATIC_MAP = AliasSeq!(STATIC_MAP, fun!arg); +} + +template STATIC_UNROLL(alias array) +{ + static if (is(typeof(array) : T[], T)) + { + alias STATIC_UNROLL = AliasSeq!(); + static foreach (i; 0 .. array.length) + STATIC_UNROLL = AliasSeq!(STATIC_UNROLL, array[i]); + } + else + static assert(false, "STATIC_UNROLL requires an array"); +} + +template STATIC_FILTER(alias filter, args...) +{ + alias STATIC_FILTER = AliasSeq!(); static foreach (arg; args) - staticMap = AliasSeq!(staticMap, fun!arg); + static if (filter!arg) + STATIC_FILTER = AliasSeq!(STATIC_FILTER, arg); } -template staticIndexOf(args...) +template static_index_of(args...) if (args.length >= 1) { - enum staticIndexOf = { + enum static_index_of = { static foreach (idx, arg; args[1 .. $]) - static if (isSame!(args[0], arg)) + static if (is_same!(args[0], arg)) // `if (__ctfe)` is redundant here but avoids the "Unreachable code" warning. if (__ctfe) return idx; return -1; }(); } -template InterleaveSeparator(alias sep, Args...) +template INTERLEAVE_SEPARATOR(alias sep, Args...) { - alias InterleaveSeparator = AliasSeq!(); + alias INTERLEAVE_SEPARATOR = AliasSeq!(); static foreach (i, A; Args) static if (i > 0) - InterleaveSeparator = AliasSeq!(InterleaveSeparator, sep, A); + INTERLEAVE_SEPARATOR = AliasSeq!(INTERLEAVE_SEPARATOR, sep, A); else - InterleaveSeparator = AliasSeq!(A); + INTERLEAVE_SEPARATOR = AliasSeq!(A); } -template EnumKeys(E) +const(E)* enum_from_key(E)(const(char)[] key) pure + if (is_enum!E) + => enum_info!E.value_for(key); + +const(char)[] enum_key_from_value(E)(EnumType!E value) pure + if (is_enum!E) + => enum_info!E.key_for(value); + +const(char)[] enum_key_by_decl_index(E)(size_t value) pure + if (is_enum!E) + => enum_info!E.key_by_decl_index(value); + +struct VoidEnumInfo +{ + import urt.algorithm : binary_search; + import urt.string; +nothrow @nogc: + + // keys and values are sorted for binary search + ushort count; + ushort stride; + uint type_hash; + + const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_by_decl_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return get_key(_lookup_tables[count*2 + i]); + } + + const(void)* value_for(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + if (i == count) + return null; + i = _lookup_tables[i]; + return _values + i*stride; + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + return i < count; + } + +private: + const void* _values; + const ushort* _keys; + const char* _string_buffer; + + // these tables map between indices of keys and values + const ubyte* _lookup_tables; + + this(ubyte count, ushort stride, uint type_hash, inout void* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + { + this.count = count; + this.stride = stride; + this.type_hash = type_hash; + this._keys = keys; + this._values = values; + this._string_buffer = strings; + this._lookup_tables = lookup; + } + + const(char)[] get_key(size_t i) const pure + { + const(char)* s = _string_buffer + _keys[i]; + return s[0 .. s.key_length]; + } +} + +template EnumInfo(E) { - static assert(is(E == enum), "EnumKeys only works with enums!"); - __gshared immutable string[EnumStrings.length] EnumKeys = [ EnumStrings ]; - private alias EnumStrings = __traits(allMembers, E); + alias UE = Unqual!E; + + static if (is(UE == void)) + alias EnumInfo = VoidEnumInfo; + else + { + struct EnumInfo + { + import urt.algorithm : binary_search; + nothrow @nogc: + + static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); + + static if (is(UE T == enum)) + alias V = T; + else + static assert(false, E.string ~ " is not an enum type!"); + + // keys and values are sorted for binary search + union { + VoidEnumInfo _base; + struct { + ubyte[VoidEnumInfo._values.offsetof] _pad; + const UE* _values; // shadows the _values in _base with a typed version + } + } + alias _base this; + + inout(VoidEnumInfo*) make_void() inout pure + => &_base; + + this(ubyte count, uint type_hash, inout UE* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + { + _base = inout(VoidEnumInfo)(count, UE.sizeof, type_hash, values, keys, strings, lookup); + } + + const(UE)[] values() const pure + => _values[0 .. count]; + + const(char)[] key_for(V value) const pure + { + size_t i = binary_search(values[0 .. count], value); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_by_decl_index(size_t i) const pure + => _base.key_by_decl_index(i); + + const(UE)* value_for(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + if (i == count) + return null; + return _values + _lookup_tables[i]; + } + + bool contains(const(char)[] key) const pure + => _base.contains(key); + } + } } -E enumFromString(E)(const(char)[] key) -if (is(E == enum)) +template enum_info(E) + if (is(Unqual!E == enum)) { - foreach (i, k; EnumKeys!E) - if (key[] == k[]) - return cast(E)i; - return cast(E)-1; + alias UE = Unqual!E; + + enum ubyte num_items = enum_members.length; + static assert(num_items <= ubyte.max, "Too many enum items!"); + + __gshared immutable enum_info = immutable(EnumInfo!UE)( + num_items, + fnv1a(cast(ubyte[])UE.stringof), + _values.ptr, + _keys.ptr, + _strings.ptr, + _lookup.ptr + ); + +private: + import urt.algorithm : binary_search, compare, qsort; + import urt.hash : fnv1a; + import urt.string.uni : uni_compare; + + // keys and values are sorted for binary search + __gshared immutable UE[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; + + // keys are stored as offsets info the string buffer + __gshared immutable ushort[num_items] _keys = () { + ushort[num_items] key_offsets; + size_t offset = 2; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + key_offsets[i] = cast(ushort)offset; + offset += 2 + key.length; + if (key.length & 1) + offset += 1; // align to 2 bytes + } + return key_offsets; + }(); + + // build the string buffer + __gshared immutable char[total_strings] _strings = () { + char[total_strings] str_data; + char* ptr = str_data.ptr; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + version (LittleEndian) + { + *ptr++ = key.length & 0xFF; + *ptr++ = (key.length >> 8) & 0xFF; + } + else + { + *ptr++ = (key.length >> 8) & 0xFF; + *ptr++ = key.length & 0xFF; + } + ptr[0 .. key.length] = key[]; + ptr += key.length; + if (key.length & 1) + *ptr++ = 0; // align to 2 bytes + } + return str_data; + }(); + + // these tables map between indices of keys and values + __gshared immutable ubyte[num_items * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), + STATIC_MAP!(GetValRedirect, iota), + STATIC_MAP!(GetKeyOrig, iota) ]; + + // a whole bunch of nonsense to build the tables... + struct KI + { + string k; + ubyte i; + } + struct VI + { + UE v; + ubyte i; + } + + alias iota = Iota!(enum_members.length); + enum enum_members = __traits(allMembers, E); + enum by_key = (){ KI[num_items] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); + enum by_value = (){ VI[num_items] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); + enum inv_key = (){ KI[num_items] bk = by_key; ubyte[num_items] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); + enum inv_val = (){ VI[num_items] bv = by_value; ubyte[num_items] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + + // calculate the total size of the string buffer + enum total_strings = () { + size_t total = 0; + static foreach (k; enum_members) + total += 2 + k.length + (k.length & 1); + return total; + }(); + + enum MakeKI(ushort i) = KI(trim_key!(enum_members[i]), i); + enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); + enum GetValue(size_t i) = by_value[i].v; + enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; + enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; + enum GetKeyOrig(size_t i) = inv_key[i]; } +VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] values) +{ + import urt.algorithm; + import urt.hash : fnv1a; + import urt.mem.allocator; + import urt.string; + import urt.string.uni; + import urt.util; + + assert(keys.length == values.length, "keys and values must have the same length"); + assert(keys.length <= ubyte.max, "Too many enum items!"); + + size_t count = keys.length; + + struct VI(T) + { + T v; + ubyte i; + } + + // first we'll sort the keys and values for binary searching + // we need to associate their original indices for the lookup tables + auto ksort = tempAllocator().allocArray!(VI!(const(char)[]))(count); + auto vsort = tempAllocator().allocArray!(VI!T)(count); + foreach (i; 0 .. count) + { + ksort[i] = VI!(const(char)[])(keys[i], cast(ubyte)i); + vsort[i] = VI!T(values[i], cast(ubyte)i); + } + ksort.qsort!((ref a, ref b) => uni_compare(a.v, b.v)); + vsort.qsort!((ref a, ref b) => compare(a.v, b.v)); + + // build the reverse lookup tables + ubyte[] inv_k = tempAllocator().allocArray!ubyte(count); + ubyte[] inv_v = tempAllocator().allocArray!ubyte(count); + foreach (i, ref ki; ksort) + inv_k[ki.i] = cast(ubyte)i; + foreach (i, ref vi; vsort) + inv_v[vi.i] = cast(ubyte)i; + + // count the string memory + size_t total_string; + foreach (i; 0 .. count) + total_string += 2 + keys[i].length + (keys[i].length & 1); + + // calculate the total size + size_t total_size = VoidEnumInfo.sizeof + T.sizeof*count; + total_size += (total_size & 1) + ushort.sizeof*count + count*3; + total_size += (total_size & 1) + total_string; + + // allocate a buffer and assign all the sub-buffers + void[] info = defaultAllocator().alloc(total_size); + VoidEnumInfo* result = cast(VoidEnumInfo*)info.ptr; + T* value_ptr = cast(T*)&result[1]; + char* str_data = cast(char*)&value_ptr[count]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + ushort* key_ptr = cast(ushort*)str_data; + ubyte* lookup = cast(ubyte*)&key_ptr[count]; + str_data = cast(char*)&lookup[count*3]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + char* str_ptr = str_data + 2; + + // populate the enum info data + foreach (i; 0 .. count) + { + value_ptr[i] = vsort[i].v; + + // write the string data and store the key offset + const(char)[] key = ksort[i].v; + key_ptr[i] = cast(ushort)(str_ptr - str_data); + writeString(str_ptr, key); + if (key.length & 1) + (str_ptr++)[key.length] = 0; // align to 2 bytes + str_ptr += 2 + key.length; + + lookup[i] = inv_v[ksort[i].i]; + lookup[count + i] = inv_k[vsort[i].i]; + lookup[count*2 + i] = inv_k[i]; + } + + // build and return the object + return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup); +} private: -template isSame(alias a, alias b) +import urt.string : trim; +enum trim_key(string key) = key.trim!(c => c == '_'); + +template is_same(alias a, alias b) { static if (!is(typeof(&a && &b)) // at least one is an rvalue - && __traits(compiles, { enum isSame = a == b; })) // c-t comparable - enum isSame = a == b; + && __traits(compiles, { enum is_same = a == b; })) // c-t comparable + enum is_same = a == b; else - enum isSame = __traits(isSame, a, b); + enum is_same = __traits(isSame, a, b); } // TODO: remove after https://github.com/dlang/dmd/pull/11320 and https://issues.dlang.org/show_bug.cgi?id=21889 are fixed -template isSame(A, B) +template is_same(A, B) +{ + enum is_same = is(A == B); +} + +ushort key_length(const(char)* key) pure +{ + if (__ctfe) + { + version (LittleEndian) + return key[-2] | cast(ushort)(key[-1] << 8); + else + return key[-1] | cast(ushort)(key[-2] << 8); + } + else + return *cast(ushort*)(key - 2); +} + +int key_compare(ushort a, const(char)[] b, const(char)* strings) pure { - enum isSame = is(A == B); + import urt.string.uni : uni_compare; + const(char)* s = strings + a; + return uni_compare(s[0 .. s.key_length], b); } diff --git a/src/urt/meta/tuple.d b/src/urt/meta/tuple.d index a2fe66f..f29e758 100644 --- a/src/urt/meta/tuple.d +++ b/src/urt/meta/tuple.d @@ -64,20 +64,20 @@ template Tuple(Specs...) // Returns Specs for a subtuple this[from .. to] preserving field // names if any. - alias sliceSpecs(size_t from, size_t to) = staticMap!(expandSpec, fieldSpecs[from .. to]); + alias sliceSpecs(size_t from, size_t to) = STATIC_MAP!(expandSpec, fieldSpecs[from .. to]); struct Tuple { nothrow @nogc: - alias Types = staticMap!(extractType, fieldSpecs); + alias Types = STATIC_MAP!(extractType, fieldSpecs); private alias _Fields = Specs; /** * The names of the `Tuple`'s components. Unnamed fields have empty names. */ - alias fieldNames = staticMap!(extractName, fieldSpecs); + alias fieldNames = STATIC_MAP!(extractName, fieldSpecs); /** * Use `t.expand` for a `Tuple` `t` to expand it into its @@ -369,7 +369,7 @@ template Tuple(Specs...) } import std.range : roundRobin, iota; - alias NewTupleT = Tuple!(staticMap!(GetItem, aliasSeqOf!( + alias NewTupleT = Tuple!(STATIC_MAP!(GetItem, aliasSeqOf!( roundRobin(iota(nT), iota(nT, 2*nT))))); return *(() @trusted => cast(NewTupleT*)&this)(); } @@ -545,7 +545,7 @@ template Tuple(Specs...) } else { - formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand)); + formattedWrite(sink, fmt.nested, STATIC_MAP!(sharedToString, this.expand)); } } else if (fmt.spec == 's') diff --git a/src/urt/package.d b/src/urt/package.d index 354632b..6e57129 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -25,10 +25,10 @@ void crt_bootup() initClock(); import urt.rand; - initRand(); + init_rand(); - import urt.dbg : setupAssertHandler; - setupAssertHandler(); + import urt.dbg : setup_assert_handler; + setup_assert_handler(); import urt.string.string : initStringAllocators; initStringAllocators(); diff --git a/src/urt/rand.d b/src/urt/rand.d index b4b0f1e..c54cd25 100644 --- a/src/urt/rand.d +++ b/src/urt/rand.d @@ -55,7 +55,7 @@ private: rng.state = rng.state * PCG_DEFAULT_MULTIPLIER_64 + rng.inc; } - package void initRand() + package void init_rand() { import urt.time; srand(getTime().ticks, cast(size_t)&globalRand); diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 06de7f0..94af160 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -8,23 +8,23 @@ template map(fun...) { /** Params: - r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + r = an $(REF_ALTTEXT input range, is_input_range, std,range,primitives) Returns: A range with each fun applied to all the elements. If there is more than one fun, the element type will be `Tuple` containing one element for each fun. */ auto map(Range)(Range r) - if (isInputRange!(Unqual!Range)) + if (is_input_range!(Unqual!Range)) { - import urt.meta : AliasSeq, staticMap; + import urt.meta : AliasSeq, STATIC_MAP; alias RE = ElementType!(Range); static if (fun.length > 1) { import std.functional : adjoin; - import urt.meta : staticIndexOf; + import urt.meta : static_index_of; - alias _funs = staticMap!(unaryFun, fun); + alias _funs = STATIC_MAP!(unaryFun, fun); alias _fun = adjoin!_funs; // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed @@ -56,7 +56,7 @@ private struct MapResult(alias fun, Range) alias R = Unqual!Range; R _input; - static if (isBidirectionalRange!R) + static if (is_bidirectional_range!R) { @property auto ref back()() { @@ -101,7 +101,7 @@ private struct MapResult(alias fun, Range) return fun(_input.front); } - static if (isRandomAccessRange!R) + static if (is_random_access_range!R) { static if (is(typeof(Range.init[ulong.max]))) private alias opIndex_t = ulong; @@ -147,7 +147,7 @@ private struct MapResult(alias fun, Range) } } - static if (isForwardRange!R) + static if (is_forward_range!R) { @property auto save() { @@ -160,9 +160,9 @@ private struct MapResult(alias fun, Range) template reduce(fun...) if (fun.length >= 1) { - import urt.meta : staticMap; + import urt.meta : STATIC_MAP; - alias binfuns = staticMap!(binaryFun, fun); + alias binfuns = STATIC_MAP!(binaryFun, fun); static if (fun.length > 1) import urt.meta.tuple : tuple, isTuple; @@ -182,24 +182,21 @@ template reduce(fun...) Returns: the final result of the accumulator applied to the iterable - - Throws: `Exception` if `r` is empty +/ auto reduce(R)(R r) if (isIterable!R) { - import std.exception : enforce; - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); - alias Args = staticMap!(ReduceSeedType!E, binfuns); + alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); + alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); - static if (isInputRange!R) + static if (is_input_range!R) { // no need to throw if range is statically known to be non-empty static if (!__traits(compiles, { static assert(r.length > 0); })) - enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + assert(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); Args result = r.front; r.popFront(); @@ -245,7 +242,7 @@ template reduce(fun...) private auto reducePreImpl(R, Args...)(R r, ref Args args) { - alias Result = staticMap!(Unqual, Args); + alias Result = STATIC_MAP!(Unqual, Args); static if (is(Result == Args)) alias result = args; else @@ -259,7 +256,7 @@ template reduce(fun...) import std.algorithm.internal : algoFormat; static assert(Args.length == fun.length, algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length)); - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); static if (mustInitialize) bool initialized = false; @@ -279,7 +276,7 @@ template reduce(fun...) static if (mustInitialize) if (initialized == false) { - import core.internal.lifetime : emplaceRef; + import urt.lifetime : emplaceRef; foreach (i, f; binfuns) emplaceRef!(Args[i])(args[i], e); initialized = true; @@ -309,7 +306,7 @@ template fold(fun...) { /** Params: - r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to fold + r = the $(REF_ALTTEXT input range, is_input_range, std,range,primitives) to fold seeds = the initial values of each accumulator (optional), one for each predicate Returns: Either the accumulated result for a single predicate, or a @@ -323,7 +320,7 @@ template fold(fun...) } else { - import std.typecons : tuple; + import urt.meta : tuple; return reduce!fun(tuple(seeds), r); } } diff --git a/src/urt/range/primitives.d b/src/urt/range/primitives.d index b9789cd..f5ddec8 100644 --- a/src/urt/range/primitives.d +++ b/src/urt/range/primitives.d @@ -3,44 +3,44 @@ module urt.range.primitives; import urt.traits; -enum bool isInputRange(R) = +enum bool is_input_range(R) = is(typeof(R.init) == R) && is(typeof((R r) { return r.empty; } (R.init)) == bool) && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front))) && !is(typeof((R r) { return r.front; } (R.init)) == void) && is(typeof((R r) => r.popFront)); -enum bool isInputRange(R, E) = - .isInputRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_input_range(R, E) = + .is_input_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isForwardRange(R) = isInputRange!R +enum bool is_forward_range(R) = is_input_range!R && is(typeof((R r) { return r.save; } (R.init)) == R); -enum bool isForwardRange(R, E) = - .isForwardRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_forward_range(R, E) = + .is_forward_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isBidirectionalRange(R) = isForwardRange!R +enum bool is_bidirectional_range(R) = is_forward_range!R && is(typeof((R r) => r.popBack)) && (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back))) && is(typeof(R.init.back.init) == ElementType!R); -enum bool isBidirectionalRange(R, E) = - .isBidirectionalRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_bidirectional_range(R, E) = + .is_bidirectional_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isRandomAccessRange(R) = - is(typeof(lvalueOf!R[1]) == ElementType!R) +enum bool is_random_access_range(R) = + is(typeof(lvalue_of!R[1]) == ElementType!R) && !(isAutodecodableString!R && !isAggregateType!R) - && isForwardRange!R - && (isBidirectionalRange!R || isInfinite!R) + && is_forward_range!R + && (is_bidirectional_range!R || isInfinite!R) && (hasLength!R || isInfinite!R) - && (isInfinite!R || !is(typeof(lvalueOf!R[$ - 1])) - || is(typeof(lvalueOf!R[$ - 1]) == ElementType!R)); -enum bool isRandomAccessRange(R, E) = - .isRandomAccessRange!R && isQualifierConvertible!(ElementType!R, E); + && (isInfinite!R || !is(typeof(lvalue_of!R[$ - 1])) + || is(typeof(lvalue_of!R[$ - 1]) == ElementType!R)); +enum bool is_random_access_range(R, E) = + .is_random_access_range!R && isQualifierConvertible!(ElementType!R, E); // is this in the wrong place? should this be a general traits for arrays and stuff too? template ElementType(R) { - static if (is(typeof(lvalueOf!R.front))) - alias ElementType = typeof(lvalueOf!R.front); + static if (is(typeof(lvalue_of!R.front))) + alias ElementType = typeof(lvalue_of!R.front); else static if (is(R : T[], T)) alias ElementType = T; else diff --git a/src/urt/result.d b/src/urt/result.d index 564b15c..2462386 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -3,18 +3,10 @@ module urt.result; nothrow @nogc: -enum InternalCode -{ - Success = 0, - BufferTooSmall, - InvalidParameter, - Unsupported -} - struct Result { nothrow @nogc: - enum Success = Result(); + enum success = Result(); uint systemCode = 0; @@ -27,49 +19,58 @@ nothrow @nogc: => systemCode != 0; } +struct StringResult +{ +nothrow @nogc: + enum success = StringResult(); + + const(char)[] message = null; + + bool opCast(T : bool)() const + => message is null; + + bool succeeded() const + => message is null; + bool failed() const + => message !is null; +} + +// TODO: should we have a way to convert Result to StringResult, so we can format error messages? + version (Windows) { import core.sys.windows.windows; - Result InternalResult(InternalCode code) + enum InternalResult : Result { - switch (code) - { - case InternalCode.Success: - return Result(); - case InternalCode.BufferTooSmall: - return Result(ERROR_INSUFFICIENT_BUFFER); - case InternalCode.InvalidParameter: - return Result(ERROR_INVALID_PARAMETER); - default: - return Result(ERROR_INVALID_FUNCTION); // InternalCode.Unsupported - } + success = Result.success, + buffer_too_small = Result(ERROR_INSUFFICIENT_BUFFER), + invalid_parameter = Result(ERROR_INVALID_PARAMETER), + data_error = Result(ERROR_INVALID_DATA), + unsupported = Result(ERROR_INVALID_FUNCTION), } - Result Win32Result(uint err) + Result win32_result(uint err) => Result(err); + Result getlasterror_result() + => Result(GetLastError()); } else version (Posix) { import core.stdc.errno; - Result InternalResult(InternalCode code) + + enum InternalResult : Result { - switch (code) - { - case InternalCode.Success: - return Result(); - case InternalCode.BufferTooSmall: - return Result(ERANGE); - case InternalCode.InvalidParameter: - return Result(EINVAL); - default: - return Result(ENOTSUP); // InternalCode.Unsupported - } + success = Result.success, + buffer_too_small = Result(ERANGE), + invalid_parameter = Result(EINVAL), + data_error = Result(EILSEQ), + unsupported = Result(ENOTSUP), } - Result PosixResult(int err) + Result posix_result(int err) => Result(err); - Result ErrnoResult() + Result errno_result() => Result(errno); } diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 33462df..e293ebb 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -1,14 +1,22 @@ module urt.si.quantity; +import urt.meta : TypeForOp; import urt.si.unit; +import urt.traits; nothrow @nogc: -alias VarQuantity = Quantity!(double); +alias VarQuantity = Quantity!double; alias Scalar = Quantity!(double, ScaledUnit()); alias Metres = Quantity!(double, ScaledUnit(Metre)); alias Seconds = Quantity!(double, ScaledUnit(Second)); +alias Volts = Quantity!(double, ScaledUnit(Volt)); +alias Amps = Quantity!(double, ScaledUnit(Ampere)); +alias AmpHours = Quantity!(double, AmpereHour); +alias Watts = Quantity!(double, ScaledUnit(Watt)); +alias Kilowatts = Quantity!(double, Kilowatt); +alias WattHours = Quantity!(double, WattHour); struct Quantity(T, ScaledUnit _unit = ScaledUnit(uint.max)) @@ -20,7 +28,7 @@ nothrow @nogc: enum Dynamic = _unit.pack == uint.max; enum IsCompatible(ScaledUnit U) = _unit.unit == U.unit; - T value; + T value = 0; static if (Dynamic) ScaledUnit unit; @@ -31,11 +39,20 @@ nothrow @nogc: if (is(U : T)) => unit.unit == compatibleWith.unit.unit; - this(T value) pure + static if (Dynamic) { - static if (Dynamic) - this.unit = ScaledUnit(); - this.value = value; + this(T value, ScaledUnit unit = ScaledUnit()) pure + { + this.unit = unit; + this.value = value; + } + } + else + { + this(T value) pure + { + this.value = value; + } } this(U, ScaledUnit _U)(Quantity!(U, _U) b) pure @@ -49,10 +66,10 @@ nothrow @nogc: else { static if (b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); - value = adjustScale(b); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); + value = adjust_scale(b); } } @@ -61,13 +78,14 @@ nothrow @nogc: static if (Dynamic) unit = Scalar; else - static assert(unit == Unit(), "Incompatible unit: ", unit, " and Scalar"); + static assert(unit == Unit(), "Incompatible units: ", unit.toString, " and Scalar"); this.value = value; } void opAssign(U, ScaledUnit _U)(Quantity!(U, _U) b) pure - if (is(U : T)) { + static assert(__traits(compiles, value = b.value), "cannot implicitly convert ScaledUnit of type `", U, "` to `", T, "`"); + static if (Dynamic) { unit = b.unit; @@ -76,37 +94,45 @@ nothrow @nogc: else { static if (b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); - value = adjustScale(b); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); + value = adjust_scale(b); } } - auto opBinary(string op, U)(U value) const pure - if ((op == "+" || op == "-") && is(U : T)) + auto opUnary(string op)() const pure + if (op == "+" || op == "-") { + alias RT = Quantity!(TypeForOp!(op, T), _unit); static if (Dynamic) - assert(unit == Scalar); + return RT(mixin(op ~ "value"), unit); else - static assert(unit == Unit(), "Incompatible unit: ", unit, " and Scalar"); - return mixin("this.value " ~ op ~ " value"); + return RT(mixin(op ~ "value")); } + auto opBinary(string op, U)(U value) const pure + if ((op == "+" || op == "-") && is(U : T)) + => opBinary!op(Quantity!(U, ScaledUnit())(value)); + auto opBinary(string op, U, ScaledUnit _U)(Quantity!(U, _U) b) const pure if ((op == "+" || op == "-") && is(U : T)) { + // TODO: what unit should be result take? + // for float T, I reckon maybe the MAX exponent? + // for int types... we need to do some special shit to manage overflows! + // HACK: for now, we just scale to the left-hand size... :/ static if (!Dynamic && !b.Dynamic && unit == Unit() && b.unit == Unit()) return mixin("value " ~ op ~ " b.value"); else { static if (Dynamic || b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - This r; - r.value = mixin("value " ~ op ~ " adjustScale(b)"); + Quantity!(TypeForOp!(op, T, U), _unit) r; + r.value = mixin("value " ~ op ~ " adjust_scale(b)"); static if (Dynamic) r.unit = unit; return r; @@ -120,11 +146,12 @@ nothrow @nogc: return mixin("this.value " ~ op ~ " value"); else { - This r; - r.value = mixin("this.value " ~ op ~ " value"); + alias RT = TypeForOp!(op, T, U); + RT v = mixin("this.value " ~ op ~ " value"); static if (Dynamic) - r.unit = unit; - return r; + return Quantity!RT(v, unit); + else + return Quantity!(RT, unit)(v); } } @@ -135,33 +162,63 @@ nothrow @nogc: return mixin("value " ~ op ~ " b.value"); else { + // TODO: if the unit product is invalid, then we need to decide a target scaling factor... static if (Dynamic || b.Dynamic) - { - Quantity!T r; - r.unit = mixin("unit " ~ op ~ " b.unit"); - } + const u = mixin("unit " ~ op ~ " b.unit"); else - Quantity!(T, mixin("unit " ~ op ~ " b.unit")) r; + enum u = mixin("unit " ~ op ~ " b.unit"); - // TODO: if the unit product is invalid, then we apply the scaling factor... - // ... but which side should we scale to? probably the left I guess... + alias RT = TypeForOp!(op, T, U); + RT v = mixin("value " ~ op ~ " b.value"); - r.value = mixin("value " ~ op ~ " b.value"); - return r; + static if (Dynamic || b.Dynamic) + return Quantity!RT(v, u); + else + return Quantity!(RT, u)(v); } } - void opOpAssign(string op, U)(U value) pure - if (is(U : T)) + void opOpAssign(string op)(T value) pure { + // TODO: in D; ubyte += int is allowed, so we should cast the result to T this = opBinary!op(value); } void opOpAssign(string op, U, ScaledUnit _U)(Quantity!(U, _U) b) pure { + // TODO: in D; ubyte += int is allowed, so we should cast the result to T this = opBinary!op(b); } + bool opCast(T : bool)() const pure + => value != 0; + + // not clear if this should return the raw value, or the normalised value...? +// T opCast(T)() const pure +// if (is_some_float!T || is_some_int!T) +// { +// assert(unit.pack == 0, "Non-scalar unit can't cast to scalar"); +// assert(false, "TODO: should we be applying the scale to this result?"); +// return cast(T)value; +// } + + T opCast(T)() const pure + if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + T r; + static if (Dynamic || T.Dynamic) + assert(isCompatible(r), "Incompatible units!"); + else + static assert(IsCompatible!_U, "Incompatible units: ", r.unit.toString, " and ", unit.toString); + r.value = cast(U)r.adjust_scale(this); + static if (T.Dynamic) + r.unit = unit; + return r; + } + } + bool opEquals(U)(U value) const pure if (is(U : T)) { @@ -185,14 +242,14 @@ nothrow @nogc: // can't compare mismatch unit types... i think? static if (Dynamic || rh.Dynamic) - assert(isCompatible(rh), "Incompatible unit!"); + assert(isCompatible(rh), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", rh.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", rh.unit.toString); // TODO: meeting in the middle is only better if the signs are opposite // otherwise we should just scale to the left... static if (Dynamic && rh.Dynamic) - { + {{ // if the scale values are both dynamic, it should be more precise if we meet in the middle... auto lScale = unit.scale(); auto lTrans = unit.offset(); @@ -200,9 +257,9 @@ nothrow @nogc: auto rTrans = rh.unit.offset(); lhs = lhs*lScale + lTrans; rhs = rhs*rScale + rTrans; - } + }} else - rhs = adjustScale(rh); + rhs = adjust_scale(rh); compare: static if (epsilon == 0) @@ -222,41 +279,136 @@ nothrow @nogc: } } -private: - T adjustScale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure + auto normalise() const pure { static if (Dynamic) { - auto lScale = unit.scale!true(); - auto lTrans = unit.offset!true(); + Quantity!T r; + r.unit = ScaledUnit(unit.unit); } else - { - enum lScale = unit.scale!true(); - enum lTrans = unit.offset!true(); - } - static if (b.Dynamic) - { - auto rScale = b.unit.scale(); - auto rTrans = b.unit.offset(); - } + Quantity!(T, ScaledUnit(unit.unit)) r; + r.value = r.adjust_scale(this); + return r; + } + + Quantity!Ty adjust_scale(Ty = T)(ScaledUnit su) const pure + { + Quantity!Ty r; + r.unit = su; + assert(r.isCompatible(this), "Incompatible units!"); + if (su == unit) + r.value = cast(Ty)this.value; else + r.value = r.adjust_scale(this); + return r; + } + + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const + { + import urt.conv : format_float; + + double v = value; + ScaledUnit u = unit; + + if (u.pack) { - enum rScale = b.unit.scale(); - enum rTrans = b.unit.offset(); + // round upward to the nearest ^3 + if (u.siScale) + { + int x = u.exp; + if (u.unit.pack == 0) + { + if (x == -3) + { + v *= 0.1; + u = ScaledUnit(Unit(), x + 1); + } + else if (x != -2) + { + v *= u.scale(); + u = ScaledUnit(); + } + } + else + { + x = (x + 33) % 3; + if (x != 0) + { + u = ScaledUnit(u.unit, u.exp + (3 - x)); + if (x == 1) + v *= 0.01; + else + v *= 0.1; + } + } + } } - static if (Dynamic || b.Dynamic) + ptrdiff_t l = format_float(v, buffer); + if (l < 0) + return l; + + if (u.pack) { - auto scale = lScale*rScale; - auto trans = lTrans + lScale*rTrans; + ptrdiff_t l2 = u.toString(buffer[l .. $], null, null); + if (l2 < 0) + return l2; + l += l2; } + + return l; + } + + ptrdiff_t fromString(const(char)[] s) + { + return -1; + } + +private: + T adjust_scale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure + { + static if (!Dynamic && !b.Dynamic && unit == b.unit) + return cast(T)b.value; else { - enum scale = lScale*rScale; - enum trans = lTrans + lScale*rTrans; + if (unit == b.unit) + return cast(T)b.value; + + static if (Dynamic) + { + auto lScale = unit.scale!true(); + auto lTrans = unit.offset!true(); + } + else + { + enum lScale = unit.scale!true(); + enum lTrans = unit.offset!true(); + } + static if (b.Dynamic) + { + auto rScale = b.unit.scale(); + auto rTrans = b.unit.offset(); + } + else + { + enum rScale = b.unit.scale(); + enum rTrans = b.unit.offset(); + } + + static if (Dynamic || b.Dynamic) + { + auto scale = lScale*rScale; + auto trans = lTrans + lScale*rTrans; + } + else + { + enum scale = lScale*rScale; + enum trans = lTrans + lScale*rTrans; + } + return cast(T)(b.value*scale + trans); } - return cast(T)(b.value*scale + trans); } } diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index cf40a9e..7bc059b 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -1,5 +1,8 @@ module urt.si.unit; +import urt.array; +import urt.string; + nothrow @nogc: @@ -29,6 +32,9 @@ nothrow @nogc: // +enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parse_unit(desc, f); assert(e > 0, "Invalid unit"); assert(f == 1, "Unit requires pre-scale"); return r; }(); + + // base units enum Metre = Unit(UnitType.Length); enum Kilogram = Unit(UnitType.Mass); @@ -39,6 +45,9 @@ enum Candela = Unit(UnitType.Luma); enum Radian = Unit(UnitType.Angle); // non-si units +enum Minute = ScaledUnit(Second, ScaleFactor.Minute); +enum Hour = ScaledUnit(Second, ScaleFactor.Hour); +enum Day = ScaledUnit(Second, ScaleFactor.Day); enum Inch = ScaledUnit(Metre, ScaleFactor.Inch); enum Foot = ScaledUnit(Metre, ScaleFactor.Foot); enum Mile = ScaledUnit(Metre, ScaleFactor.Mile); @@ -60,7 +69,9 @@ enum CubicMetre = Metre^^3; enum Litre = ScaledUnit(CubicMetre, -3); enum Gram = ScaledUnit(Kilogram, -3); enum Milligram = ScaledUnit(Kilogram, -6); +enum Nanosecond = ScaledUnit(Second, -9); enum Hertz = Cycle / Second; +//enum Kilohertz = TODO: ONOES! Our system can't encode kilohertz! This is disaster!! enum Newton = Kilogram * Metre / Second^^2; enum Pascal = Newton / Metre^^2; enum PSI = ScaledUnit(Pascal, ScaleFactor.PSI); @@ -96,7 +107,18 @@ enum UnitType : ubyte struct Unit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t = void; + ptrdiff_t l = toString(t, null, null); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -225,82 +247,40 @@ nothrow @nogc: this = this.opBinary!op(rh); } - ptrdiff_t toString(char[] buffer) const + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { - immutable string[24] si = [ -// "da", "h", "k", "10k", "100k", "M", "10M", "100M", "G", "10G", "100G", "T", "10T", "100T", "P", "10P", "100P", "E", "10E", "100E", "Z", "10Z", "100Z", "Y" - "10", "100", "k", "10k", "100k", "M", "10M", "100M", "G", "10G", "100G", "T", "10T", "100T", "P", "10P", "100P", "E", "10E", "100E", "Z", "10Z", "100Z", "Y" - ]; - immutable string[24] si_inv = [ -// "d", "c", "m", "100μ", "10μ", "μ", "100n", "10n", "n", "100p", "10p", "p", "100f", "10f", "f", "100a", "10a", "a", "100z", "10z", "z", "100y", "10y", "y" - "100m", "10m", "m", "100μ", "10μ", "μ", "100n", "10n", "n", "100p", "10p", "p", "100f", "10f", "f", "100a", "10a", "a", "100z", "10z", "z", "100y", "10y", "y" - ]; - - ptrdiff_t len = 0; - - // ⁰¹⁻²³⁴⁵⁶⁷⁸⁹·° - -// if (q(0).exponent == 0) -// { -// // if we have a scaling factor... what do we write? -//// if (sf) ... -// len = 0; -// return len; -// } -// -// auto name = this in unitNames; -// if (name) -// { -// len = name.length; -// buffer[0 .. len] = *name; -// return len; -// } -// -// int f = sf(); -// name = unit() in unitNames; -// -// if ((name || q(1).exponent == 0) && f <= 24) -// { -// if (f > 0) -// { -// ref immutable string[24] prefix = inv ? si_inv : si; -// -// uint scale = f - 1; -// if (q(0).measure == UnitType.Mass) -// scale += 3; -// -// len = prefix[scale].length; -// buffer[0 .. len] = prefix[scale][]; -// } -// -// if (name) -// { -// buffer[len .. len + name.length] = *name; -// len += name.length; -// } -// else -// { -// immutable string[UnitType.max + 1] unit_name = [ "g", "m", "s", "A", "K", "cd", "cy" ]; -// -// string uname = unit_name[q(0).measure]; -// buffer[len .. len + uname.length] = uname[]; -// len += uname.length; -// } -// -// if (q(0).measure != 1) -// { -// buffer[len++] = '^'; -// len += q(0).exponent.toString(buffer[len .. $]); -// } -// -// } -// else -// { -// // multiple terms, or an odd scale factor... -// // TODO... -// assert(false); -// } + assert(false, "TODO"); + } + ptrdiff_t fromString(const(char)[] s) pure + { + if (s.length == 0) + { + pack = 0; + return 0; + } + + Unit r; + size_t len = s.length; + bool invert; + char sep; + while (const(char)[] unit = s.split!('/', '*')(sep)) + { + int p = unit.take_power(); + if (p == 0) + return -1; // invalid power + + if (const Unit* u = unit in unitMap) + r *= (*u) ^^ (invert ? -p : p); + else + { + assert(false, "TODO?"); + } + if (sep == '/') + invert = true; + } + this = r; return len; } @@ -381,7 +361,18 @@ enum ExtendedScaleFactor : ubyte struct ScaledUnit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t = void; + ptrdiff_t l = toString(t, null, null); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -432,12 +423,12 @@ nothrow @nogc: return unit == b.unit; } - double scale(bool inv = false)() const pure + double scale(bool invert = false)() const pure { if (siScale) { int e = exp(); - if (inv) + if (invert) e = -e; if (uint(e + 9) < 19) return sciScaleFactor[e + 9]; @@ -445,9 +436,9 @@ nothrow @nogc: } if (isExtended()) - return extScaleFactor[(pack >> 29) ^ (inv << 2)]; + return extScaleFactor[(pack >> 29) ^ (invert << 2)]; - double s = scaleFactor[(pack >> 31) ^ inv][sf()]; + double s = scaleFactor[(pack >> 31) ^ invert][sf()]; for (uint i = ((pack >> 29) & 3); i > 0; --i) s *= s; return s; @@ -574,9 +565,266 @@ nothrow @nogc: bool opEquals(Unit rh) const pure => (pack & 0xFF000000) ? false : unit == rh; + alias parseUnit = parse_unit; // TODO: DELETE ME!!! + ptrdiff_t parse_unit(const(char)[] s, out float pre_scale, bool allow_unit_scale = true) pure + { + import urt.conv : parse_uint_with_exponent; + + pre_scale = 1; + + if (s.length == 0) + { + pack = 0; + return 0; + } + + size_t len = s.length; + if (s[0] == '-') + { + if (s.length == 1) + return -1; + pre_scale = -1; + s = s[1 .. $]; + } + + ScaledUnit r; + bool invert; + char sep; + while (const(char)[] term = s.split!(['/', '*'], false, false)(&sep)) + { + int p = term.take_power(); + if (p == 0) + return -1; // invalid exponent + if (term.length == 0) + return -1; + + size_t offset = 0; + + // parse the scale factor + int e = 0; + if (term[0].is_numeric) + { + if (!allow_unit_scale) + return -1; // no numeric scale factor allowed + ulong sf = term.parse_uint_with_exponent(e, &offset); + pre_scale *= sf; + } + + if (offset == term.length) + r *= ScaledUnit(Unit(), e); + else if (const ScaledUnit* su = term[offset .. $] in noScaleUnitMap) + { + r *= (*su) ^^ (invert ? -p : p); + pre_scale *= 10.0^^e; + } + else + { + // try and parse SI prefix... + switch (term[offset]) + { + case 'Y': e += 24; ++offset; break; + case 'Z': e += 21; ++offset; break; + case 'E': e += 18; ++offset; break; + case 'P': e += 15; ++offset; break; + case 'T': e += 12; ++offset; break; + case 'G': e += 9; ++offset; break; + case 'M': e += 6; ++offset; break; + case 'k': e += 3; ++offset; break; + case 'h': e += 2; ++offset; break; + case 'c': e -= 2; ++offset; break; + case 'u': e -= 6; ++offset; break; + case 'n': e -= 9; ++offset; break; + case 'p': e -= 12; ++offset; break; + case 'f': e -= 15; ++offset; break; + case 'a': e -= 18; ++offset; break; + case 'z': e -= 21; ++offset; break; + case 'y': e -= 24; ++offset; break; + case 'm': + // can confuse with metres... so gotta check... + if (offset + 1 < term.length) + e -= 3, ++offset; + break; + case 'd': + if (offset + 1 < term.length && term[offset + 1] == 'a') + { + e += 1, offset += 2; + break; + } + e -= 1, ++offset; + break; + default: + if (offset + "µ".length < term.length && term[offset .. offset + "µ".length] == "µ") + e -= 6, offset += "µ".length; + break; + } + if (offset == term.length) + return -1; + + term = term[offset .. $]; + if (const Unit* u = term in unitMap) + { + if (term == "kg") + { + // we alrady parsed the 'k', so this string must have been "kkg", which is nonsense + return -1; + } + r *= ScaledUnit((*u) ^^ (invert ? -p : p), e); + } + else if (const ScaledUnit* su = term in scaledUnitMap) + r *= ScaledUnit(su.unit, su.exp + e) ^^ (invert ? -p : p); + else if (const ScaledUnit* su = term in noScaleUnitMapSI) + { + r *= (*su) ^^ (invert ? -p : p); + pre_scale *= 10.0^^e; + } + else + return -1; // string was not taken? + } + + if (sep == '/') + invert = true; + } + this = r; + return len; + } + + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure + { + if (!unit.pack) + { + if (siScale && exp == -2) + { + if (buffer.ptr) + { + if (buffer.length == 0) + return -1; + buffer[0] = '%'; + } + return 1; + } + else if (siScale && exp == -3) + { + enum pm_len = "‰".length; + if (buffer.ptr) + { + if (buffer.length < pm_len) + return -1; + buffer[0..pm_len] = "‰"; + } + return pm_len; + } + else + assert(false, "TODO!"); // how (or should?) we encode a scale as a unit type? + } + + size_t len = 0; + if (siScale) + { + int x = exp; + if (x != 0) + { + // for scale factors between SI units, we'll normalise to the next higher unit... + int y = (x + 33) % 3; + if (y != 0) + { + if (y == 1) + { + if (buffer.ptr) + { + if (buffer.length < 2) + return -1; + buffer[0..2] = "10"; + } + --x; + len += 2; + } + else + { + if (buffer.ptr) + { + if (buffer.length < 3) + return -1; + buffer[0..3] = "100"; + } + x -= 2; + len += 3; + } + } + assert(x >= -30, "TODO: handle this very small case"); + + if (x != 0) + { + if (buffer.ptr) + { + if (buffer.length <= len) + return -1; + buffer[len] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + } + ++len; + } + } + + if (const string* name = unit in unitNames) + { + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } + len += name.length; + } + else + { + // synth a unit name... + assert(false, "TODO"); + } + } + else + { + if (const string* name = this in scaledUnitNames) + { + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } + len += name.length; + } + else + { + // what now? + assert(false, "TODO"); + } + } + return len; + } + + ptrdiff_t fromString(const(char)[] s) pure + { + float scale; + ptrdiff_t r = parse_unit(s, scale); + if (scale != 1) + return -1; + return r; + } + size_t toHash() const pure => pack; + auto __debugOverview() + { + debug { + char[] buffer = new char[32]; + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + else + return pack; + } + package: this(uint pack) pure { @@ -649,9 +897,9 @@ immutable double[16][2] scaleFactor = [ [ PI/180, // Degrees double.nan // extended... ], [ - 1/60, // Minute - 1/3600, // Hour - 1/86400, // Day + 1/60.0, // Minute + 1/3600.0, // Hour + 1/86400.0, // Day 1/0.0254, // Inch 1/0.3048, // Foot 1/1609.344, // Mile @@ -688,39 +936,41 @@ immutable double[4] tempOffsets = [ (-273.15*9)/5 + 32 // K -> F ]; -immutable string[ScaledUnit] unitNames = [ +immutable string[Unit] unitNames = [ + Metre : "m", + Metre^^2 : "m²", + Metre^^3 : "m³", + Kilogram : "kg", + Second : "s", + Ampere : "A", + Kelvin : "K", + Candela : "cd", + Radian : "rad", - // ⁰¹⁻²³⁴⁵⁶⁷⁸⁹·° + // derived units + Newton : "N", + Pascal : "Pa", + Joule : "J", + Watt : "W", + Coulomb : "C", + Volt : "V", + Ohm : "Ω", + Farad : "F", + Siemens : "S", + Weber : "Wb", + Tesla : "T", + Henry : "H", + Lumen : "lm", + Lux : "lx", +]; - // base units - ScaledUnit() : "", - ScaledUnit(Metre) : "m", - ScaledUnit(Kilogram) : "kg", - ScaledUnit(Second) : "s", - ScaledUnit(Ampere) : "A", - ScaledUnit(Kelvin) : "°K", - ScaledUnit(Candela) : "cd", - ScaledUnit(Radian) : "rad", +immutable string[ScaledUnit] scaledUnitNames = [ + Minute : "min", +// Minute : "mins", + Hour : "hr", +// Hour : "hrs", + Day : "day", - // derived units - ScaledUnit(SquareMetre) : "m²", - ScaledUnit(CubicMetre) : "m³", - ScaledUnit(Newton) : "N", - ScaledUnit(Pascal) : "Pa", - ScaledUnit(Joule) : "J", - ScaledUnit(Watt) : "W", - ScaledUnit(Coulomb) : "C", - ScaledUnit(Volt) : "V", - ScaledUnit(Ohm) : "Ω", - ScaledUnit(Farad) : "F", - ScaledUnit(Siemens) : "S", - ScaledUnit(Weber) : "Wb", - ScaledUnit(Tesla) : "T", - ScaledUnit(Henry) : "H", - ScaledUnit(Lumen) : "lm", - ScaledUnit(Lux) : "lx", - - // scaled units Inch : "in", Foot : "ft", Mile : "mi", @@ -745,3 +995,143 @@ immutable string[ScaledUnit] unitNames = [ AmpereHour : "Ah", WattHour : "Wh", ]; + +immutable Unit[string] unitMap = [ + // base units + "m" : Metre, + "kg" : Kilogram, + "s" : Second, + "A" : Ampere, + "K" : Kelvin, + "cd" : Candela, + "rad" : Radian, + + // derived units + "N" : Newton, + "Pa" : Pascal, + "J" : Joule, + "W" : Watt, + "C" : Coulomb, + "V" : Volt, + "Ω" : Ohm, + "F" : Farad, + "S" : Siemens, + "Wb" : Weber, + "T" : Tesla, + "H" : Henry, + "lm" : Lumen, + "lx" : Lux, + + // questionable... :/ + "VA" : Watt, + "var" : Watt, +]; + +immutable ScaledUnit[string] noScaleUnitMap = [ + "min" : Minute, + "mins" : Minute, + "hr" : Hour, + "hrs" : Hour, + "day" : Day, + "days" : Day, + "'" : Inch, + "in" : Inch, + "\"" : Foot, + "ft" : Foot, + "mi" : Mile, + "oz" : Ounce, + // TODO: us/uk floz/gallon? + "lb" : Pound, + "°" : Degree, + "deg" : Degree, + "°C" : Celsius, + "°F" : Fahrenheit, + "cy" : Cycle, + "psi" : PSI, + "%" : Percent, + "‰" : Permille, + "‱" : ScaledUnit(Unit(), -4), + "ppm" : ScaledUnit(Unit(), -6), +]; + +// these can have SI prefixes +immutable ScaledUnit[string] scaledUnitMap = [ + "l" : Litre, + "g" : Gram, +]; + +// these can have SI prefixes, but scale must be converted to coefficient +immutable ScaledUnit[string] noScaleUnitMapSI = [ + "Ah" : AmpereHour, + "Wh" : WattHour, + "Hz" : Hertz, + + // questionable... :/ + "VAh" : WattHour, + "varh" : WattHour, +]; + +int take_power(ref const(char)[] s) pure +{ + size_t e = s.findFirst('^'); + if (e < s.length) + { + const(char)[] p = s[e+1..$]; + s = s[0..e]; + if (s.length == 0 || p.length == 0) + return 0; + if (p[0] == '-') + { + if (p.length != 2 || uint(p[2] - '0') > 4) + return 0; + return -(p[2] - '0'); + } + if (p.length != 1 || uint(p[1] - '0') > 4) + return 0; + return p[2] - '0'; + } + else if (s.length > 2) + { + if (s[$-2..$] == "¹") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -1; + } + s = s[0..$-2]; + return 1; + } + if (s[$-2..$] == "²") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -2; + } + s = s[0..$-2]; + return 2; + } + if (s[$-2..$] == "³") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -3; + } + s = s[0..$-2]; + return 3; + } + } + else if (s.length > 3 && s[$-3..$] == "⁴") + { + if (s.length > 6 && s[$-6..$-3] == "⁻") + { + s = s[0..$-6]; + return -4; + } + s = s[0..$-3]; + return 4; + } + return 1; +} diff --git a/src/urt/socket.d b/src/urt/socket.d index 800f72d..7902c74 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -20,6 +20,9 @@ version (Windows) version = HasIPv6; alias SocketHandle = SOCKET; + + enum IPV6_RECVPKTINFO = 49; + enum IPV6_PKTINFO = 50; } else version (Posix) { @@ -28,7 +31,7 @@ else version (Posix) import core.sys.posix.poll; import core.sys.posix.unistd : close, gethostname; import urt.internal.os; // use ImportC to import system C headers... - import core.sys.posix.netinet.in_ : sockaddr_in6; + import core.sys.posix.netinet.in_ : in6_addr, sockaddr_in6; alias _bind = urt.internal.os.bind, _listen = urt.internal.os.listen, _connect = urt.internal.os.connect, _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, @@ -57,119 +60,120 @@ nothrow @nogc: enum SocketResult { - Success, - Failure, - WouldBlock, - NoBuffer, - NetworkDown, - ConnectionRefused, - ConnectionReset, - ConnectionAborted, - ConnectionClosed, - Interrupted, - InvalidSocket, - InvalidArgument, + success, + failure, + would_block, + no_buffer, + network_down, + connection_refused, + connection_reset, + connection_aborted, + connection_closed, + interrupted, + invalid_socket, + invalid_argument, } enum SocketType : byte { - Unknown = -1, - Stream = 0, - Datagram, - Raw, + unknown = -1, + stream = 0, + datagram, + raw, } enum Protocol : byte { - Unknown = -1, - TCP = 0, - UDP, - IP, - ICMP, - Raw, + unknown = -1, + tcp = 0, + udp, + ip, + icmp, + raw, } enum SocketShutdownMode : ubyte { - Read, - Write, - ReadWrite + read, + write, + read_write } enum SocketOption : ubyte { // not traditionally a 'socket option', but this is way more convenient - NonBlocking, + non_blocking, // Socket options - KeepAlive, - Linger, - RandomizePort, - SendBufferLength, - RecvBufferLength, - ReuseAddress, - NoSigPipe, - Error, + keep_alive, + linger, + randomize_port, + send_buffer_length, + recv_buffer_length, + reuse_address, + no_sig_pPipe, + error, // IP options - FirstIpOption, - Multicast = FirstIpOption, - MulticastLoopback, - MulticastTTL, + first_ip_option, + multicast = first_ip_option, + multicast_loopback, + multicast_ttl, + ip_pktinfo, // IPv6 options - FirstIpv6Option, + first_ipv6_option, + ipv6_pktinfo = first_ipv6_option, // ICMP options - FirstIcmpOption = FirstIpv6Option, + first_icmp_option, // ICMPv6 options - FirstIcmpv6Option = FirstIcmpOption, + first_icmpv6_option = first_icmp_option, // TCP options - FirstTcpOption = FirstIcmpv6Option, - TCP_KeepIdle = FirstTcpOption, - TCP_KeepIntvl, - TCP_KeepCnt, - TCP_KeepAlive, // Apple: similar to KeepIdle - TCP_NoDelay, - + first_tcp_option = first_icmpv6_option, + tcp_keep_idle = first_tcp_option, + tcp_keep_intvl, + tcp_keep_cnt, + tcp_keep_alive, // Apple: similar to KeepIdle + tcp_no_delay, // UDP options - FirstUdpOption, + first_udp_option, } enum MsgFlags : ubyte { - None = 0, - OOB = 1 << 0, - Peek = 1 << 1, - Confirm = 1 << 2, - NoSig = 1 << 3, + none = 0, + oob = 1 << 0, + peek = 1 << 1, + confirm = 1 << 2, + no_sig = 1 << 3, //... } enum AddressInfoFlags : ubyte { - None = 0, - Passive = 1 << 0, - CanonName = 1 << 1, - NumericHost = 1 << 2, - NumericServ = 1 << 3, - All = 1 << 4, - AddrConfig = 1 << 5, - V4Mapped = 1 << 6, - FQDN = 1 << 7, + none = 0, + passive = 1 << 0, + canon_name = 1 << 1, + numeric_host = 1 << 2, + numeric_serv = 1 << 3, + all = 1 << 4, + addr_config = 1 << 5, + v4_mapped = 1 << 6, + fqdn = 1 << 7, } enum PollEvents : ubyte { - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Error = 1 << 2, - HangUp = 1 << 3, - Invalid = 1 << 4, + none = 0, + read = 1 << 0, + write = 1 << 1, + error = 1 << 2, + hangup = 1 << 3, + invalid = 1 << 4, } @@ -190,12 +194,13 @@ private: Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Socket socket) { version (HasUnixSocket) {} else - assert(af != AddressFamily.Unix, "Unix sockets not supported on this platform!"); + assert(af != AddressFamily.unix, "Unix sockets not supported on this platform!"); socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); if (socket == Socket.invalid) return socket_getlasterror(); - return Result.Success; + + return Result.success; } Result close(Socket socket) @@ -214,7 +219,7 @@ Result close(Socket socket) // s_noSignal.Erase(socket); // } - return Result.Success; + return Result.success; } Result shutdown(Socket socket, SocketShutdownMode how) @@ -224,15 +229,15 @@ Result shutdown(Socket socket, SocketShutdownMode how) { version (Windows) { - case SocketShutdownMode.Read: t = SD_RECEIVE; break; - case SocketShutdownMode.Write: t = SD_SEND; break; - case SocketShutdownMode.ReadWrite: t = SD_BOTH; break; + case SocketShutdownMode.read: t = SD_RECEIVE; break; + case SocketShutdownMode.write: t = SD_SEND; break; + case SocketShutdownMode.read_write: t = SD_BOTH; break; } else version (Posix) { - case SocketShutdownMode.Read: t = SHUT_RD; break; - case SocketShutdownMode.Write: t = SHUT_WR; break; - case SocketShutdownMode.ReadWrite: t = SHUT_RDWR; break; + case SocketShutdownMode.read: t = SHUT_RD; break; + case SocketShutdownMode.write: t = SHUT_WR; break; + case SocketShutdownMode.read_write: t = SHUT_RDWR; break; } default: assert(false, "Invalid `how`"); @@ -240,41 +245,41 @@ Result shutdown(Socket socket, SocketShutdownMode how) if (_shutdown(socket.handle, t) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result bind(Socket socket, ref const InetAddress address) { ubyte[512] buffer = void; size_t addrLen; - sockaddr* sockAddr = make_sockaddr(address, buffer, addrLen); - assert(sockAddr, "Invalid socket address"); + sockaddr* sock_addr = make_sockaddr(address, buffer, addrLen); + assert(sock_addr, "Invalid socket address"); - if (_bind(socket.handle, sockAddr, cast(int)addrLen) < 0) + if (_bind(socket.handle, sock_addr, cast(int)addrLen) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result listen(Socket socket, uint backlog = -1) { if (_listen(socket.handle, int(backlog & 0x7FFFFFFF)) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result connect(Socket socket, ref const InetAddress address) { ubyte[512] buffer = void; size_t addrLen; - sockaddr* sockAddr = make_sockaddr(address, buffer, addrLen); - assert(sockAddr, "Invalid socket address"); + sockaddr* sock_addr = make_sockaddr(address, buffer, addrLen); + assert(sock_addr, "Invalid socket address"); - if (_connect(socket.handle, sockAddr, cast(int)addrLen) < 0) + if (_connect(socket.handle, sock_addr, cast(int)addrLen) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } -Result accept(Socket socket, out Socket connection, InetAddress* connectingSocketAddress = null) +Result accept(Socket socket, out Socket connection, InetAddress* remote_address = null, InetAddress* local_address = null) { char[sockaddr_storage.sizeof] buffer = void; sockaddr* addr = cast(sockaddr*)buffer.ptr; @@ -283,14 +288,23 @@ Result accept(Socket socket, out Socket connection, InetAddress* connectingSocke connection.handle = _accept(socket.handle, addr, &size); if (connection == Socket.invalid) return socket_getlasterror(); - else if (connectingSocketAddress) - *connectingSocketAddress = make_InetAddress(addr); - return Result.Success; + if (remote_address) + *remote_address = make_InetAddress(addr); + if (local_address) + { + if (getsockname(connection.handle, addr, &size) < 0) + return socket_getlasterror(); + *local_address = make_InetAddress(addr); + } + // platforms are inconsistent regarding whether accept inherits the listening socket's blocking mode + // for consistentency, we always set blocking on the accepted socket + connection.set_socket_option(SocketOption.non_blocking, false); + return Result.success; } -Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None, size_t* bytesSent = null) +Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytes_sent = null) { - Result r = Result.Success; + Result r = Result.success; ptrdiff_t sent = _send(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags)); if (sent < 0) @@ -298,43 +312,43 @@ Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None r = socket_getlasterror(); sent = 0; } - if (bytesSent) - *bytesSent = sent; + if (bytes_sent) + *bytes_sent = sent; return r; } -Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None, const InetAddress* address = null, size_t* bytesSent = null) +Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytes_sent = null) { ubyte[sockaddr_storage.sizeof] tmp = void; size_t addrLen; - sockaddr* sockAddr = null; + sockaddr* sock_addr = null; if (address) { - sockAddr = make_sockaddr(*address, tmp, addrLen); - assert(sockAddr, "Invalid socket address"); + sock_addr = make_sockaddr(*address, tmp, addrLen); + assert(sock_addr, "Invalid socket address"); } - Result r = Result.Success; - ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sockAddr, cast(int)addrLen); + Result r = Result.success; + ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sock_addr, cast(int)addrLen); if (sent < 0) { r = socket_getlasterror(); sent = 0; } - if (bytesSent) - *bytesSent = sent; + if (bytes_sent) + *bytes_sent = sent; return r; } -Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, size_t* bytesReceived) +Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytes_received) { - Result r = Result.Success; + Result r = Result.success; ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); if (bytes > 0) - *bytesReceived = bytes; + *bytes_received = bytes; else { - *bytesReceived = 0; + *bytes_received = 0; if (bytes == 0) { // if we request 0 bytes, we receive 0 bytes, and it doesn't imply end-of-stream @@ -351,52 +365,111 @@ Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, size_t Result error = socket_getlasterror(); // TODO: Do we want a better way to distinguish between receiving a 0-length packet vs no-data (which looks like an error)? // Is a zero-length packet possible to detect in TCP streams? Makes more sense for recvfrom... - SocketResult sr = get_SocketResult(error); - if (sr != SocketResult.WouldBlock) + SocketResult sr = socket_result(error); + if (sr != SocketResult.would_block) r = error; } } return r; } -Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, InetAddress* senderAddress = null, size_t* bytesReceived) +Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, InetAddress* sender_address = null, size_t* bytes_received, InetAddress* local_address = null) { - char[sockaddr_storage.sizeof] addrBuffer = void; - sockaddr* addr = cast(sockaddr*)addrBuffer.ptr; - socklen_t size = addrBuffer.sizeof; + char[sockaddr_storage.sizeof] addr_buffer = void; + sockaddr* addr = cast(sockaddr*)addr_buffer.ptr; - Result r = Result.Success; - ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); - if (bytes >= 0) - *bytesReceived = bytes; + if (local_address) + { + version (Windows) + { + assert(WSARecvMsg, "WSARecvMsg not available!"); + + void[1500] ctrl = void; // HUGE BUFFER! + + WSABUF msg_buf; + msg_buf.buf = cast(char*)buffer.ptr; + msg_buf.len = cast(uint)buffer.length; + + WSAMSG msg; + msg.name = addr; + msg.namelen = addr_buffer.sizeof; + msg.lpBuffers = &msg_buf; + msg.dwBufferCount = 1; + msg.Control.buf = cast(char*)ctrl.ptr; + msg.Control.len = cast(uint)ctrl.length; + msg.dwFlags = 0; + uint bytes; + int r = WSARecvMsg(socket.handle, &msg, &bytes, null, null); + if (r == 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } + + // parse the control messages + *local_address = InetAddress(); + for (WSACMSGHDR* c = WSA_CMSG_FIRSTHDR(&msg); c != null; c = WSA_CMSG_NXTHDR(&msg, c)) + { + if (c.cmsg_level == IPPROTO_IP && c.cmsg_type == IP_PKTINFO) + { + IN_PKTINFO* pk = cast(IN_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPAddr(pk.ipi_addr), 0); // TODO: be nice to populate the listening port... + // pk.ipi_ifindex = receiving interface index + } + if (c.cmsg_level == IPPROTO_IPV6 && c.cmsg_type == IPV6_PKTINFO) + { + IN6_PKTINFO* pk6 = cast(IN6_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPv6Addr(pk6.ipi6_addr), 0); // TODO: be nice to populate the listening port... + // pk6.ipi6_ifindex = receiving interface index + } + } + } + else + { + assert(false, "TODO: call recvmsg and all that..."); + } + } else { - *bytesReceived = 0; - - Result error = socket_getlasterror(); - SocketResult sockRes = get_SocketResult(error); - if (sockRes != SocketResult.NoBuffer && // buffers full - sockRes != SocketResult.ConnectionRefused && // posix error - sockRes != SocketResult.ConnectionReset) // !!! windows may report this error, but it appears to mean something different on posix - r = error; + socklen_t size = addr_buffer.sizeof; + ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); + if (bytes >= 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } } - if (r && senderAddress) - *senderAddress = make_InetAddress(addr); - return r; + + if (sender_address) + *sender_address = make_InetAddress(addr); + return Result.success; + +fail: + Result error = socket_getlasterror(); + SocketResult sockRes = socket_result(error); + if (sockRes != SocketResult.no_buffer && // buffers full + sockRes != SocketResult.connection_refused && // posix error + sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix + return error; + return Result.success; } Result set_socket_option(Socket socket, SocketOption option, const(void)* optval, size_t optlen) { - Result r = Result.Success; + Result r = Result.success; // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rtType != OptType.Unsupported, "Socket option is unsupported on this platform!"); - assert(optlen == s_optTypeRtSize[optInfo.rtType], "Socket option has incorrect payload size!"); + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(optlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); // special case for non-blocking // this is not strictly a 'socket option', but this rather simplifies our API - if (option == SocketOption.NonBlocking) + if (option == SocketOption.non_blocking) { bool value = *cast(const(bool)*)optval; version (Windows) @@ -420,30 +493,30 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval // LockGuard!SharedMutex lock(s_noSignalMut); // s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); // -// if (optInfo.platformType == OptType.Unsupported) +// if (opt_info.platform_type == OptType.unsupported) // return r; // } // determine the option 'level' OptLevel level = get_optlevel(option); version (HasIPv6) {} else - assert(level != OptLevel.IPv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); + assert(level != OptLevel.ipv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); // platforms don't all agree on option data formats! const(void)* arg = optval; int itmp = void; linger ling = void; - if (optInfo.rtType != optInfo.platformType) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.rtType) + switch (opt_info.rt_type) { // TODO: there are more converstions necessary as options/platforms are added - case OptType.Bool: + case OptType.bool_: { const bool value = *cast(const(bool)*)optval; - switch (optInfo.platformType) + switch (opt_info.platform_type) { - case OptType.Int: + case OptType.int_: itmp = value ? 1 : 0; arg = &itmp; break; @@ -451,20 +524,20 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } break; } - case OptType.Duration: + case OptType.duration: { const Duration value = *cast(const(Duration)*)optval; - switch (optInfo.platformType) + switch (opt_info.platform_type) { - case OptType.Seconds: + case OptType.seconds: itmp = cast(int)value.as!"seconds"; arg = &itmp; break; - case OptType.Milliseconds: + case OptType.milliseconds: itmp = cast(int)value.as!"msecs"; arg = &itmp; break; - case OptType.Linger: + case OptType.linger: itmp = cast(int)value.as!"seconds"; ling = linger(!!itmp, cast(ushort)itmp); arg = &ling; @@ -479,88 +552,88 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } // set the option - r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(const(char)*)arg, s_optTypePlatformSize[optInfo.platformType]); + r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); return r; } Result set_socket_option(Socket socket, SocketOption option, bool value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Bool, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); return set_socket_option(socket, option, &value, bool.sizeof); } Result set_socket_option(Socket socket, SocketOption option, int value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Int, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); return set_socket_option(socket, option, &value, int.sizeof); } Result set_socket_option(Socket socket, SocketOption option, Duration value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Duration, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); return set_socket_option(socket, option, &value, Duration.sizeof); } Result set_socket_option(Socket socket, SocketOption option, IPAddr value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.INAddress, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); return set_socket_option(socket, option, &value, IPAddr.sizeof); } Result set_socket_option(Socket socket, SocketOption option, ref MulticastGroup value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.MulticastGroup, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.multicast_group, "Incorrect value type for option"); return set_socket_option(socket, option, &value, MulticastGroup.sizeof); } Result get_socket_option(Socket socket, SocketOption option, void* output, size_t outputlen) { - Result r = Result.Success; + Result r = Result.success; // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rtType != OptType.Unsupported, "Socket option is unsupported on this platform!"); - assert(outputlen == s_optTypeRtSize[optInfo.rtType], "Socket option has incorrect payload size!"); + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(outputlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); - assert(option != SocketOption.NonBlocking, "Socket option NonBlocking cannot be get"); + assert(option != SocketOption.non_blocking, "Socket option NonBlocking cannot be get"); // determine the option 'level' OptLevel level = get_optlevel(option); version (HasIPv6) - assert(level != OptLevel.IPv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); // platforms don't all agree on option data formats! void* arg = output; int itmp = 0; linger ling = { 0, 0 }; - if (optInfo.rtType != optInfo.platformType) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.platformType) + switch (opt_info.platform_type) { - case OptType.Int: - case OptType.Seconds: - case OptType.Milliseconds: + case OptType.int_: + case OptType.seconds: + case OptType.milliseconds: { arg = &itmp; break; } - case OptType.Linger: + case OptType.linger: { arg = &ling; break; @@ -570,39 +643,39 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - socklen_t writtenLen = s_optTypePlatformSize[optInfo.platformType]; + socklen_t writtenLen = s_optTypePlatformSize[opt_info.platform_type]; // get the option - r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(char*)arg, &writtenLen); + r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); - if (optInfo.rtType != optInfo.platformType) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.rtType) + switch (opt_info.rt_type) { // TODO: there are more converstions necessary as options/platforms are added - case OptType.Bool: + case OptType.bool_: { bool* value = cast(bool*)output; - switch (optInfo.platformType) + switch (opt_info.platform_type) { - case OptType.Int: + case OptType.int_: *value = !!itmp; break; default: assert(false, "Unexpected"); } break; } - case OptType.Duration: + case OptType.duration: { Duration* value = cast(Duration*)output; - switch (optInfo.platformType) + switch (opt_info.platform_type) { - case OptType.Seconds: + case OptType.seconds: *value = seconds(itmp); break; - case OptType.Milliseconds: + case OptType.milliseconds: *value = msecs(itmp); break; - case OptType.Linger: + case OptType.linger: *value = seconds(ling.l_linger); break; default: assert(false, "Unexpected"); @@ -614,10 +687,10 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - assert(optInfo.rtType != OptType.INAddress, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); + assert(opt_info.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); /+ // Options expected in network-byte order - switch (optInfo.rtType) + switch (opt_info.rt_type) { case OptType.INAddress: { @@ -634,37 +707,37 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ Result get_socket_option(Socket socket, SocketOption option, out bool output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Bool, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); return get_socket_option(socket, option, &output, bool.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out int output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Int, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); return get_socket_option(socket, option, &output, int.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out Duration output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Duration, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); return get_socket_option(socket, option, &output, Duration.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out IPAddr output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.INAddress, "Incorrect value type for option"); + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); return get_socket_option(socket, option, &output, IPAddr.sizeof); } @@ -680,27 +753,27 @@ Result set_keepalive(Socket socket, bool enable, Duration keepIdle, Duration kee uint bytesReturned = 0; if (WSAIoctl(socket.handle, SIO_KEEPALIVE_VALS, &alive, alive.sizeof, null, 0, &bytesReturned, null, null) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } else { - Result res = set_socket_option(socket, SocketOption.KeepAlive, enable); - if (!enable || res != Result.Success) + Result res = set_socket_option(socket, SocketOption.keep_alive, enable); + if (!enable || res != Result.success) return res; version (Darwin) { // OSX doesn't support setting keep-alive interval and probe count. - return set_socket_option(socket, SocketOption.TCP_KeepAlive, keepIdle); + return set_socket_option(socket, SocketOption.tcp_keep_alive, keepIdle); } else { - res = set_socket_option(socket, SocketOption.TCP_KeepIdle, keepIdle); - if (res != Result.Success) + res = set_socket_option(socket, SocketOption.tcp_keep_idle, keepIdle); + if (res != Result.success) return res; - res = set_socket_option(socket, SocketOption.TCP_KeepIntvl, keepInterval); - if (res != Result.Success) + res = set_socket_option(socket, SocketOption.tcp_keep_intvl, keepInterval); + if (res != Result.success) return res; - return set_socket_option(socket, SocketOption.TCP_KeepCnt, keepCount); + return set_socket_option(socket, SocketOption.tcp_keep_cnt, keepCount); } } } @@ -716,7 +789,7 @@ Result get_peer_name(Socket socket, out InetAddress name) name = make_InetAddress(addr); else return socket_getlasterror(); - return Result.Success; + return Result.success; } Result get_socket_name(Socket socket, out InetAddress name) @@ -730,7 +803,7 @@ Result get_socket_name(Socket socket, out InetAddress name) name = make_InetAddress(addr); else return socket_getlasterror(); - return Result.Success; + return Result.success; } Result get_hostname(char* name, size_t len) @@ -738,12 +811,12 @@ Result get_hostname(char* name, size_t len) int fail = gethostname(name, cast(int)len); if (fail) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressInfo* hints, out AddressInfoResolver result) { - import urt.array : findFirst; + import urt.string : findFirst; import urt.mem.temp : tstringz; size_t colon = nodeName.findFirst(':'); @@ -760,9 +833,9 @@ Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressIn // translate hints... tmpHints.ai_flags = map_addrinfo_flags(hints.flags); tmpHints.ai_family = s_addressFamily[hints.family]; - tmpHints.ai_socktype = s_socketType[hints.sockType]; + tmpHints.ai_socktype = s_socketType[hints.sock_type]; tmpHints.ai_protocol = s_protocol[hints.protocol]; - tmpHints.ai_canonname = cast(char*)hints.canonName; // HAX! + tmpHints.ai_canonname = cast(char*)hints.canon_name; // HAX! tmpHints.ai_addrlen = 0; tmpHints.ai_addr = null; tmpHints.ai_next = null; @@ -780,7 +853,7 @@ Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressIn result.m_internal[0] = res; result.m_internal[1] = res; - return Result.Success; + return Result.success; } Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) @@ -795,8 +868,8 @@ Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) { fds[i].fd = pollFds[i].socket.handle; fds[i].revents = 0; - fds[i].events = ((pollFds[i].requestEvents & PollEvents.Read) ? POLLRDNORM : 0) | - ((pollFds[i].requestEvents & PollEvents.Write) ? POLLWRNORM : 0); + fds[i].events = ((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | + ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0); } version (Windows) int r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); @@ -812,14 +885,14 @@ Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) numEvents = r; for (size_t i = 0; i < pollFds.length; ++i) { - pollFds[i].returnEvents = cast(PollEvents)( - ((fds[i].revents & POLLRDNORM) ? PollEvents.Read : 0) | - ((fds[i].revents & POLLWRNORM) ? PollEvents.Write : 0) | - ((fds[i].revents & POLLERR) ? PollEvents.Error : 0) | - ((fds[i].revents & POLLHUP) ? PollEvents.HangUp : 0) | - ((fds[i].revents & POLLNVAL) ? PollEvents.Invalid : 0)); + pollFds[i].return_events = cast(PollEvents)( + ((fds[i].revents & POLLRDNORM) ? PollEvents.read : 0) | + ((fds[i].revents & POLLWRNORM) ? PollEvents.write : 0) | + ((fds[i].revents & POLLERR) ? PollEvents.error : 0) | + ((fds[i].revents & POLLHUP) ? PollEvents.hangup : 0) | + ((fds[i].revents & POLLNVAL) ? PollEvents.invalid : 0)); } - return Result.Success; + return Result.success; } Result poll(ref PollFd pollFd, Duration timeout, out uint numEvents) @@ -831,9 +904,9 @@ struct AddressInfo { AddressInfoFlags flags; AddressFamily family; - SocketType sockType; + SocketType sock_type; Protocol protocol; - const(char)* canonName; // Note: this memory is valid until the next call to `next_address`, or until `AddressInfoResolver` is destroyed + const(char)* canon_name; // Note: this memory is valid until the next call to `next_address`, or until `AddressInfoResolver` is destroyed InetAddress address; } @@ -878,11 +951,11 @@ nothrow @nogc: addrinfo* info = cast(addrinfo*)(m_internal[1]); m_internal[1] = info.ai_next; - addressInfo.flags = AddressInfoFlags.None; // info.ai_flags is only used for 'hints' + addressInfo.flags = AddressInfoFlags.none; // info.ai_flags is only used for 'hints' addressInfo.family = map_address_family(info.ai_family); - addressInfo.sockType = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.Unknown; + addressInfo.sock_type = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.unknown; addressInfo.protocol = map_protocol(info.ai_protocol); - addressInfo.canonName = info.ai_canonname; + addressInfo.canon_name = info.ai_canonname; addressInfo.address = make_InetAddress(info.ai_addr); return true; } @@ -893,9 +966,9 @@ nothrow @nogc: struct PollFd { Socket socket; - PollEvents requestEvents; - PollEvents returnEvents; - void* userData; + PollEvents request_events; + PollEvents return_events; + void* user_data; } @@ -920,74 +993,74 @@ Result get_socket_error(Socket socket) // TODO: !!! enum Result ConnectionClosedResult = Result(-12345); -SocketResult get_SocketResult(Result result) +SocketResult socket_result(Result result) { if (result) - return SocketResult.Success; + return SocketResult.success; if (result.systemCode == ConnectionClosedResult.systemCode) - return SocketResult.ConnectionClosed; + return SocketResult.connection_closed; version (Windows) { if (result.systemCode == WSAEWOULDBLOCK) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == WSAEINPROGRESS) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == WSAENOBUFS) - return SocketResult.NoBuffer; + return SocketResult.no_buffer; if (result.systemCode == WSAENETDOWN) - return SocketResult.NetworkDown; + return SocketResult.network_down; if (result.systemCode == WSAECONNREFUSED) - return SocketResult.ConnectionRefused; + return SocketResult.connection_refused; if (result.systemCode == WSAECONNRESET) - return SocketResult.ConnectionReset; + return SocketResult.connection_reset; if (result.systemCode == WSAEINTR) - return SocketResult.Interrupted; + return SocketResult.interrupted; if (result.systemCode == WSAENOTSOCK) - return SocketResult.InvalidSocket; + return SocketResult.invalid_socket; if (result.systemCode == WSAEINVAL) - return SocketResult.InvalidArgument; + return SocketResult.invalid_argument; } else version (Posix) { static if (EAGAIN != EWOULDBLOCK) if (result.systemCode == EAGAIN) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == EWOULDBLOCK) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == EINPROGRESS) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == ENOMEM) - return SocketResult.NoBuffer; + return SocketResult.no_buffer; if (result.systemCode == ENETDOWN) - return SocketResult.NetworkDown; + return SocketResult.network_down; if (result.systemCode == ECONNREFUSED) - return SocketResult.ConnectionRefused; + return SocketResult.connection_refused; if (result.systemCode == ECONNRESET) - return SocketResult.ConnectionReset; + return SocketResult.connection_reset; if (result.systemCode == EINTR) - return SocketResult.Interrupted; + return SocketResult.interrupted; if (result.systemCode == EINVAL) - return SocketResult.InvalidArgument; + return SocketResult.invalid_argument; } - return SocketResult.Failure; + return SocketResult.failure; } sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addrLen) { - sockaddr* sockAddr = cast(sockaddr*)buffer.ptr; + sockaddr* sock_addr = cast(sockaddr*)buffer.ptr; switch (address.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { addrLen = sockaddr_in.sizeof; if (buffer.length < sockaddr_in.sizeof) return null; - sockaddr_in* ain = cast(sockaddr_in*)sockAddr; + sockaddr_in* ain = cast(sockaddr_in*)sock_addr; memzero(ain, sockaddr_in.sizeof); - ain.sin_family = s_addressFamily[AddressFamily.IPv4]; + ain.sin_family = s_addressFamily[AddressFamily.ipv4]; version (Windows) { ain.sin_addr.S_un.S_un_b.s_b1 = address._a.ipv4.addr.b[0]; @@ -1002,7 +1075,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ storeBigEndian(&ain.sin_port, ushort(address._a.ipv4.port)); break; } - case AddressFamily.IPv6: + case AddressFamily.ipv6: { version (HasIPv6) { @@ -1010,11 +1083,11 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ if (buffer.length < sockaddr_in6.sizeof) return null; - sockaddr_in6* ain6 = cast(sockaddr_in6*)sockAddr; + sockaddr_in6* ain6 = cast(sockaddr_in6*)sock_addr; memzero(ain6, sockaddr_in6.sizeof); - ain6.sin6_family = s_addressFamily[AddressFamily.IPv6]; + ain6.sin6_family = s_addressFamily[AddressFamily.ipv6]; storeBigEndian(&ain6.sin6_port, cast(ushort)address._a.ipv6.port); - storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flowInfo); + storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flow_info); storeBigEndian(cast(uint*)&ain6.sin6_scope_id, address._a.ipv6.scopeId); for (int a = 0; a < 8; ++a) { @@ -1030,7 +1103,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { @@ -1038,9 +1111,9 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ // if (buffer.length < sockaddr_un.sizeof) // return null; // -// sockaddr_un* aun = cast(sockaddr_un*)sockAddr; +// sockaddr_un* aun = cast(sockaddr_un*)sock_addr; // memzero(aun, sockaddr_un.sizeof); -// aun.sun_family = s_addressFamily[AddressFamily.Unix]; +// aun.sun_family = s_addressFamily[AddressFamily.unix]; // // memcpy(aun.sun_path, address.un.path, UNIX_PATH_LEN); // break; @@ -1050,7 +1123,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ } default: { - sockAddr = null; + sock_addr = null; addrLen = 0; assert(false, "Unsupported address family"); @@ -1058,62 +1131,43 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ } } - return sockAddr; + return sock_addr; } -InetAddress make_InetAddress(const(sockaddr)* sockAddress) +InetAddress make_InetAddress(const(sockaddr)* sock_address) { InetAddress addr; - addr.family = map_address_family(sockAddress.sa_family); + addr.family = map_address_family(sock_address.sa_family); switch (addr.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { - const sockaddr_in* ain = cast(const(sockaddr_in)*)sockAddress; + const sockaddr_in* ain = cast(const(sockaddr_in)*)sock_address; addr._a.ipv4.port = loadBigEndian(&ain.sin_port); - version (Windows) - { - addr._a.ipv4.addr.b[0] = ain.sin_addr.S_un.S_un_b.s_b1; - addr._a.ipv4.addr.b[1] = ain.sin_addr.S_un.S_un_b.s_b2; - addr._a.ipv4.addr.b[2] = ain.sin_addr.S_un.S_un_b.s_b3; - addr._a.ipv4.addr.b[3] = ain.sin_addr.S_un.S_un_b.s_b4; - } - else version (Posix) - addr._a.ipv4.addr.address = ain.sin_addr.s_addr; - else - assert(false, "Not implemented!"); + addr._a.ipv4.addr = make_IPAddr(ain.sin_addr); break; } - case AddressFamily.IPv6: + case AddressFamily.ipv6: { version (HasIPv6) { - const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sockAddress; + const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sock_address; addr._a.ipv6.port = loadBigEndian(&ain6.sin6_port); - addr._a.ipv6.flowInfo = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); + addr._a.ipv6.flow_info = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); addr._a.ipv6.scopeId = loadBigEndian(cast(const(uint)*)&ain6.sin6_scope_id); - - for (int a = 0; a < 8; ++a) - { - version (Windows) - addr._a.ipv6.addr.s[a] = loadBigEndian(&ain6.sin6_addr.in6_u.u6_addr16[a]); - else version (Posix) - addr._a.ipv6.addr.s[a] = loadBigEndian(cast(const(ushort)*)ain6.sin6_addr.s6_addr + a); - else - assert(false, "Not implemented!"); - } + addr._a.ipv6.addr = make_IPv6Addr(ain6.sin6_addr); } else assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { -// const sockaddr_un* aun = cast(const(sockaddr_un)*)sockAddress; +// const sockaddr_un* aun = cast(const(sockaddr_un)*)sock_address; // // memcpy(addr.un.path, aun.sun_path, UNIX_PATH_LEN); // if (UNIX_PATH_LEN < UnixPathLen) @@ -1131,33 +1185,64 @@ InetAddress make_InetAddress(const(sockaddr)* sockAddress) return addr; } +IPAddr make_IPAddr(ref const in_addr in4) +{ + IPAddr addr; + version (Windows) + { + addr.b[0] = in4.S_un.S_un_b.s_b1; + addr.b[1] = in4.S_un.S_un_b.s_b2; + addr.b[2] = in4.S_un.S_un_b.s_b3; + addr.b[3] = in4.S_un.S_un_b.s_b4; + } + else version (Posix) + addr.address = in4.s_addr; + else + assert(false, "Not implemented!"); + return addr; +} + +IPv6Addr make_IPv6Addr(ref const in6_addr in6) +{ + IPv6Addr addr; + for (int a = 0; a < 8; ++a) + { + version (Windows) + addr.s[a] = loadBigEndian(&in6.in6_u.u6_addr16[a]); + else version (Posix) + addr.s[a] = loadBigEndian(cast(const(ushort)*)in6.s6_addr + a); + else + assert(false, "Not implemented!"); + } + return addr; +} private: enum OptLevel : ubyte { - Socket, - IP, - IPv6, - ICMP, - ICMPv6, - TCP, - UDP, + socket, + ip, + ipv6, + icmp, + icmpv6, + tcp, + udp, } enum OptType : ubyte { - Unsupported, - Bool, - Int, - Seconds, - Milliseconds, - Duration, - INAddress, // IPAddr + in_addr - //IN6Address, // IPv6Addr + in6_addr - MulticastGroup, // MulticastGroup + ip_mreq - //MulticastGroupIPv6, // MulticastGroupIPv6? + ipv6_mreq - Linger, + unsupported, + bool_, + int_, + seconds, + milliseconds, + duration, + inet_addr, // IPAddr + in_addr + //inet6_addr, // IPv6Addr + in6_addr + multicast_group, // MulticastGroup + ip_mreq + //multicast_group_ipv6, // MulticastGroupIPv6? + ipv6_mreq + linger, // etc... } @@ -1169,8 +1254,8 @@ __gshared immutable ubyte[] s_optTypePlatformSize = [ 0, 0, int.sizeof, int.size struct OptInfo { short option; - OptType rtType; - OptType platformType; + OptType rt_type; + OptType platform_type; } __gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ @@ -1182,15 +1267,15 @@ __gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ AddressFamily map_address_family(int addressFamily) { if (addressFamily == AF_INET) - return AddressFamily.IPv4; + return AddressFamily.ipv4; else if (addressFamily == AF_INET6) - return AddressFamily.IPv6; + return AddressFamily.ipv6; else if (addressFamily == AF_UNIX) - return AddressFamily.Unix; + return AddressFamily.unix; else if (addressFamily == AF_UNSPEC) - return AddressFamily.Unspecified; + return AddressFamily.unspecified; assert(false, "Unsupported address family"); - return AddressFamily.Unknown; + return AddressFamily.unknown; } __gshared immutable int[SocketType.max+1] s_socketType = [ @@ -1201,13 +1286,13 @@ __gshared immutable int[SocketType.max+1] s_socketType = [ SocketType map_socket_type(int sockType) { if (sockType == SOCK_STREAM) - return SocketType.Stream; + return SocketType.stream; else if (sockType == SOCK_DGRAM) - return SocketType.Datagram; + return SocketType.datagram; else if (sockType == SOCK_RAW) - return SocketType.Raw; + return SocketType.raw; assert(false, "Unsupported socket type"); - return SocketType.Unknown; + return SocketType.unknown; } __gshared immutable int[Protocol.max+1] s_protocol = [ @@ -1220,17 +1305,17 @@ __gshared immutable int[Protocol.max+1] s_protocol = [ Protocol map_protocol(int protocol) { if (protocol == IPPROTO_TCP) - return Protocol.TCP; + return Protocol.tcp; else if (protocol == IPPROTO_UDP) - return Protocol.UDP; + return Protocol.udp; else if (protocol == IPPROTO_IP) - return Protocol.IP; + return Protocol.ip; else if (protocol == IPPROTO_ICMP) - return Protocol.ICMP; + return Protocol.icmp; else if (protocol == IPPROTO_RAW) - return Protocol.Raw; + return Protocol.raw; assert(false, "Unsupported protocol"); - return Protocol.Unknown; + return Protocol.unknown; } version (linux) @@ -1261,68 +1346,74 @@ else version (Windows) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), - OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), -// OptInfo( SO_RANDOMIZE_PORT, OptType.Bool, OptType.Int ), // TODO: BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( -1, OptType.Bool, OptType.Unsupported ), // NoSignalPipe - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), +// OptInfo( SO_RANDOMIZE_PORT, OptType.bool_, OptType.int_ ), // TODO: BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.bool_, OptType.unsupported ), // NoSignalPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else version (linux) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), - OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( -1, OptType.Bool, OptType.Unsupported ), // NoSignalPipe - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( TCP_KEEPIDLE, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_KEEPINTVL, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_KEEPCNT, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.bool_, OptType.unsupported ), // NoSignalPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( TCP_KEEPIDLE, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPINTVL, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPCNT, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else version (Darwin) { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( SO_NOSIGPIPE, OptType.Bool, OptType.Int ), - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_KEEPALIVE, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( SO_NOSIGPIPE, OptType.bool_, OptType.int_ ), + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_KEEPALIVE, OptType.duration, OptType.seconds ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else @@ -1331,12 +1422,12 @@ else int map_message_flags(MsgFlags flags) { int r = 0; - if (flags & MsgFlags.OOB) r |= MSG_OOB; - if (flags & MsgFlags.Peek) r |= MSG_PEEK; + if (flags & MsgFlags.oob) r |= MSG_OOB; + if (flags & MsgFlags.peek) r |= MSG_PEEK; version (linux) { - if (flags & MsgFlags.Confirm) r |= MSG_CONFIRM; - if (flags & MsgFlags.NoSig) r |= MSG_NOSIGNAL; + if (flags & MsgFlags.confirm) r |= MSG_CONFIRM; + if (flags & MsgFlags.no_sig) r |= MSG_NOSIGNAL; } return r; } @@ -1344,27 +1435,27 @@ int map_message_flags(MsgFlags flags) int map_addrinfo_flags(AddressInfoFlags flags) { int r = 0; - if (flags & AddressInfoFlags.Passive) r |= AI_PASSIVE; - if (flags & AddressInfoFlags.CanonName) r |= AI_CANONNAME; - if (flags & AddressInfoFlags.NumericHost) r |= AI_NUMERICHOST; - if (flags & AddressInfoFlags.NumericServ) r |= AI_NUMERICSERV; - if (flags & AddressInfoFlags.All) r |= AI_ALL; - if (flags & AddressInfoFlags.AddrConfig) r |= AI_ADDRCONFIG; - if (flags & AddressInfoFlags.V4Mapped) r |= AI_V4MAPPED; + if (flags & AddressInfoFlags.passive) r |= AI_PASSIVE; + if (flags & AddressInfoFlags.canon_name) r |= AI_CANONNAME; + if (flags & AddressInfoFlags.numeric_host) r |= AI_NUMERICHOST; + if (flags & AddressInfoFlags.numeric_serv) r |= AI_NUMERICSERV; + if (flags & AddressInfoFlags.all) r |= AI_ALL; + if (flags & AddressInfoFlags.addr_config) r |= AI_ADDRCONFIG; + if (flags & AddressInfoFlags.v4_mapped) r |= AI_V4MAPPED; version (Windows) - if (flags & AddressInfoFlags.FQDN) r |= AI_FQDN; + if (flags & AddressInfoFlags.fqdn) r |= AI_FQDN; return r; } OptLevel get_optlevel(SocketOption opt) { - if (opt < SocketOption.FirstIpOption) return OptLevel.Socket; - else if (opt < SocketOption.FirstIpv6Option) return OptLevel.IP; - else if (opt < SocketOption.FirstIcmpOption) return OptLevel.IPv6; - else if (opt < SocketOption.FirstIcmpv6Option) return OptLevel.ICMP; - else if (opt < SocketOption.FirstTcpOption) return OptLevel.ICMPv6; - else if (opt < SocketOption.FirstUdpOption) return OptLevel.TCP; - else return OptLevel.UDP; + if (opt < SocketOption.first_ip_option) return OptLevel.socket; + else if (opt < SocketOption.first_ipv6_option) return OptLevel.ip; + else if (opt < SocketOption.first_icmp_option) return OptLevel.ipv6; + else if (opt < SocketOption.first_icmpv6_option) return OptLevel.icmp; + else if (opt < SocketOption.first_tcp_option) return OptLevel.icmpv6; + else if (opt < SocketOption.first_udp_option) return OptLevel.tcp; + else return OptLevel.udp; } @@ -1376,6 +1467,28 @@ version (Windows) WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // what if this fails??? + + // this is truly the worst thing I ever wrote!! + enum SIO_GET_EXTENSION_FUNCTION_POINTER = 0xC8000006; + struct GUID { uint Data1; ushort Data2, Data3; ubyte[8] Data4; } + __gshared immutable GUID WSAID_WSARECVMSG = GUID(0xF689D7C8, 0x6F1F, 0x436B, [0x8A,0x53,0xE5,0x4F,0xE3,0x51,0xC3,0x22]); + + Socket dummy; + uint bytes = 0; + if (!create_socket(AddressFamily.ipv4, SocketType.datagram, Protocol.udp, dummy)) + goto FAIL; + if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSARECVMSG, cast(uint)GUID.sizeof, + &WSARecvMsg, cast(uint)WSARecvMsgFn.sizeof, &bytes, null, null) != 0) + goto FAIL; + assert(bytes == WSARecvMsgFn.sizeof); + dummy.close(); + if (!WSARecvMsg) + goto FAIL; + return; + + FAIL: + import urt.log; + writeWarning("Failed to get WSARecvMsg function pointer - recvfrom() won't be able to report the dst address"); } pragma(crt_destructor) @@ -1394,43 +1507,78 @@ version (Windows) { // stuff that's missing from the windows headers... - enum: int { + enum : int + { AI_NUMERICSERV = 0x0008, AI_ALL = 0x0100, AI_V4MAPPED = 0x0800, AI_FQDN = 0x4000, } - struct pollfd + struct ip_mreq { - SOCKET fd; // Socket handle - SHORT events; // Requested events to monitor - SHORT revents; // Returned events indicating status + in_addr imr_multiaddr; + in_addr imr_interface; } - alias WSAPOLLFD = pollfd; - alias PWSAPOLLFD = pollfd*; - alias LPWSAPOLLFD = pollfd*; - - enum: short { - POLLRDNORM = 0x0100, - POLLRDBAND = 0x0200, - POLLIN = (POLLRDNORM | POLLRDBAND), - POLLPRI = 0x0400, - - POLLWRNORM = 0x0010, - POLLOUT = (POLLWRNORM), - POLLWRBAND = 0x0020, - - POLLERR = 0x0001, - POLLHUP = 0x0002, - POLLNVAL = 0x0004 + + struct WSAMSG + { + LPSOCKADDR name; + int namelen; + LPWSABUF lpBuffers; + uint dwBufferCount; + WSABUF Control; + uint dwFlags; } + alias LPWSAMSG = WSAMSG*; - extern(Windows) int WSAPoll(LPWSAPOLLFD fdArray, uint fds, int timeout); + struct WSABUF + { + uint len; + char* buf; + } + alias LPWSABUF = WSABUF*; - struct ip_mreq + alias WSARecvMsgFn = extern(Windows) int function(SOCKET s, LPWSAMSG lpMsg, uint* lpdwNumberOfBytesRecvd, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + __gshared WSARecvMsgFn WSARecvMsg; + + struct IN_PKTINFO { - in_addr imr_multiaddr; - in_addr imr_interface; + in_addr ipi_addr; + uint ipi_ifindex; + } + struct IN6_PKTINFO + { + in6_addr ipi6_addr; + uint ipi6_ifindex; + } + + struct WSACMSGHDR + { + size_t cmsg_len; + int cmsg_level; + int cmsg_type; } + alias LPWSACMSGHDR = WSACMSGHDR*; + + LPWSACMSGHDR WSA_CMSG_FIRSTHDR(LPWSAMSG msg) + => msg.Control.len >= WSACMSGHDR.sizeof ? cast(LPWSACMSGHDR)msg.Control.buf : null; + + LPWSACMSGHDR WSA_CMSG_NXTHDR(LPWSAMSG msg, LPWSACMSGHDR cmsg) + { + if (!cmsg) + return WSA_CMSG_FIRSTHDR(msg); + if (cast(ubyte*)cmsg + WSA_CMSGHDR_ALIGN(cmsg.cmsg_len) + WSACMSGHDR.sizeof > cast(ubyte*)msg.Control.buf + msg.Control.len) + return null; + return cast(LPWSACMSGHDR)(cast(ubyte*)cmsg + WSA_CMSGHDR_ALIGN(cmsg.cmsg_len)); + } + + void* WSA_CMSG_DATA(LPWSACMSGHDR cmsg) + => cast(ubyte*)cmsg + WSA_CMSGDATA_ALIGN(WSACMSGHDR.sizeof); + + size_t WSA_CMSGHDR_ALIGN(size_t length) + => (length + WSACMSGHDR.alignof-1) & ~(WSACMSGHDR.alignof-1); + + size_t WSA_CMSGDATA_ALIGN(size_t length) + => (length + size_t.alignof-1) & ~(size_t.alignof-1); } diff --git a/src/urt/string/ansi.d b/src/urt/string/ansi.d index eac1150..931d60d 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -68,33 +68,33 @@ enum ANSI_RESET = "\x1b[0m"; nothrow @nogc: -size_t parseANSICode(const(char)[] text) +size_t parse_ansi_code(const(char)[] text) { - import urt.string.ascii : isNumeric; + import urt.string.ascii : is_numeric; if (text.length < 3 || text[0] != '\x1b') return 0; if (text[1] != '[' && text[1] != 'O') return 0; size_t i = 2; - for (; i < text.length && (text[i].isNumeric || text[i] == ';'); ++i) + for (; i < text.length && (text[i].is_numeric || text[i] == ';'); ++i) {} if (i == text.length) return 0; return i + 1; } -char[] stripDecoration(char[] text) pure +char[] strip_decoration(char[] text) pure { - return stripDecoration(text, text); + return strip_decoration(text, text); } -char[] stripDecoration(const(char)[] text, char[] buffer) pure +char[] strip_decoration(const(char)[] text, char[] buffer) pure { size_t len = text.length, outLen = 0; char* dst = buffer.ptr; const(char)* src = text.ptr; - bool writeOutput = text.ptr != buffer.ptr; + bool write_output = text.ptr != buffer.ptr; for (size_t i = 0; i < len;) { char c = src[i]; @@ -106,11 +106,11 @@ char[] stripDecoration(const(char)[] text, char[] buffer) pure if (j < len && src[j] == 'm') { i = j + 1; - writeOutput = true; + write_output = true; continue; } } - if (BranchMoreExpensiveThanStore || writeOutput) + if (BranchMoreExpensiveThanStore || write_output) dst[outLen] = c; // skip stores where unnecessary (probably the common case) ++outLen; } diff --git a/src/urt/string/ascii.d b/src/urt/string/ascii.d index 5541d3a..36f814f 100644 --- a/src/urt/string/ascii.d +++ b/src/urt/string/ascii.d @@ -2,14 +2,14 @@ module urt.string.ascii; -char[] toLower(const(char)[] str) pure nothrow +char[] to_lower(const(char)[] str) pure nothrow { - return toLower(str, new char[str.length]); + return to_lower(str, new char[str.length]); } -char[] toUpper(const(char)[] str) pure nothrow +char[] to_upper(const(char)[] str) pure nothrow { - return toUpper(str, new char[str.length]); + return to_upper(str, new char[str.length]); } @@ -17,7 +17,7 @@ nothrow @nogc: // some character category flags... // 1 = alpha, 2 = numeric, 4 = white, 8 = newline, 10 = control, 20 = ???, 40 = url, 80 = hex -__gshared immutable ubyte[128] charDetails = [ +private __gshared immutable ubyte[128] char_details = [ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x14, 0x18, 0x10, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, @@ -28,20 +28,20 @@ __gshared immutable ubyte[128] charDetails = [ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x40, 0x10 ]; -__gshared immutable char[16] hexDigits = "0123456789ABCDEF"; +__gshared immutable char[16] hex_digits = "0123456789ABCDEF"; -bool isSpace(char c) pure => c < 128 && (charDetails[c] & 4); -bool isNewline(char c) pure => c < 128 && (charDetails[c] & 8); -bool isWhitespace(char c) pure => c < 128 && (charDetails[c] & 0xC); -bool isAlpha(char c) pure => c < 128 && (charDetails[c] & 1); -bool isNumeric(char c) pure => cast(uint)(c - '0') <= 9; -bool isAlphaNumeric(char c) pure => c < 128 && (charDetails[c] & 3); -bool isHex(char c) pure => c < 128 && (charDetails[c] & 0x80); -bool isControlChar(char c) pure => c < 128 && (charDetails[c] & 0x10); -bool isURL(char c) pure => c < 128 && (charDetails[c] & 0x40); +bool is_space(char c) pure => c < 128 && (char_details[c] & 4); +bool is_newline(char c) pure => c < 128 && (char_details[c] & 8); +bool is_whitespace(char c) pure => c < 128 && (char_details[c] & 0xC); +bool is_alpha(char c) pure => c < 128 && (char_details[c] & 1); +bool is_numeric(char c) pure => cast(uint)(c - '0') <= 9; +bool is_alpha_numeric(char c) pure => c < 128 && (char_details[c] & 3); +bool is_hex(char c) pure => c < 128 && (char_details[c] & 0x80); +bool is_control_char(char c) pure => c < 128 && (char_details[c] & 0x10); +bool is_url(char c) pure => c < 128 && (char_details[c] & 0x40); -char toLower(char c) pure +char to_lower(char c) pure { // this is the typical way; which is faster on a weak arch? // if (c >= 'A' && c <= 'Z') @@ -53,7 +53,7 @@ char toLower(char c) pure return c; } -char toUpper(char c) pure +char to_upper(char c) pure { // this is the typical way; which is faster on a weak arch? // if (c >= 'a' && c <= 'z') @@ -65,26 +65,62 @@ char toUpper(char c) pure return c; } -char[] toLower(const(char)[] str, char[] buffer) pure +char[] to_lower(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = toLower(str[i]); - return buffer; + buffer[i] = str[i].to_lower; + return buffer[0..str.length]; } -char[] toUpper(const(char)[] str, char[] buffer) pure +char[] to_upper(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = toUpper(str[i]); - return buffer; + buffer[i] = str[i].to_upper; + return buffer[0..str.length]; } -char[] toLower(char[] str) pure +char[] to_lower(char[] str) pure { - return toLower(str, str); + return to_lower(str, str); } -char[] toUpper(char[] str) pure +char[] to_upper(char[] str) pure { - return toUpper(str, str); + return to_upper(str, str); } + +ptrdiff_t cmp(bool case_insensitive = false)(const(char)[] a, const(char)[] b) pure +{ + if (a.length != b.length) + return a.length - b.length; + static if (case_insensitive) + { + for (size_t i = 0; i < a.length; ++i) + { + char ca = a[i].to_lower; + char cb = b[i].to_lower; + if (ca != cb) + return ca - cb; + } + } + else + { + for (size_t i = 0; i < a.length; ++i) + if (a[i] != b[i]) + return a[i] - b[i]; + } + return 0; +} + +ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b); + +bool eq(const(char)[] a, const(char)[] b) pure + => cmp(a, b) == 0; + +bool ieq(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b) == 0; diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 6016615..ae564c6 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -1,35 +1,33 @@ module urt.string.format; -import urt.conv : parseIntFast; +import urt.conv : parse_int_fast; import urt.string; import urt.traits; import urt.util; public import urt.mem.temp : tformat, tconcat, tstring; - nothrow @nogc: debug { + // format functions are not re-entrant! static bool InFormatFunction = false; } alias StringifyFunc = ptrdiff_t delegate(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc; -alias IntifyFunc = ptrdiff_t delegate() nothrow @nogc; -ptrdiff_t toString(T)(auto ref T value, char[] buffer) +ptrdiff_t toString(T)(auto ref T value, char[] buffer, const(char)[] format = null, const(FormatArg)[] formatArgs = null) { - import urt.string.format : FormatArg; - debug InFormatFunction = true; - FormatArg a = FormatArg(value); - ptrdiff_t r = a.getString(buffer, null, null); + ptrdiff_t r = get_to_string_func(value)(buffer, null, null); debug InFormatFunction = false; return r; } +alias formatValue = toString; // TODO: remove me? + char[] concat(Args...)(char[] buffer, auto ref Args args) { static if (Args.length == 0) @@ -83,11 +81,10 @@ char[] concat(Args...)(char[] buffer, auto ref Args args) // this implementation handles all the other kinds of things! debug if (!__ctfe) InFormatFunction = true; - FormatArg[Args.length] argFuncs; - // TODO: no need to collect int-ify functions in the arg set... + StringifyFunc[Args.length] arg_funcs = void; static foreach(i, arg; args) - argFuncs[i] = FormatArg(arg); - char[] r = concatImpl(buffer, argFuncs); + arg_funcs[i] = get_to_string_func(arg); + char[] r = concatImpl(buffer, arg_funcs); debug if (!__ctfe) InFormatFunction = false; return r; } @@ -104,90 +101,147 @@ char[] format(Args...)(char[] buffer, const(char)[] fmt, ref Args args) return r; } -ptrdiff_t formatValue(T)(auto ref T value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) -{ - static if (is(typeof(&value.toString) : StringifyFunc)) - return value.toString(buffer, format, formatArgs); - else - return value.defFormat.toString(buffer, format, formatArgs); -} - struct FormatArg { - enum IsAggregate(T) = is(T == struct) || is(T == class); - private this(T)(ref T value) pure nothrow @nogc { - static if (IsAggregate!T && is(typeof(&value.toString) : StringifyFunc)) - toString = &value.toString; - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format", cast(FormatArg[])null) == 0)) - { - // wrap in a delegate that adjusts for format + args... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format") == 0)) - { - // wrap in a delegate that adjusts for format... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer) == 0)) - { - // wrap in a delegate... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString((const(char)[]){}) == 0)) - { - // version with a sink function... - // wrap in a delegate that adjusts for format + args... - static assert(false); - } - else - toString = &defFormat(value).toString; + getString = get_to_string_func(value); - static if (is(typeof(&defFormat(value).toInt))) - toInt = &defFormat(value).toInt; - else - toInt = null; + static if (can_default_int!T) + _to_int_fun = &DefInt!T.to_int; } - ptrdiff_t getString(char[] buffer, const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(buffer, format, args); - } + StringifyFunc getString; + ptrdiff_t getLength(const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(null, format, args); - } + => getString(null, format, args); - bool canInt() const nothrow @nogc - { - return toInt != null; - } - ptrdiff_t getInt() const nothrow @nogc + bool canInt() const pure nothrow @nogc + => _to_int_fun !is null; + + ptrdiff_t getInt() const pure nothrow @nogc { - return toInt(); + ptrdiff_t delegate() pure nothrow @nogc to_int; + to_int.ptr = getString.ptr; + to_int.funcptr = _to_int_fun; + return to_int(); } private: - // TODO: we could assert that the delegate pointers match, and only store it once... - StringifyFunc toString; - IntifyFunc toInt; + // only store the function pointer, since 'this' will be common + ptrdiff_t function() pure nothrow @nogc _to_int_fun; } private: -pragma(inline, true) -DefFormat!T* defFormat(T)(ref const(T) value) pure nothrow @nogc +alias StringifyFuncReduced = ptrdiff_t delegate(char[] buffer, const(char)[] format) nothrow @nogc; +alias StringifyFuncReduced2 = ptrdiff_t delegate(char[] buffer) nothrow @nogc; + +enum can_default_int(T) = is_some_int!T || is(T == bool); + +template to_string_overload_index(T) { - return cast(DefFormat!T*)&value; + static if (!is(T == Unqual!T)) // minimise redundant instantiations + enum to_string_overload_index = to_string_overload_index!(Unqual!T); + else + enum to_string_overload_index = () { + static if (__traits(hasMember, T, "toString")) + { + // multiple passes so that we correctly preference the overload with more arguments... + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) + { + static if (is(typeof(&overload) : typeof(StringifyFunc.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFunc.funcptr))) + return i; + } + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) + { + static if (is(typeof(&overload) : typeof(StringifyFuncReduced.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFuncReduced.funcptr))) + return i; + } + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) + { + static if (is(typeof(&overload) : typeof(StringifyFuncReduced2.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFuncReduced2.funcptr))) + return i; + } + } + return -1; + }(); } -ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc +StringifyFunc get_to_string_func(T)(ref T value) { - return (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + enum overload_index = to_string_overload_index!T; + + static if (overload_index >= 0) + { + alias overload = __traits(getOverloads, T, "toString", true)[overload_index]; + static if (__traits(isTemplate, overload)) + alias to_string = overload!(); + else + alias to_string = overload; + + // TODO: we can't alias the __traits(child) expression, so we need to repeat it everywhere! + + alias method_type = typeof(&__traits(child, value, to_string)); + + static if (is(method_type : StringifyFunc)) + return &__traits(child, value, to_string); + + else static if (is(method_type : StringifyFuncReduced) || is(method_type : StringifyFuncReduced2)) + { + StringifyFunc d; + d.ptr = &value; + static if (is(method_type : StringifyFuncReduced)) + d.funcptr = &ToStringShim.shim1!T; + else + d.funcptr = &ToStringShim.shim2!T; + return d; + } + + // TODO: do we want to support toString variants with sink instead of buffer? + + return null; + } + else + return &(cast(DefFormat!T*)&value).toString; +} + +struct ToStringShim +{ + ptrdiff_t shim1(T)(char[] buffer, const(char)[] format, const(FormatArg)[]) + { + ref T _this = *cast(T*)&this; + return _this.toString(buffer, format); + } + ptrdiff_t shim2(T)(char[] buffer, const(char)[], const(FormatArg)[]) + { + ref T _this = *cast(T*)&this; + return _this.toString(buffer); + } } +struct DefInt(T) +{ + T value; + + ptrdiff_t to_int() const pure nothrow @nogc + { + static if (T.max > ptrdiff_t.max) + debug assert(value <= ptrdiff_t.max); + return cast(ptrdiff_t)value; + } +} + +ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc + => (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + struct DefFormat(T) { T value; @@ -269,39 +323,39 @@ struct DefFormat(T) } else static if (is(T == double) || is(T == float)) { - import urt.conv : formatFloat, formatInt; + import urt.conv : format_float, format_int; char[16] tmp = void; if (format.length && format[0] == '*') { bool success; - size_t arg = format[1..$].parseIntFast(success); + size_t arg = format[1..$].parse_int_fast(success); if (!success || !formatArgs[arg].canInt) return -2; size_t width = formatArgs[arg].getInt; - size_t len = width.formatInt(tmp); + size_t len = width.format_int(tmp); format = tmp[0..len]; } - return formatFloat(value, buffer, format); + return format_float(value, buffer, format); } else static if (is(T == ulong) || is(T == long)) { - import urt.conv : formatInt, formatUint; + import urt.conv : format_int, format_uint; // TODO: what formats are interesting for ints? bool leadingZeroes = false; - bool toLower = false; + bool to_lower = false; bool varLen = false; ptrdiff_t padding = 0; uint base = 10; static if (is(T == long)) { - bool showSign = false; + bool show_sign = false; if (format.length && format[0] == '+') { - showSign = true; + show_sign = true; format.popFront; } } @@ -315,10 +369,10 @@ struct DefFormat(T) varLen = true; format.popFront; } - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; - padding = format.parseIntFast(success); + padding = format.parse_int_fast(success); if (varLen) { if (padding < 0 || !formatArgs[padding].canInt) @@ -332,7 +386,7 @@ struct DefFormat(T) if (b == 'x') { base = 16; - toLower = format[0] == 'x' && buffer.ptr; + to_lower = format[0] == 'x' && buffer.ptr; } else if (b == 'b') base = 2; @@ -344,11 +398,11 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = formatInt(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); + ptrdiff_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); else - size_t len = formatUint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + ptrdiff_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); - if (toLower && len > 0) + if (to_lower && len > 0) { for (size_t i = 0; i < len; ++i) if (cast(uint)(buffer.ptr[i] - 'A') < 26) @@ -392,12 +446,12 @@ struct DefFormat(T) varLen = true; format.popFront; } - if (varLen && (!format.length || !format[0].isNumeric)) + if (varLen && (!format.length || !format[0].is_numeric)) return -2; - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; - width = format.parseIntFast(success); + width = format.parse_int_fast(success); if (varLen) { if (width < 0 || !formatArgs[width].canInt) @@ -450,15 +504,15 @@ struct DefFormat(T) return 0; int grp1 = 1, grp2 = 0; - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; - grp1 = cast(int)format.parseIntFast(success); + grp1 = cast(int)format.parse_int_fast(success); if (success && format.length > 0 && format[0] == ':' && - format.length > 1 && format[1].isNumeric) + format.length > 1 && format[1].is_numeric) { format.popFront(); - grp2 = cast(int)format.parseIntFast(success); + grp2 = cast(int)format.parse_int_fast(success); } if (!success) return -2; @@ -473,6 +527,8 @@ struct DefFormat(T) } char[] hex = toHexString(cast(ubyte[])value, buffer, grp1, grp2); + if (!hex.ptr) + return -1; return hex.length; } else static if (is(T : const U[], U)) @@ -567,6 +623,9 @@ struct DefFormat(T) } else static if (is(T == class)) { + // HACK: class toString is not @nogc, so we'll just stringify the pointer for right now... + return defToString(cast(void*)value, buffer, format, formatArgs); +/+ try { const(char)[] t = (cast()value).toString(); @@ -579,6 +638,7 @@ struct DefFormat(T) } catch (Exception) return -1; ++/ } else static if (is(T == const)) { @@ -586,6 +646,8 @@ struct DefFormat(T) } else static if (is(T == struct)) { + static assert(!__traits(hasMember, T, "toString"), "Struct with custom toString not properly selected!"); + // general structs if (buffer.ptr) { @@ -630,27 +692,27 @@ struct DefFormat(T) } return ++len; } - else - static assert(false, "Not implemented for type: ", T.stringof); - } - - static if (isSomeInt!T || is(T == bool)) - { - ptrdiff_t toInt() const pure nothrow @nogc + else static if (is(T == function)) { - static if (T.max > ptrdiff_t.max) - debug assert(value <= ptrdiff_t.max); - return cast(ptrdiff_t)value; + assert(false, "TODO"); + return 0; } + else static if (is(T == delegate)) + { + assert(false, "TODO"); + return 0; + } + else + static assert(false, "Not implemented for type: ", T.stringof); } } -char[] concatImpl(char[] buffer, const(FormatArg)[] args) nothrow @nogc +char[] concatImpl(char[] buffer, const(StringifyFunc)[] args) nothrow @nogc { size_t len = 0; foreach (a; args) { - ptrdiff_t r = a.getString(buffer.ptr ? buffer[len..$] : null, null, null); + ptrdiff_t r = a(buffer.ptr ? buffer[len..$] : null, null, null); if (r < 0) return null; len += r; @@ -744,7 +806,7 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA // get the arg index bool success; - arg = format.parseIntFast(success); + arg = format.parse_int_fast(success); if (!success) { assert(false, "Invalid format string: Number expected!"); @@ -807,7 +869,7 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA // } bool success; - ptrdiff_t index = formatSpec.parseIntFast(success); + ptrdiff_t index = formatSpec.parse_int_fast(success); // if (varRef) // { // if (arg < 0 || !args[arg].canInt) @@ -861,6 +923,8 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA if (bytes < 0) return -2; char[] t = formatImpl(buffer, indirectFormat.ptr[0 .. bytes], args); + if (!t.ptr) + return -1; len = t.length; } else @@ -922,9 +986,9 @@ unittest template allAreStrings(Args...) { static if (Args.length == 1) - enum allAreStrings = is(Args[0] : const(char[])) || is(isSomeChar!(Args[0])); + enum allAreStrings = is(Args[0] : const(char[])) || is(is_some_char!(Args[0])); else - enum allAreStrings = (is(Args[0] : const(char[])) || is(isSomeChar!(Args[0]))) && allAreStrings!(Args[1 .. $]); + enum allAreStrings = (is(Args[0] : const(char[])) || is(is_some_char!(Args[0]))) && allAreStrings!(Args[1 .. $]); } template allConstCorrectStrings(Args...) @@ -943,7 +1007,7 @@ template constCorrectedStrings(Args...) { static if (is(Ty : const(char)[])) constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char[])); - else static if (isSomeChar!Ty) + else static if (is_some_char!Ty) constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char)); else static assert(false, "Argument must be a char array or a char: ", T); diff --git a/src/urt/string/package.d b/src/urt/string/package.d index c4abc6f..ca5ee74 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -1,132 +1,249 @@ module urt.string; +import urt.string.uni; +import urt.traits : is_some_char; + public import urt.string.ascii; public import urt.string.string; public import urt.string.tailstring; // seful string operations defined elsewhere public import urt.array : empty, popFront, popBack, takeFront, takeBack; -public import urt.mem : strlen; - - -enum TempStringBufferLen = 1024; -enum TempStringMaxLen = TempStringBufferLen / 2; +public import urt.mem : strlen, wcslen; +public import urt.mem.temp : tstringz, twstringz; -static char[TempStringBufferLen] s_tempStringBuffer; -static size_t s_tempStringBufferPos = 0; +nothrow @nogc: -char[] allocTempString(size_t len) nothrow @nogc +size_t strlen_s(const(char)[] s) pure { - assert(len <= TempStringMaxLen); + size_t len = 0; + while (len < s.length && s[len] != '\0') + ++len; + return len; +} - if (len <= TempStringBufferLen - s_tempStringBufferPos) +ptrdiff_t cmp(bool case_insensitive = false, T, U)(const(T)[] a, const(U)[] b) pure +{ + static if (case_insensitive) + return uni_compare_i(a, b); + else { - char[] s = s_tempStringBuffer[s_tempStringBufferPos .. s_tempStringBufferPos + len]; - s_tempStringBufferPos += len; - return s; + static if (is(T == U)) + { + if (a.length != b.length) + return a.length - b.length; + } + return uni_compare(a, b); } - s_tempStringBufferPos = len; - return s_tempStringBuffer[0 .. len]; } -char* tstringz(const(char)[] str) nothrow @nogc -{ - char[] buffer = allocTempString(str.length + 1); - buffer[0..str.length] = str[]; - buffer[str.length] = 0; - return buffer.ptr; -} +ptrdiff_t icmp(T, U)(const(T)[] a, const(U)[] b) pure + => cmp!true(a, b); + +bool eq(const(char)[] a, const(char)[] b) pure + => cmp(a, b) == 0; + +bool ieq(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b) == 0; -wchar* twstringz(const(char)[] str) nothrow @nogc +size_t findFirst(bool case_insensitive = false, T, U)(const(T)[] s, const U c) + if (is_some_char!T && is_some_char!U) { - wchar[] buffer = cast(wchar[])allocTempString((str.length + 1) * 2); + static if (is(U == char)) + assert(c <= 0x7F, "Invalid UTF character"); + else static if (is(U == wchar)) + assert(c < 0xD800 || c >= 0xE000, "Invalid UTF character"); - // TODO: actually decode UTF8 into UTF16!! + // TODO: what if `c` is 'ß'? do we find "ss" in case-insensitive mode? + // and if `c` is 's', do we match 'ß'? + + static if (case_insensitive) + const U lc = cast(U)c.uni_case_fold(); + else + alias lc = c; - foreach (i, c; str) - buffer[i] = c; - buffer[str.length] = 0; - return buffer.ptr; + size_t i = 0; + while (i < s.length) + { + static if (U.sizeof <= T.sizeof) + { + enum l = 1; + dchar d = s[i]; + } + else + { + size_t l; + dchar d = next_dchar(s[i..$], l); + } + static if (case_insensitive) + { + static if (is(U == char)) + { + // only fold the ascii characters, since lc is known to be ascii + if (uint(d - 'A') < 26) + d |= 0x20; + } + else + d = d.uni_case_fold(); + } + if (d == lc) + break; + i += l; + } + return i; } -ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +size_t find_first_i(T, U)(const(T)[] s, U c) + if (is_some_char!T && is_some_char!U) + => findFirst!true(s, c); + +size_t findLast(bool case_insensitive = false, T, U)(const(T)[] s, const U c) + if (is_some_char!T && is_some_char!U) { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + static assert(case_insensitive == false, "TODO"); + + static if (is(U == char)) + assert(c <= 0x7F, "Invalid unicode character"); + else static if (is(U == wchar)) + assert(c >= 0xD800 && c < 0xE000, "Invalid unicode character"); + + ptrdiff_t last = s.length-1; + while (last >= 0) { - ptrdiff_t diff = a[i] - b[i]; - if (diff) - return diff; + static if (U.sizeof <= T.sizeof) + { + if (s[last] == c) + return cast(size_t)last; + } + else + { + // this is tricky, because we need to seek backwards to the start of surrogate sequences + assert(false, "TODO"); + } } - return 0; + return s.length; } -ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +size_t find_last_i(T, U)(const(T)[] s, U c) + if (is_some_char!T && is_some_char!U) + => findLast!true(s, c); + +size_t findFirst(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + if (t.length == 0) + return 0; + + // fast-path for one-length tokens + size_t l = t.uni_seq_len(); + if (l == t.length) { - ptrdiff_t diff = toLower(a[i]) - toLower(b[i]); - if (diff) - return diff; + dchar c = t.next_dchar(l); + if (c < 0x80) + return findFirst!case_insensitive(s, cast(char)c); + if (c < 0x10000) + return findFirst!case_insensitive(s, cast(wchar)c); + return findFirst!case_insensitive(s, c); } - return 0; + + size_t offset = 0; + while (offset < s.length) + { + + static if (case_insensitive) + int c = uni_compare_i(s[offset .. $], t); + else + int c = uni_compare(s[offset .. $], t); + if (c == int.max || c == 0) + return offset; + if (c == int.min) + return s.length; + offset += s[offset .. $].uni_seq_len(); + } + return s.length; +} + +size_t find_first_i(T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) + => findFirst!true(s, t); + +size_t findLast(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) +{ + // this is tricky, because we need to seek backwards to the start of surrogate sequences + assert(false, "TODO"); } -bool ieq(const(char)[] a, const(char)[] b) pure nothrow @nogc - => icmp(a, b) == 0; +size_t find_last_i(T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) + => findLast!true(s, t); -bool startsWith(const(char)[] s, const(char)[] prefix) pure nothrow @nogc +bool contains(bool case_insensitive = false, T, U)(const(T)[] s, U c, size_t *offset = null) + if (is_some_char!T && is_some_char!U) +{ + size_t i = findFirst!case_insensitive(s, c); + if (i == s.length) + return false; + if (offset) + *offset = i; + return true; +} + +bool contains(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t, size_t *offset = null) + if (is_some_char!T && is_some_char!U) +{ + size_t i = findFirst!case_insensitive(s, t); + if (i == s.length) + return false; + if (offset) + *offset = i; + return true; +} + +bool contains_i(T, U)(const(T)[] s, U c, size_t *offset = null) + if (is_some_char!T && is_some_char!U) + => contains!true(s, c, offset); + +bool contains_i(T, U)(const(T)[] s, const(U)[] t, size_t *offset = null) + if (is_some_char!T && is_some_char!U) + => contains!true(s, t, offset); + +bool startsWith(const(char)[] s, const(char)[] prefix) pure { if (s.length < prefix.length) return false; - return s[0 .. prefix.length] == prefix[]; + return cmp(s[0 .. prefix.length], prefix) == 0; } -bool endsWith(const(char)[] s, const(char)[] suffix) pure nothrow @nogc +bool endsWith(const(char)[] s, const(char)[] suffix) pure { if (s.length < suffix.length) return false; - return s[$ - suffix.length .. $] == suffix[]; + return cmp(s[$ - suffix.length .. $], suffix) == 0; } -inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure nothrow @nogc +inout(char)[] trim(alias pred = is_whitespace, bool Front = true, bool Back = true)(inout(char)[] s) pure { size_t first = 0, last = s.length; static if (Front) { - while (first < s.length && isWhitespace(s.ptr[first])) + while (first < s.length && pred(s.ptr[first])) ++first; } static if (Back) { - while (last > first && isWhitespace(s.ptr[last - 1])) + while (last > first && pred(s.ptr[last - 1])) --last; } return s.ptr[first .. last]; } -alias trimFront = trim!(true, false); +alias trimFront(alias pred = is_whitespace) = trim!(pred, true, false); -alias trimBack = trim!(false, true); +alias trimBack(alias pred = is_whitespace) = trim!(pred, false, true); -inout(char)[] trimComment(char Delimiter)(inout(char)[] s) -{ - size_t i = 0; - for (; i < s.length; ++i) - { - if (s[i] == Delimiter) - break; - } - while(i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) - --i; - return s[0 .. i]; -} - -inout(char)[] takeLine(ref inout(char)[] s) pure nothrow @nogc +inout(char)[] takeLine(ref inout(char)[] s) pure { for (size_t i = 0; i < s.length; ++i) { @@ -148,20 +265,26 @@ inout(char)[] takeLine(ref inout(char)[] s) pure nothrow @nogc return t; } -inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] s) +inout(char)[] split(char[] separators, bool handle_quotes = true, bool do_trim = true)(ref inout(char)[] s, char* separator = null) pure { - static if (HandleQuotes) + static if (handle_quotes) int inQuotes = 0; else enum inQuotes = false; size_t i = 0; - for (; i < s.length; ++i) + loop: for (; i < s.length; ++i) { - if (s[i] == Separator && !inQuotes) - break; - - static if (HandleQuotes) + static foreach (sep; separators) + { + if (s[i] == sep && !inQuotes) + { + if (separator) + *separator = s[i]; + break loop; + } + } + static if (handle_quotes) { if (s[i] == '"' && !(inQuotes & 0x6)) inQuotes = 1 - inQuotes; @@ -171,40 +294,26 @@ inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] inQuotes = 4 - inQuotes; } } - inout(char)[] t = s[0 .. i].trimBack; - s = i < s.length ? s[i+1 .. $].trimFront : null; - return t; -} - -inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) -{ - sep = '\0'; - int inQuotes = 0; - size_t i = 0; - loop: for (; i < s.length; ++i) + static if (do_trim) { - static foreach (S; Separator) - { - static assert(is(typeof(S) == char), "Only single character separators supported"); - if (s[i] == S && !inQuotes) - { - sep = s[i]; - break loop; - } - } - if (s[i] == '"' && !(inQuotes & 0x6)) - inQuotes = 1 - inQuotes; - else if (s[i] == '\'' && !(inQuotes & 0x5)) - inQuotes = 2 - inQuotes; - else if (s[i] == '`' && !(inQuotes & 0x3)) - inQuotes = 4 - inQuotes; + inout(char)[] t = s[0 .. i].trimBack; + s = i < s.length ? s[i+1 .. $].trimFront : null; + } + else + { + inout(char)[] t = s[0 .. i]; + s = i < s.length ? s[i+1 .. $] : null; } - inout(char)[] t = s[0 .. i].trimBack; - s = i < s.length ? s[i+1 .. $].trimFront : null; return t; } -char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc +alias split(char separator, bool handle_quotes = true, bool do_trim = true) = split!([separator], handle_quotes, do_trim); + +// TODO: deprecate this one... +inout(char)[] split(separators...)(ref inout(char)[] s, out char sep) pure + => split!([separators], true, true)(s, &sep); + +char[] unQuote(const(char)[] s, char[] buffer) pure { // TODO: should this scan and match quotes rather than assuming there are no rogue closing quotes in the middle of the string? if (s.empty) @@ -223,18 +332,18 @@ char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc return buffer; } -char[] unQuote(char[] s) pure nothrow @nogc +char[] unQuote(char[] s) pure { return unQuote(s, s); } -char[] unQuote(const(char)[] s) nothrow @nogc +char[] unQuote(const(char)[] s) { import urt.mem.temp : talloc; return unQuote(s, cast(char[])talloc(s.length)); } -char[] unEscape(inout(char)[] s, char[] buffer) pure nothrow @nogc +char[] unEscape(inout(char)[] s, char[] buffer) pure { if (s.empty) return null; @@ -266,17 +375,17 @@ char[] unEscape(inout(char)[] s, char[] buffer) pure nothrow @nogc return buffer[0..len]; } -char[] unEscape(char[] s) pure nothrow @nogc +char[] unEscape(char[] s) pure { return unEscape(s, s); } -char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure nothrow @nogc +char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure { - import urt.util : isPowerOf2; - assert(group.isPowerOf2); - assert(secondaryGroup.isPowerOf2); + import urt.util : is_power_of_2; + assert(group.is_power_of_2); + assert(secondaryGroup.is_power_of_2); assert((secondaryGroup == 0 && seps.length > 0) || seps.length > 1, "Secondary grouping requires additional separator"); if (data.length == 0) @@ -296,8 +405,8 @@ char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secon size_t offset = 0; for (size_t i = 0; true; ) { - buffer[offset++] = hexDigits[src[i] >> 4]; - buffer[offset++] = hexDigits[src[i] & 0xF]; + buffer[offset++] = hex_digits[src[i] >> 4]; + buffer[offset++] = hex_digits[src[i] & 0xF]; bool sep = (i & mask) == mask; if (++i == data.length) @@ -307,7 +416,7 @@ char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secon } } -char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") nothrow @nogc +char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") { import urt.mem.temp; @@ -319,7 +428,7 @@ char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, unittest { - ubyte[] data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; + ubyte[8] data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; assert(data.toHexString(0) == "0123456789ABCDEF"); assert(data.toHexString(1) == "01 23 45 67 89 AB CD EF"); assert(data.toHexString(2) == "0123 4567 89AB CDEF"); @@ -329,17 +438,476 @@ unittest } -bool wildcardMatch(const(char)[] wildcard, const(char)[] value) +// Pattern grammar: +// * - Match zero or more characters +// ? - Match exactly one character +// # - Match exactly one digit (0-9) +// ~T - Token T is optional (matches with or without T) +// \C - Escape character C (literal match) +// Examples: +// "h*o" matches "hello", "ho" +// "h?llo" matches "hello", "hallo" but not "hllo" +// "v#.#" matches "v1.2", "v3.7" but not "va.b" +// "~ab" matches "ab", "b" +// "h\*o" matches "h*o" literally +bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false, bool case_insensitive = false) pure { - // TODO: write this function... + const(char)* a = wildcard.ptr, ae = a + wildcard.length, b = value.ptr, be = b + value.length; - // HACK: we just use this for tail wildcards right now... - for (size_t i = 0; i < wildcard.length; ++i) + static inout(char)* skip_wilds(inout(char)* p, const(char)* pe) { - if (wildcard[i] == '*') - return true; - if (wildcard[i] != value[i]) + while (p < pe) + { + if (*p == '*') + ++p; + else if (*p == '~') + { + one_more: + if (++p < pe) + { + if (*p == '~') + goto one_more; + if (*p++ == '\\' && p < pe) + ++p; + } + } + else break; + } + // HACK: skip trailing slash (handle a degenerate case) + if (p == pe - 1 && *p == '\\') + ++p; + return p; + } + + struct BacktrackState + { + const(char)* a, b; + } + BacktrackState[64] backtrack_stack = void; + size_t backtrack_depth = 0; + + char ca_orig = void, cb_orig = void, ca = void, cb = void; + + while (a < ae && b < be) + { + ca_orig = *a, cb_orig = *b; + + // handle wildcards + if (ca_orig == '*') + { + a = skip_wilds(++a, ae); + if (a == ae) // tail star matches everything + return true; + + const(char)[] a_remain = a[0 .. ae - a]; + for (; b <= be; ++b) + { + if (wildcard_match(a_remain, b[0 .. be - b], value_wildcard, case_insensitive)) + return true; + } return false; + } + + // handle optionals + if (ca_orig == '~') + { + while (++a < ae && *a == '~') {} + if (a == ae) + break; + + if (backtrack_depth < backtrack_stack.length) + { + backtrack_stack[backtrack_depth].a = a + (*a == '\\' ? 2 : 1); + backtrack_stack[backtrack_depth].b = b; + ++backtrack_depth; + } + continue; + } + + // handle escape + ca = ca_orig, cb = cb_orig; + if (ca == '\\') + { + if (++a < ae) + ca = *a; + else + break; + } + + // handle value wildcards + if (value_wildcard) + { + if (cb_orig == '*') + { + b = skip_wilds(++b, be); + if (b == be) // tail star matches everything + return true; + + const(char)[] b_remain = b[0 .. be - b]; + for (; a <= ae; ++a) + { + if (wildcard_match(a[0 .. ae - a], b_remain, true, case_insensitive)) + return true; + } + return false; + } + if (cb_orig == '~') + { + while (++b < be && *b == '~') {} + if (b == be) + break; + + if (backtrack_depth < backtrack_stack.length) + { + backtrack_stack[backtrack_depth].a = a; + backtrack_stack[backtrack_depth].b = b + (*b == '\\' ? 2 : 1); + ++backtrack_depth; + } + continue; + } + if (cb == '\\') + { + if (++b < be) + cb = *b; + else + break; + } + if (cb_orig == '#') + { + if (ca.is_numeric || ca_orig == '#') + goto advance; + } + } + + // compare next char + if (ca_orig == '#') + { + if (cb.is_numeric) + goto advance; + } + else if ((case_insensitive ? ca.to_lower == cb.to_lower : ca == cb) || (ca_orig == '?') || (value_wildcard && cb_orig == '?')) + goto advance; + + try_backtrack: + if (backtrack_depth == 0) + return false; + + --backtrack_depth; + a = backtrack_stack[backtrack_depth].a; + b = backtrack_stack[backtrack_depth].b; + continue; + + advance: + ++a, ++b; + } + + // check the strings are equal... + if (a == ae) + { + if (value_wildcard) + b = skip_wilds(b, be); + if (b == be) + return true; } - return wildcard.length == value.length; + else if (b == be && skip_wilds(a, ae) == ae) + return true; + + goto try_backtrack; +} + +bool wildcard_match_i(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure + => wildcard_match(wildcard, value, value_wildcard, true); + + +unittest +{ + // test findFirst + assert("hello".findFirst('e') == 1); + assert("hello".findFirst('a') == 5); + assert("hello".findFirst("e") == 1); + assert("hello".findFirst("ll") == 2); + assert("hello".findFirst("lo") == 3); + assert("hello".findFirst("la") == 5); + assert("hello".findFirst("low") == 5); + assert("héllo".findFirst('é') == 1); + assert("héllo"w.findFirst('é') == 1); + assert("héllo".findFirst("éll") == 1); + assert("héllo".findFirst('a') == 6); + assert("héllo".findFirst("la") == 6); + assert("hello".find_first_i('E') == 1); + assert("HELLO".find_first_i("e") == 1); + assert("hello".find_first_i("LL") == 2); + assert("héllo".find_first_i('É') == 1); + assert("HÉLLO".find_first_i("é") == 1); + assert("HÉLLO".find_first_i("éll") == 1); + + assert("HÉLLO".contains('É')); + assert(!"HÉLLO".contains('A')); + assert("HÉLLO".contains_i("éll")); + + // test wildcard_match + assert(wildcard_match("hello", "hello")); + assert(!wildcard_match("hello", "world")); + assert(wildcard_match("h*o", "hello")); + assert(wildcard_match("h*", "hello")); + assert(wildcard_match("*o", "hello")); + assert(wildcard_match("*", "hello")); + assert(wildcard_match("h?llo", "hello")); + assert(!wildcard_match("h?llo", "hllo")); + assert(wildcard_match("h??lo", "hello")); + assert(!wildcard_match("a*b", "axxxc")); + + // test optional - basic cases + assert(wildcard_match("~ab", "b")); + assert(wildcard_match("~ab", "ab")); + assert(wildcard_match("a~b", "a")); + assert(wildcard_match("a~b", "ab")); + assert(!wildcard_match("~ab", "a")); + assert(!wildcard_match("a~b", "b")); + assert(!wildcard_match("a~b", "ac")); + + // optional - multiple optionals + assert(wildcard_match("~a~b", "")); + assert(wildcard_match("~a~b", "a")); + assert(wildcard_match("~a~b", "b")); + assert(wildcard_match("~a~b", "ab")); + assert(!wildcard_match("~a~b", "ba")); + assert(wildcard_match("~a~b~c", "")); + assert(wildcard_match("~a~b~c", "ac")); + assert(wildcard_match("~a~b~c", "abc")); + + // optional with wildcards + assert(wildcard_match("~a*", "")); + assert(wildcard_match("~a*", "a")); + assert(wildcard_match("~a*", "abc")); + assert(wildcard_match("*~a", "")); + assert(wildcard_match("*~a", "a")); + assert(wildcard_match("*~a", "xya")); + assert(wildcard_match("a~b*c", "ac")); + assert(wildcard_match("a~b*c", "abc")); + assert(wildcard_match("a~b*c", "abxc")); + assert(wildcard_match("a~b*c", "axbc")); + assert(!wildcard_match("a*~bc", "a")); + assert(wildcard_match("a*~bc", "ac")); + assert(wildcard_match("a*~bc", "abc")); + assert(wildcard_match("a*~bc", "axbc")); + + // optional with ? + assert(wildcard_match("~a?", "a")); + assert(wildcard_match("~a?", "x")); + assert(wildcard_match("~a?", "ax")); + assert(wildcard_match("?~a", "x")); + assert(wildcard_match("?~a", "xa")); + assert(wildcard_match("?~a", "a")); + assert(wildcard_match("?~a", "aa")); + + // optional with # + assert(wildcard_match("~a#", "5")); + assert(wildcard_match("~a#", "a5")); + assert(!wildcard_match("~a#", "a")); + assert(!wildcard_match("~a#", "ax")); + assert(wildcard_match("v~#.#", "v.5")); + assert(wildcard_match("v~#.#", "v1.5")); + + // optional with escape + assert(wildcard_match("~\\*", "")); + assert(wildcard_match("~\\*", "*")); + assert(!wildcard_match("~\\*", "x")); + assert(wildcard_match("a~\\*b", "ab")); + assert(wildcard_match("a~\\*b", "a*b")); + assert(!wildcard_match("a~\\*b", "axb")); + + // double optional ~~ + assert(wildcard_match("~~a", "")); + assert(wildcard_match("~~a", "a")); + assert(!wildcard_match("~~a", "aa")); + + // degenerates + assert(wildcard_match("a\\", "a")); + assert(!wildcard_match("a\\", "")); + assert(!wildcard_match("a\\", "x")); + assert(wildcard_match("a~", "a")); + assert(!wildcard_match("a~", "")); + assert(!wildcard_match("a~", "x")); + assert(wildcard_match("a~\\", "a")); + assert(!wildcard_match("a~\\", "")); + assert(!wildcard_match("a~\\", "x")); + + // optional with value_wildcard - basic + assert(wildcard_match("~b", "", true)); + assert(wildcard_match("~b", "b", true)); + assert(wildcard_match("", "~b", true)); + assert(wildcard_match("b", "~b", true)); + assert(wildcard_match("ab", "~ab", true)); + assert(wildcard_match("ab", "~a~b", true)); + assert(wildcard_match("~ab", "ab", true)); + assert(wildcard_match("~ab", "~ab", true)); + assert(wildcard_match("~ab", "~a~b", true)); + assert(wildcard_match("a~b", "~ab", true)); + assert(wildcard_match("a~b", "~a~b", true)); + assert(wildcard_match("~a~b", "~ab", true)); + assert(wildcard_match("~a~b", "~a~b", true)); + + // optional with value_wildcard - with wildcards on rhs + assert(wildcard_match("~a", "*", true)); + assert(wildcard_match("~ab", "*", true)); + assert(wildcard_match("~a~b", "*", true)); + assert(wildcard_match("*", "~a", true)); + assert(wildcard_match("*", "~ab", true)); + assert(wildcard_match("*", "~a~b", true)); + assert(wildcard_match("~a", "?", true)); + assert(wildcard_match("?", "~a", true)); + assert(wildcard_match("~ab", "?", true)); + assert(wildcard_match("~ab", "?b", true)); + assert(wildcard_match("~ab", "a?", true)); + assert(wildcard_match("~ab", "??", true)); + assert(wildcard_match("?", "~ab", true)); + assert(wildcard_match("?b", "~ab", true)); + assert(wildcard_match("a?", "~ab", true)); + assert(wildcard_match("??", "~ab", true)); + assert(wildcard_match("~ab", "?b", true)); + assert(wildcard_match("~abc", "?c", true)); + assert(wildcard_match("~a*", "*", true)); + assert(wildcard_match("*~a", "*", true)); + assert(wildcard_match("*", "~a*", true)); + assert(wildcard_match("*", "*~a", true)); + assert(wildcard_match("ab", "*~c", true)); + assert(wildcard_match("abc", "a?~c", true)); + + // optional on both sides with wildcards + assert(wildcard_match("~a*", "*~b", true)); + assert(wildcard_match("a~b*", "*~cd", true)); + assert(wildcard_match("~ab", "*b", true)); + assert(wildcard_match("~ab", "*ab", true)); + assert(wildcard_match("x~ab", "*ab", true)); + assert(wildcard_match("*b", "~ab", true)); + assert(wildcard_match("*ab", "~ab", true)); + assert(wildcard_match("*ab", "x~ab", true)); + assert(wildcard_match("~a~b", "~c~d", true)); + assert(wildcard_match("~a~b", "~c~da", true)); + assert(wildcard_match("~a~b", "~c~db", true)); + assert(wildcard_match("~a~b", "~c~dab", true)); + assert(wildcard_match("~a~bc", "~c~d", true)); + assert(wildcard_match("~a~bd", "~c~d", true)); + assert(wildcard_match("~a~bcd", "~c~d", true)); + assert(wildcard_match("*a~bc*d", "xxacxxd", true)); + assert(!wildcard_match("*a~bc*de", "xxabcxxd", true)); + assert(!wildcard_match("*a~bc*d", "xxabcxxde", true)); + assert(wildcard_match("*a~bc*d", "~x~x~a~c~x~x~d", true)); + + // case insensitive with optional + assert(wildcard_match_i("~a", "A")); + assert(wildcard_match_i("~aB", "Ab")); + assert(wildcard_match_i("A~b", "aB")); + + // multiple wildcards + assert(wildcard_match("*l*o", "hello")); + assert(wildcard_match("h*l*o", "hello")); + assert(wildcard_match("h*l*", "hello")); + assert(wildcard_match("*e*l*", "hello")); + assert(wildcard_match("*h*e*l*l*o*", "hello")); + + // wildcards with sequences in between + assert(wildcard_match("h*ll*", "hello")); + assert(wildcard_match("*el*", "hello")); + assert(wildcard_match("h*el*o", "hello")); + assert(!wildcard_match("h*el*x", "hello")); + assert(wildcard_match("*lo", "hello")); + assert(!wildcard_match("*lx", "hello")); + + // mixed wildcards and single matches + assert(wildcard_match("h?*o", "hello")); + assert(wildcard_match("h*?o", "hello")); + assert(wildcard_match("?e*o", "hello")); + assert(wildcard_match("h?ll?", "hello")); + assert(!wildcard_match("h?ll?", "hllo")); + + // overlapping wildcards + assert(wildcard_match("**hello", "hello")); + assert(wildcard_match("hello**", "hello")); + assert(wildcard_match("h**o", "hello")); + assert(wildcard_match("*?*", "hello")); + assert(wildcard_match("?*?", "hello")); + assert(!wildcard_match("?*?", "x")); + assert(wildcard_match("?*?", "xx")); + + // escape sequences + assert(wildcard_match("\\*", "*")); + assert(wildcard_match("\\?", "?")); + assert(wildcard_match("\\#", "#")); + assert(wildcard_match("\\\\", "\\")); + assert(!wildcard_match("\\*", "a")); + assert(!wildcard_match("\\?", "a")); + assert(!wildcard_match("\\#", "5")); + assert(wildcard_match("h\\*o", "h*o")); + assert(!wildcard_match("h\\*o", "hello")); + assert(wildcard_match("\\*\\?\\\\", "*?\\")); + assert(wildcard_match("a\\*b*c", "a*bxyzc")); + assert(wildcard_match("*\\**", "hello*world")); + + // edge cases + assert(wildcard_match("", "")); + assert(!wildcard_match("", "a")); + assert(wildcard_match("*", "")); + assert(wildcard_match("**", "")); + assert(!wildcard_match("?", "")); + assert(wildcard_match("a*b*c", "abc")); + assert(wildcard_match("a*b*c", "aXbYc")); + assert(wildcard_match("a*b*c", "aXXbYYc")); + + // value_wildcard tests - bidirectional matching + assert(wildcard_match("hello", "h*o", true)); + assert(wildcard_match("h*o", "hello", true)); + assert(wildcard_match("h?llo", "he?lo", true)); + assert(wildcard_match("h\\*o", "h\\*o", true)); + assert(wildcard_match("test*", "*test", true)); + + // both sides have wildcards + assert(wildcard_match("h*o", "h*o", true)); + assert(wildcard_match("*hello*", "*world*", true)); + assert(wildcard_match("a*b*c", "a*b*c", true)); + assert(wildcard_match("*", "*", true)); + assert(wildcard_match("?", "?", true)); + + // complex interplay - wildcards matching wildcards + assert(wildcard_match("a*c", "a?c", true)); + assert(wildcard_match("a?c", "a*c", true)); + assert(wildcard_match("*abc", "?abc", true)); + assert(wildcard_match("abc*", "abc?", true)); + + // multiple wildcards on both sides + assert(wildcard_match("a*b*c", "a?b?c", true)); + assert(wildcard_match("*a*b*", "?a?b?", true)); + assert(wildcard_match("a**b", "a*b", true)); + assert(wildcard_match("a*b", "a**b", true)); + + // wildcards at different positions + assert(wildcard_match("*test", "test*", true)); + assert(wildcard_match("test*end", "*end", true)); + assert(wildcard_match("*middle*", "start*", true)); + + // edge cases with value_wildcard + assert(wildcard_match("", "", true)); + assert(wildcard_match("*", "", true)); + assert(wildcard_match("", "*", true)); + assert(wildcard_match("**", "*", true)); + assert(wildcard_match("*", "**", true)); + + // digit wildcard tests + assert(wildcard_match("#", "0")); + assert(wildcard_match("#", "5")); + assert(wildcard_match("#", "9")); + assert(!wildcard_match("#", "a")); + assert(!wildcard_match("#", "A")); + assert(!wildcard_match("#", "")); + assert(!wildcard_match("#", "12")); + assert(!wildcard_match("#", "#")); + assert(wildcard_match("#", "#", true)); + assert(!wildcard_match("#", "\\#", true)); + assert(wildcard_match("v#.#", "v1.2")); + assert(!wildcard_match("v#.#", "va.b")); + assert(!wildcard_match("v#.#", "v1.")); + assert(wildcard_match("port-##", "port-42")); + assert(wildcard_match("#*#", "123")); + assert(!wildcard_match("#*#", "abc")); } diff --git a/src/urt/string/string.d b/src/urt/string/string.d index b1281cd..ccb06f9 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -3,7 +3,7 @@ module urt.string.string; import urt.lifetime : forward, move; import urt.mem; import urt.mem.string : CacheString; -import urt.hash : fnv1aHash, fnv1aHash64; +import urt.hash : fnv1a, fnv1a64; import urt.string.tailstring : TailString; public import urt.array : Alloc_T, Alloc, Reserve_T, Reserve, Concat_T, Concat; @@ -23,8 +23,7 @@ enum StringAlloc : ubyte TempString, // allocates in the temp ring buffer; could be overwritten at any time! // these must be last... (because comparison logic) - StringCache, // writes to the immutable string cache - StringCacheDedup, // writes to the immutable string cache with de-duplication + StringCache, // writes to the immutable string cache with de-duplication } struct StringAllocator @@ -33,6 +32,61 @@ struct StringAllocator void delegate(char* s) nothrow @nogc free; } +struct StringCacheBuilder +{ +nothrow @nogc: + this(char[] buffer) pure + { + assert(buffer.length >= 2 && buffer.length <= ushort.max, "Invalid buffer length"); + buffer[0..2] = 0; + this._buffer = buffer; + this._offset = 2; + } + + ushort add_string(const(char)[] s) pure + { + if (s.length == 0) + return 0; + + assert(s.length <= MaxStringLen, "String too long"); + assert(_offset + s.length + 2 + (s.length & 1) <= _buffer.length, "Not enough space in buffer"); + if (__ctfe) + { + version (LittleEndian) + { + _buffer[_offset + 0] = cast(char)(s.length & 0xFF); + _buffer[_offset + 1] = cast(char)(s.length >> 8); + } + else + { + _buffer[_offset + 0] = cast(char)(s.length >> 8); + _buffer[_offset + 1] = cast(char)(s.length & 0xFF); + } + } + else + *cast(ushort*)(_buffer.ptr + _offset) = cast(ushort)s.length; + + ushort result = cast(ushort)(_offset + 2); + _buffer[result .. result + s.length] = s[]; + _offset = cast(ushort)(result + s.length); + if (_offset & 1) + _buffer[_offset++] = '\0'; + return result; + } + + size_t used() const pure + => _offset; + + size_t remaining() const pure + => _buffer.length - _offset; + + bool full() const pure + => _offset == _buffer.length; + +private: + char[] _buffer; + ushort _offset; +} //enum String StringLit(string s) = s.makeString; template StringLit(const(char)[] lit, bool zeroTerminate = true) @@ -58,12 +112,11 @@ template StringLit(const(char)[] lit, bool zeroTerminate = true) return buffer; }(); pragma(aligned, 2) - private __gshared literal = LiteralData; + private __gshared immutable literal = LiteralData; - enum String StringLit = String(literal.ptr + 2, false); + enum StringLit = immutable(String)(literal.ptr + 2, false); } - String makeString(const(char)[] s) nothrow { if (s.length == 0) @@ -87,11 +140,11 @@ String makeString(const(char)[] s, StringAlloc allocator, void* userData = null) { return String(writeString(cast(char*)tempAllocator().alloc(2 + s.length, 2).ptr + 2, s), false); } - else if (allocator >= StringAlloc.StringCache) + else if (allocator == StringAlloc.StringCache) { import urt.mem.string : CacheString, addString; - CacheString cs = s.addString(allocator == StringAlloc.StringCacheDedup); + CacheString cs = s.addString(); return String(cs.ptr, false); } assert(false, "Invalid string allocator"); @@ -118,6 +171,46 @@ String makeString(const(char)[] s, char[] buffer) nothrow @nogc return String(writeString(buffer.ptr + 2, s), false); } +char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc +{ + // TODO: assume the calling code has confirmed the length is within spec + if (__ctfe) + { + version (LittleEndian) + { + buffer[-2] = cast(char)(str.length & 0xFF); + buffer[-1] = cast(char)(str.length >> 8); + } + else + { + buffer[-2] = cast(char)(str.length >> 8); + buffer[-1] = cast(char)(str.length & 0xFF); + } + } + else + (cast(ushort*)buffer)[-1] = cast(ushort)str.length; + buffer[0 .. str.length] = str[]; + return buffer; +} + +String as_string(const(char)* s) nothrow @nogc + => String(s, false); + +inout(char)[] as_dstring(inout(char)* s) pure nothrow @nogc +{ + debug assert(s !is null); + + if (__ctfe) + { + version (LittleEndian) + ushort len = cast(ushort)(s[-2] | (s[-1] << 8)); + else + ushort len = cast(ushort)(s[-1] | (s[-2] << 8)); + return s[0 .. len]; + } + else + return s[0 .. (cast(ushort*)s)[-1]]; +} struct String { @@ -188,7 +281,17 @@ nothrow @nogc: // TODO: I made this return ushort, but normally length() returns size_t ushort length() const pure - => ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + { + if (__ctfe) + { + version (LittleEndian) + return ptr ? cast(ushort)(ptr[-2] | (ptr[-1] << 8)) & 0x7FFF : 0; + else + return ptr ? cast(ushort)((ptr[-1] | (ptr[-2] << 8)) & 0x7FFF) : 0; + } + else + return ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + } bool opCast(T : bool)() const pure => ptr != null && ((cast(ushort*)ptr)[-1] & 0x7FFF) != 0; @@ -240,9 +343,9 @@ nothrow @nogc: if (!ptr) return 0; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])ptr[0 .. length]); + return fnv1a(cast(ubyte[])ptr[0 .. length]); else - return fnv1aHash64(cast(ubyte[])ptr[0 .. length]); + return fnv1a64(cast(ubyte[])ptr[0 .. length]); } const(char)[] opIndex() const pure @@ -470,9 +573,9 @@ nothrow @nogc: append(forward!things); } - this(Args...)(Format_T, auto ref Args args) + this(Args...)(Format_T, const(char)[] format, auto ref Args args) { - format(forward!args); + this.format(format, forward!args); } ~this() @@ -601,9 +704,9 @@ nothrow @nogc: return this; } - ref MutableString!Embed appendFormat(Things...)(auto ref Things things) + ref MutableString!Embed append_format(Things...)(const(char)[] format, auto ref Things args) { - insertFormat(length(), forward!things); + insert_format(length(), format, forward!args); return this; } @@ -615,18 +718,18 @@ nothrow @nogc: return this; } - ref MutableString!Embed format(Args...)(auto ref Args args) + ref MutableString!Embed format(Args...)(const(char)[] format, auto ref Args args) { if (ptr) writeLength(0); - insertFormat(0, forward!args); + insert_format(0, format, forward!args); return this; } ref MutableString!Embed insert(Things...)(size_t offset, auto ref Things things) { import urt.string.format : _concat = concat; - import urt.util : max, nextPowerOf2; + import urt.util : max, next_power_of_2; char* oldPtr = ptr; size_t oldLen = length(); @@ -638,7 +741,7 @@ nothrow @nogc: debug assert(newLen <= MaxStringLen, "String too long"); size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).nextPowerOf2 - 4); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); _concat(ptr[offset .. offset + insertLen], forward!things); writeLength(newLen); @@ -651,24 +754,24 @@ nothrow @nogc: return this; } - ref MutableString!Embed insertFormat(Things...)(size_t offset, auto ref Things things) + ref MutableString!Embed insert_format(Things...)(size_t offset, const(char)[] format, auto ref Things args) { import urt.string.format : _format = format; - import urt.util : max, nextPowerOf2; + import urt.util : max, next_power_of_2; char* oldPtr = ptr; size_t oldLen = length(); - size_t insertLen = _format(null, things).length; + size_t insertLen = _format(null, format, args).length; size_t newLen = oldLen + insertLen; if (newLen == oldLen) return this; debug assert(newLen <= MaxStringLen, "String too long"); size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).nextPowerOf2 - 4); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); - _format(ptr[offset .. offset + insertLen], forward!things); + _format(ptr[offset .. offset + insertLen], format, forward!args); writeLength(newLen); if (oldPtr && ptr != oldPtr) @@ -824,11 +927,11 @@ unittest m.append(" Text"); assert(m == "X! More Text"); - // appendFormat + // append_format m.clear(); - m.appendFormat("Value: {0}", 123); + m.append_format("Value: {0}", 123); assert(m == "Value: 123"); - m.appendFormat(", String: {0}", "abc"); + m.append_format(", String: {0}", "abc"); assert(m == "Value: 123, String: abc"); // concat @@ -849,13 +952,13 @@ unittest m.insert(m.length, "!"); // End (same as append) assert(m == "My Super String!"); - // insertFormat + // insert_format m = "Data"; - m.insertFormat(0, "[{0}] ", 1); + m.insert_format(0, "[{0}] ", 1); assert(m == "[1] Data"); - m.insertFormat(4, "\\{{0}\\}", "fmt"); + m.insert_format(4, "\\{{0}\\}", "fmt"); assert(m == "[1] {fmt}Data"); - m.insertFormat(m.length, " End"); + m.insert_format(m.length, " End"); assert(m == "[1] {fmt}Data End"); // erase @@ -933,14 +1036,6 @@ private: __gshared StringAllocator[4] stringAllocators; static assert(stringAllocators.length <= 4, "Only 2 bits reserved to store allocator index"); -char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc -{ - // TODO: assume the calling code has confirmed the length is within spec - (cast(ushort*)buffer)[-1] = cast(ushort)str.length; - buffer[0 .. str.length] = str[]; - return buffer; -} - package(urt) void initStringAllocators() { stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) { diff --git a/src/urt/string/tailstring.d b/src/urt/string/tailstring.d index 3664342..0c7b77d 100644 --- a/src/urt/string/tailstring.d +++ b/src/urt/string/tailstring.d @@ -23,7 +23,7 @@ struct TailString(T) else { const(char)* thisptr = cast(const(char)*)&this; - const(char)* sptr = s.ptr - (s.ptr[-1] < 128 ? 1 : 2); + const(char)* sptr = s.ptr - 2; assert(sptr > thisptr && sptr - thisptr <= offset.max, "!!"); offset = cast(ubyte)(sptr - thisptr); } @@ -39,7 +39,7 @@ struct TailString(T) if (offset == 0) return null; const(char)* ptr = (cast(const(char)*)&this) + offset; - return ptr[0] < 128 ? ptr + 1 : ptr + 2; + return ptr + 2; } size_t length() const nothrow @nogc @@ -64,7 +64,7 @@ struct TailString(T) else { const(char)* thisptr = cast(const(char)*)&this; - const(char)* sptr = s.ptr - (s.ptr[-1] < 128 ? 1 : 2); + const(char)* sptr = s.ptr - 2; assert(sptr > thisptr && sptr - thisptr <= offset.max, "!!"); offset = cast(ubyte)(sptr - thisptr); } @@ -81,9 +81,9 @@ struct TailString(T) import urt.hash; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])toString()); + return fnv1a(cast(ubyte[])toString()); else - return fnv1aHash64(cast(ubyte[])toString()); + return fnv1a64(cast(ubyte[])toString()); } private: diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index ee9b551..8447be9 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -1,18 +1,155 @@ module urt.string.uni; -nothrow @nogc: +import urt.string.ascii : to_lower, to_upper; +import urt.traits : is_some_char; +pure nothrow @nogc: -size_t uniConvert(const(char)[] s, wchar[] buffer) + +size_t uni_seq_len(const(char)[] str) +{ + debug assert(str.length > 0); + + const(char)* s = str.ptr; + if (s[0] < 0x80) // 1-byte sequence: 0xxxxxxx + return 1; + else if ((s[0] & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + return (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + return (str.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return (str.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : + (str.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return 1; // Invalid UTF-8 sequence +} + +size_t uni_seq_len(const(wchar)[] str) +{ + debug assert(str.length > 0); + + const(wchar)* s = str.ptr; + if (s[0] >= 0xD800 && s[0] < 0xDC00 && str.length >= 2 && s[1] >= 0xDC00 && s[1] < 0xE000) + return 2; // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + return 1; +} + +pragma(inline, true) +size_t uni_seq_len(const(dchar)[] s) + => 1; + +size_t uni_strlen(C)(const(C)[] s) + if (is_some_char!C) +{ + static if (is(C == dchar)) + { + pragma(inline, true); + return s.length; + } + else + { + size_t count = 0; + while (s.length) + { + size_t l = s.uni_seq_len; + s = s[l .. $]; + ++count; + } + return count; + } +} + +dchar next_dchar(const(char)[] s, out size_t seq_len) +{ + assert(s.length > 0); + + const(char)* p = s.ptr; + if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx + { + seq_len = 1; + return *p; + } + else if ((*p & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + { + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + seq_len = 2; + return ((p[0] & 0x1F) << 6) | (p[1] & 0x3F); + } + } + else if ((*p & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + { + if (s.length >= 3 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) + { + seq_len = 3; + return ((p[0] & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F); + } + // check for seq_len == 2 error cases + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + seq_len = 2; + return 0xFFFD; + } + } + else if ((*p & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + { + if (s.length >= 4 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80 && (p[3] & 0xC0) == 0x80) + { + seq_len = 4; + return ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); + } + // check for seq_len == 2..3 error cases + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + if (s.length == 2 || (p[2] & 0xC0) != 0x80) + seq_len = 2; + else + seq_len = 3; + return 0xFFFD; + } + } + seq_len = 1; + return 0xFFFD; // Invalid UTF-8 sequence +} + +dchar next_dchar(const(wchar)[] s, out size_t seq_len) +{ + assert(s.length > 0); + + const(wchar)* p = s.ptr; + if (p[0] < 0xD800 || p[0] >= 0xE000) + { + seq_len = 1; + return p[0]; + } + if (p[0] < 0xDC00 && s.length >= 2 && p[1] >= 0xDC00 && p[1] < 0xE000) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + { + seq_len = 2; + return 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); + } + seq_len = 1; + return 0xFFFD; // Invalid UTF-16 sequence +} + +pragma(inline, true) +dchar next_dchar(const(dchar)[] s, out size_t seq_len) +{ + assert(s.length > 0); + seq_len = 1; + return s[0]; +} + +size_t uni_convert(const(char)[] s, wchar[] buffer) { const(char)* p = s.ptr; const(char)* pend = p + s.length; wchar* b = buffer.ptr; - wchar* bEnd = buffer.ptr + buffer.length; + wchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx *b++ = *p++; @@ -32,7 +169,7 @@ size_t uniConvert(const(char)[] s, wchar[] buffer) } else if ((*p & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { - if (p + 3 >= pend || b + 1 >= bEnd) + if (p + 3 >= pend || b + 1 >= bend) return 0; // Unexpected end of input/output dchar codepoint = ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); codepoint -= 0x10000; @@ -47,16 +184,16 @@ size_t uniConvert(const(char)[] s, wchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(char)[] s, dchar[] buffer) +size_t uni_convert(const(char)[] s, dchar[] buffer) { const(char)* p = s.ptr; const(char)* pend = p + s.length; dchar* b = buffer.ptr; - dchar* bEnd = buffer.ptr + buffer.length; + dchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx *b++ = *p++; @@ -84,16 +221,16 @@ size_t uniConvert(const(char)[] s, dchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(wchar)[] s, char[] buffer) +size_t uni_convert(const(wchar)[] s, char[] buffer) { const(wchar)* p = s.ptr; const(wchar)* pend = p + s.length; char* b = buffer.ptr; - char* bEnd = buffer.ptr + buffer.length; + char* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (p[0] >= 0xD800) { @@ -103,7 +240,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) return 0; // Unexpected end of input if (p[0] < 0xDC00) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx { - if (b + 3 >= bEnd) + if (b + 3 >= bend) return 0; // End of output buffer dchar codepoint = 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); b[0] = 0xF0 | (codepoint >> 18); @@ -120,7 +257,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) *b++ = cast(char)*p++; else if (*p < 0x800) // 2-byte sequence: 110xxxxx 10xxxxxx { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer b[0] = 0xC0 | cast(char)(*p >> 6); b[1] = 0x80 | (*p++ & 0x3F); @@ -129,7 +266,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) else // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx { three_byte_seq: - if (b + 2 >= bEnd) + if (b + 2 >= bend) return 0; // End of output buffer b[0] = 0xE0 | (*p >> 12); b[1] = 0x80 | ((*p >> 6) & 0x3F); @@ -140,16 +277,16 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(wchar)[] s, dchar[] buffer) +size_t uni_convert(const(wchar)[] s, dchar[] buffer) { const(wchar)* p = s.ptr; const(wchar)* pend = p + s.length; dchar* b = buffer.ptr; - dchar* bEnd = buffer.ptr + buffer.length; + dchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (p[0] >= 0xD800 && p[0] < 0xE000) { @@ -168,22 +305,22 @@ size_t uniConvert(const(wchar)[] s, dchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(dchar)[] s, char[] buffer) +size_t uni_convert(const(dchar)[] s, char[] buffer) { const(dchar)* p = s.ptr; const(dchar)* pend = p + s.length; char* b = buffer.ptr; - char* bEnd = buffer.ptr + buffer.length; + char* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (*p < 0x80) // 1-byte sequence: 0xxxxxxx *b++ = cast(char)*p++; else if (*p < 0x800) // 2-byte sequence: 110xxxxx 10xxxxxx { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer b[0] = 0xC0 | cast(char)(*p >> 6); b[1] = 0x80 | (*p++ & 0x3F); @@ -191,7 +328,7 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) } else if (*p < 0x10000) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx { - if (b + 2 >= bEnd) + if (b + 2 >= bend) return 0; // End of output buffer b[0] = 0xE0 | cast(char)(*p >> 12); b[1] = 0x80 | ((*p >> 6) & 0x3F); @@ -200,7 +337,7 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) } else if (*p < 0x110000) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { - if (b + 3 >= bEnd) + if (b + 3 >= bend) return 0; // End of output buffer b[0] = 0xF0 | (*p >> 18); b[1] = 0x80 | ((*p >> 12) & 0x3F); @@ -214,22 +351,22 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(dchar)[] s, wchar[] buffer) +size_t uni_convert(const(dchar)[] s, wchar[] buffer) { const(dchar)* p = s.ptr; const(dchar)* pend = p + s.length; wchar* b = buffer.ptr; - wchar* bEnd = buffer.ptr + buffer.length; + wchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (*p < 0x10000) *b++ = cast(wchar)*p++; else if (*p < 0x110000) { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer dchar codepoint = *p++ - 0x10000; b[0] = 0xD800 | (codepoint >> 10); @@ -242,10 +379,507 @@ size_t uniConvert(const(dchar)[] s, wchar[] buffer) return b - buffer.ptr; } +char uni_to_lower(char c) + => c.to_lower(); + +dchar uni_to_lower(dchar c) +{ + if (uint(c - 'A') < 26) + return c | 0x20; + + // TODO: this is a deep rabbit-hole! (and the approach might not be perfect) + + if (c < 0xFF) + { + if (c >= 0xC0) // Latin-1 Supplement + return g_to_lower_latin_1.ptr[c - 0xC0]; + } + else if (c <= 0x556) + { + if (c >= 0x370) + { + if (c < 0x400) // Greek and Coptic + return 0x300 | g_to_lower_greek.ptr[c - 0x370]; + else if (c < 0x460) // Cyrillic + { + if (c < 0x410) + return c + 0x50; + else if (c < 0x430) + return c + 0x20; + } + else if (c < 0x530) // Cyrillic Supplement + { + if (c >= 0x482 && c < 0x48A) // exceptions + return c; + return c | 1; + } + else if (c >= 0x531) // Armenian + return c + 0x30; + } + else if (c < 0x180) // Latin Extended-A + return (0x100 | g_to_lower_latin_extended_a.ptr[c - 0xFF]) - 1; + } + else if (c <= 0x1CB0) // Georgian + { + if (c >= 0x1C90) + return c - 0x0BC0; // Mtavruli -> Mkhedruli + else if (c >= 0x10A0 && c <= 0x10C5) + return c + 0x1C60; // Asomtavruli -> Nuskhuri + } + else if (c >= 0x1E00) + { + if (c < 0x1F00) // Latin Extended Additional + { + if (c >= 0x1E96 && c < 0x1EA0) // exceptions + { + if (c == 0x1E9E) // 'ẞ' -> 'ß' + return 0xDF; + return c; + } + return c | 1; + } + else if (c <= 0x1FFC) // Greek Extended + { + if (c < 0x1F70) + return c & ~0x8; + return 0x1F00 | g_to_lower_greek_extended.ptr[c - 0x1F70]; + } + else if (c < 0x2CE4) + { + if (c >= 0x2C80) // Coptic + return c | 1; + } + } + return c; +} + +char uni_to_upper(char c) + => c.to_upper(); + +dchar uni_to_upper(dchar c) +{ + if (uint(c - 'a') < 26) + return c ^ 0x20; + + // TODO: this is a deep rabbit-hole! (and the approach might not be perfect) + + if (c < 0xFF) + { + if (c == 0xDF) // 'ß' -> 'ẞ' + return 0x1E9E; + if (c >= 0xC0) // Latin-1 Supplement + return g_to_upper_latin_1.ptr[c - 0xC0]; + } + else if (c <= 0x586) + { + if (c >= 0x370) + { + if (c < 0x400) // Greek and Coptic + return 0x300 | g_to_upper_greek.ptr[c - 0x370]; + else if (c < 0x460) // Cyrillic + { + if (c >= 0x450) + return c - 0x50; + else if (c >= 0x430) + return c - 0x20; + } + else if (c < 0x530) // Cyrillic Supplement + { + if (c >= 0x482 && c < 0x48A) // exceptions + return c; + return c & ~1; + } + else if (c >= 0x561) // Armenian + return c - 0x30; + } + else if (c < 0x180) // Latin Extended-A + return 0x100 | g_to_upper_latin_extended_a.ptr[c - 0xFF]; + } + else if (c <= 0x10F0) // Georgian + { + if (c >= 0x10D0) + return c + 0x0BC0; // Mkhedruli -> Mtavruli + } + else if (c >= 0x1E00) + { + if (c < 0x1F00) // Latin Extended Additional + { + if (c >= 0x1E96 && c < 0x1EA0) // exceptions + return c; + return c & ~1; + } + else if (c <= 0x1FFC) // Greek Extended + { + if (c < 0x1F70) + return c | 0x8; + return 0x1F00 | g_to_upper_greek_extended.ptr[c - 0x1F70]; + } + else if (c < 0x2CE4) + { + if (c >= 0x2C80) // Coptic + return c & ~1; + } + else if (c <= 0x2D25) // Georgian + { + if(c >= 0x2D00) + return c - 0x1C60; // Nuskhuri -> Asomtavruli + } + } + return c; +} + +char uni_case_fold(char c) + => c.to_lower(); + +dchar uni_case_fold(dchar c) +{ + // case-folding is stronger than to_lower, there may be many misc cases... + if (c >= 0x3C2) // Greek has bonus case-folding... + { + if (c < 0x3FA) + return 0x300 | g_case_fold_greek.ptr[c - 0x3C2]; + } + else if (c == 'ſ') // TODO: pointless? it's in the spec... + return 's'; + return uni_to_lower(c); +} + +int uni_compare(T, U)(const(T)[] s1, const(U)[] s2) + if (is_some_char!T && is_some_char!U) +{ + const(T)* p1 = s1.ptr; + const(T)* p1end = p1 + s1.length; + const(U)* p2 = s2.ptr; + const(U)* p2end = p2 + s2.length; + + // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) + + while (true) + { + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + if (p1 >= p1end) + return p2 < p2end ? int.min : 0; + if (p2 >= p2end) + return int.max; + + dchar a = *p1, b = *p2; + + if (a < 0x80) + { + if (a != b) + return int(a) - int(b); + ++p1; + p2 += b < 0x80 ? 1 : p2[0 .. p2end - p2].uni_seq_len; + } + else if (b < 0x80) + { + if (a != b) + return int(a) - int(b); + p1 += p1[0 .. p1end - p1].uni_seq_len; + ++p2; + } + else + { + size_t al, bl; + a = next_dchar(p1[0 .. p1end - p1], al); + b = next_dchar(p2[0 .. p2end - p2], bl); + if (a != b) + return cast(int)a - cast(int)b; + p1 += al; + p2 += bl; + } + } +} + +int uni_compare_i(T, U)(const(T)[] s1, const(U)[] s2) + if (is_some_char!T && is_some_char!U) +{ + const(T)* p1 = s1.ptr; + const(T)* p1end = p1 + s1.length; + const(U)* p2 = s2.ptr; + const(U)* p2end = p2 + s2.length; + + // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) + // that said, it's also overkill for embedded use! + + size_t al, bl; + while (p1 < p1end && p2 < p2end) + { + dchar a = *p1; + dchar b = void; + if (a < 0x80) + { + // ascii fast-path + a = (cast(char)a).to_lower; + b = *p2; + if (uint(b - 'A') < 26) + b |= 0x20; + if (a != b) + { + if (b >= 0x80) + { + // `b` is not ascii; break-out to the slow path... + al = 1; + goto uni_compare_load_b; + } + return cast(int)a - cast(int)b; + } + ++p1; + ++p2; + } + else + { + a = next_dchar(p1[0 .. p1end - p1], al).uni_case_fold; + uni_compare_load_b: + b = next_dchar(p2[0 .. p2end - p2], bl).uni_case_fold; + uni_compare_a_b: + if (a != b) + { + // it is _SO UNFORTUNATE_ that the ONLY special-case letter in all of unicode is german 'ß' (0xDF)!! + if (b == 0xDF) + { + if (a != 's') + return cast(int)a - cast(int)'s'; + if (++p1 == p1end) + return -1; // only one 's', so the a-side is a shorter string + a = next_dchar(p1[0 .. p1end - p1], al).uni_case_fold; + b = 's'; + p2 += bl - 1; + bl = 1; + goto uni_compare_a_b; + } + else if (a == 0xDF) + { + if (b != 's') + return cast(int)'s' - cast(int)b; + if (++p2 == p2end) + return 1; // only one 's', so the b-side is a shorter string + a = 's'; + p1 += al - 1; + al = 1; + goto uni_compare_load_b; + } + return cast(int)a - cast(int)b; + } + p1 += al; + p2 += bl; + } + } + + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + return (p1 < p1end) ? int.max : (p2 < p2end) ? int.min : 0; +} + + +private: + +// this is a helper to crush character maps into single byte arrays... +ubyte[N] map_chars(size_t N)(ubyte function(wchar) pure nothrow @nogc translate, wchar[N] map) +{ + if (__ctfe) + { + ubyte[N] result; + foreach (i; 0 .. N) + result[i] = translate(map[i]); + return result; + } + else + assert(false, "Not for runtime!"); +} + +// lookup tables for case conversion +__gshared immutable g_to_lower_latin_1 = map_chars(c => cast(ubyte)c, to_lower_latin[0 .. 0x3F]); +__gshared immutable g_to_upper_latin_1 = map_chars(c => cast(ubyte)c, to_upper_latin[0 .. 0x3F]); +__gshared immutable g_to_lower_latin_extended_a = map_chars(c => cast(ubyte)(c + 1), to_lower_latin[0x3F .. 0xC0]); // calculate `(0x100 | table[n]) - 1` at runtime +__gshared immutable g_to_upper_latin_extended_a = map_chars(c => cast(ubyte)c, to_upper_latin[0x3F .. 0xC0]); // calculate `0x100 | table[n]` at runtime +__gshared immutable g_to_lower_greek = map_chars(c => cast(ubyte)c, to_lower_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_to_upper_greek = map_chars(c => cast(ubyte)c, to_upper_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_case_fold_greek = map_chars(c => cast(ubyte)c, case_fold_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_to_lower_greek_extended = map_chars(c => cast(ubyte)c, to_lower_greek_extended); // calculate `0x1F00 | table[n]` at runtime +__gshared immutable g_to_upper_greek_extended = map_chars(c => cast(ubyte)c, to_upper_greek_extended); // calculate `0x1F00 | table[n]` at runtime + +// Latin-1 Supplement and Latin Extended-A +enum wchar[0x180 - 0xC0] to_lower_latin = [ + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0xD7,'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß', + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0xF7,'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', + 'ā', 'ā', 'ă', 'ă', 'ą', 'ą', 'ć', 'ć', 'ĉ', 'ĉ', 'ċ', 'ċ', 'č', 'č', 'ď', 'ď', + 'đ', 'đ', 'ē', 'ē', 'ĕ', 'ĕ', 'ė', 'ė', 'ę', 'ę', 'ě', 'ě', 'ĝ', 'ĝ', 'ğ', 'ğ', + 'ġ', 'ġ', 'ģ', 'ģ', 'ĥ', 'ĥ', 'ħ', 'ħ', 'ĩ', 'ĩ', 'ī', 'ī', 'ĭ', 'ĭ', 'į', 'į', + 0x130,0x131,'ij', 'ij', 'ĵ', 'ĵ', 'ķ', 'ķ',0x138,'ĺ', 'ĺ', 'ļ', 'ļ', 'ľ', 'ľ', 'ŀ', + 'ŀ', 'ł', 'ł', 'ń', 'ń', 'ņ', 'ņ', 'ň', 'ň',0x149,'ŋ', 'ŋ', 'ō', 'ō', 'ŏ', 'ŏ', + 'ő', 'ő', 'œ', 'œ', 'ŕ', 'ŕ', 'ŗ', 'ŗ', 'ř', 'ř', 'ś', 'ś', 'ŝ', 'ŝ', 'ş', 'ş', + 'š', 'š', 'ţ', 'ţ', 'ť', 'ť', 'ŧ', 'ŧ', 'ũ', 'ũ', 'ū', 'ū', 'ŭ', 'ŭ', 'ů', 'ů', + 'ű', 'ű', 'ų', 'ų', 'ŵ', 'ŵ', 'ŷ', 'ŷ', 'ÿ', 'ź', 'ź', 'ż', 'ż', 'ž', 'ž', 'ſ' +]; + +enum wchar[0x180 - 0xC0] to_upper_latin = [ + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0xD7,'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ẞ', + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0xF7,'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'Ÿ', + 'Ā', 'Ā', 'Ă', 'Ă', 'Ą', 'Ą', 'Ć', 'Ć', 'Ĉ', 'Ĉ', 'Ċ', 'Ċ', 'Č', 'Č', 'Ď', 'Ď', + 'Đ', 'Đ', 'Ē', 'Ē', 'Ĕ', 'Ĕ', 'Ė', 'Ė', 'Ę', 'Ę', 'Ě', 'Ě', 'Ĝ', 'Ĝ', 'Ğ', 'Ğ', + 'Ġ', 'Ġ', 'Ģ', 'Ģ', 'Ĥ', 'Ĥ', 'Ħ', 'Ħ', 'Ĩ', 'Ĩ', 'Ī', 'Ī', 'Ĭ', 'Ĭ', 'Į', 'Į', + 0x130,0x131,'IJ', 'IJ', 'Ĵ', 'Ĵ', 'Ķ', 'Ķ',0x138,'Ĺ', 'Ĺ', 'Ļ', 'Ļ', 'Ľ', 'Ľ', 'Ŀ', + 'Ŀ', 'Ł', 'Ł', 'Ń', 'Ń', 'Ņ', 'Ņ', 'Ň', 'Ň',0x149,'Ŋ', 'Ŋ', 'Ō', 'Ō', 'Ŏ', 'Ŏ', + 'Ő', 'Ő', 'Œ', 'Œ', 'Ŕ', 'Ŕ', 'Ŗ', 'Ŗ', 'Ř', 'Ř', 'Ś', 'Ś', 'Ŝ', 'Ŝ', 'Ş', 'Ş', + 'Š', 'Š', 'Ţ', 'Ţ', 'Ť', 'Ť', 'Ŧ', 'Ŧ', 'Ũ', 'Ũ', 'Ū', 'Ū', 'Ŭ', 'Ŭ', 'Ů', 'Ů', + 'Ű', 'Ű', 'Ų', 'Ų', 'Ŵ', 'Ŵ', 'Ŷ', 'Ŷ', 'Ÿ', 'Ź', 'Ź', 'Ż', 'Ż', 'Ž', 'Ž', 'S' +]; + +// Greek and Coptic +enum wchar[0x400 - 0x370] to_lower_greek = [ + 'ͱ', 'ͱ', 'ͳ', 'ͳ',0x374,0x375,'ͷ','ͷ',0x378,0x379,0x37A,'ͻ','ͼ','ͽ',0x37E,'ϳ', +0x380,0x381,0x382,0x383,0x384,0x385,'ά',0x387,'έ','ή','ί',0x38B,'ό',0x38D,'ύ', 'ώ', + 0x390,'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', + 'π', 'ρ',0x3A2,'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ά', 'έ', 'ή', 'ί', + 0x3B0,'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', + 'π', 'ρ', 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ό', 'ύ', 'ώ', 'ϗ', + 'ϐ', 'ϑ', 'υ', 'ύ', 'ϋ', 'ϕ', 'ϖ', 'ϗ', 'ϙ', 'ϙ', 'ϛ', 'ϛ', 'ϝ', 'ϝ', 'ϟ', 'ϟ', + 'ϡ', 'ϡ', 'ϣ', 'ϣ', 'ϥ', 'ϥ', 'ϧ', 'ϧ', 'ϩ', 'ϩ', 'ϫ', 'ϫ', 'ϭ', 'ϭ', 'ϯ', 'ϯ', + 'ϰ', 'ϱ', 'ϲ', 'ϳ', 'θ', 'ϵ',0x3F6,'ϸ', 'ϸ', 'ϲ', 'ϻ', 'ϻ',0x3FC,'ͻ', 'ͼ', 'ͽ' +]; + +enum wchar[0x400 - 0x370] to_upper_greek = [ + 'Ͱ', 'Ͱ', 'Ͳ', 'Ͳ',0x374,0x375,'Ͷ','Ͷ',0x378,0x379,0x37A,'Ͻ','Ͼ','Ͽ',0x37E,'Ϳ', +0x380,0x381,0x382,0x383,0x384,0x385,'Ά',0x387,'Έ','Ή','Ί',0x38B,'Ό',0x38D,'Ύ', 'Ώ', + 0x390,'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', + 'Π', 'Ρ',0x3A2,'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'Ϊ', 'Ϋ', 'Ά', 'Έ', 'Ή', 'Ί', + 0x3B0,'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', + 'Π', 'Ρ', 'Σ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'Ϊ', 'Ϋ', 'Ό', 'Ύ', 'Ώ', 'Ϗ', + 'Β','Θ',0x3D2,0x3D3,0x3D4,'Φ','Π', 'Ϗ', 'Ϙ', 'Ϙ', 'Ϛ', 'Ϛ', 'Ϝ', 'Ϝ', 'Ϟ', 'Ϟ', + 'Ϡ', 'Ϡ', 'Ϣ', 'Ϣ', 'Ϥ', 'Ϥ', 'Ϧ', 'Ϧ', 'Ϩ', 'Ϩ', 'Ϫ', 'Ϫ', 'Ϭ', 'Ϭ', 'Ϯ', 'Ϯ', + 'Κ', 'Ρ', 'Ϲ', 'Ϳ', 'ϴ', 'Ε',0x3F6,'Ϸ', 'Ϸ', 'Ϲ', 'Ϻ', 'Ϻ',0x3FC,'Ͻ', 'Ͼ', 'Ͽ' +]; + +enum wchar[0x3FA - 0x3C2] case_fold_greek = [ + 'σ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ό', 'ύ', 'ώ', 'ϗ', + 'β', 'θ', 'υ', 'ύ', 'ϋ', 'φ', 'π', 'ϗ', 'ϙ', 'ϙ', 'ϛ', 'ϛ', 'ϝ', 'ϝ', 'ϟ', 'ϟ', + 'ϡ', 'ϡ', 'ϣ', 'ϣ', 'ϥ', 'ϥ', 'ϧ', 'ϧ', 'ϩ', 'ϩ', 'ϫ', 'ϫ', 'ϭ', 'ϭ', 'ϯ', 'ϯ', + 'κ', 'ρ', 'σ', 'ϳ', 'θ', 'ε',0x3F6,'ϸ', 'ϸ', 'σ' +]; + +enum wchar[0x1FFD - 0x1F70] to_lower_greek_extended = [ + 'ὰ', 'ά', 'ὲ', 'έ', 'ὴ', 'ή', 'ὶ', 'ί', 'ὸ', 'ό', 'ὺ', 'ύ', 'ὼ', 'ώ', 0x1F7E,0x1F7F, + 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', + 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', + 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', + 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 0x1FB5, 'ᾶ', 'ᾷ', 'ᾰ', 'ᾱ', 'ὰ', 'ά', 'ᾳ', 0x1FBD,0x1FBE,0x1FBF, +0x1FC0,0x1FC1,'ῂ', 'ῃ', 'ῄ', 0x1FC5, 'ῆ', 'ῇ', 'ὲ', 'έ', 'ὴ', 'ή', 'ῃ', 0x1FCD,0x1FCE,0x1FCF, + 'ῐ', 'ῑ', 'ῒ', 'ΐ', 0x1FD4,0x1FD5, 'ῖ', 'ῗ', 'ῐ', 'ῑ', 'ὶ', 'ί',0x1FDC,0x1FDD,0x1FDE,0x1FDF, + 'ῠ', 'ῡ', 'ῢ', 'ΰ', 'ῤ', 'ῥ', 'ῦ', 'ῧ', 'ῠ', 'ῡ', 'ὺ', 'ύ', 'ῥ', 0x1FED,0x1FEE,0x1FEF, +0x1FF0,0x1FF1,'ῲ', 'ῳ', 'ῴ', 0x1FF5, 'ῶ', 'ῷ', 'ὸ', 'ό', 'ὼ', 'ώ', 'ῳ' +]; + +enum wchar[0x1FFD - 0x1F70] to_upper_greek_extended = [ + 'Ὰ', 'Ά', 'Ὲ', 'Έ', 'Ὴ', 'Ή', 'Ὶ', 'Ί', 'Ὸ', 'Ό', 'Ὺ', 'Ύ', 'Ὼ', 'Ώ', 0x1F7E,0x1F7F, + 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', + 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', + 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', + 'Ᾰ', 'Ᾱ', 0x1FB2, 'ᾼ', 0x1FB4,0x1FB5,0x1FB6,0x1FB7, 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 0x1FBD,0x1FBE,0x1FBF, +0x1FC0,0x1FC1,0x1FC2, 'ῌ', 0x1FC4,0x1FC5,0x1FC6,0x1FC7, 'Ὲ', 'Έ', 'Ὴ', 'Ή', 'ῌ', 0x1FCD,0x1FCE,0x1FCF, + 'Ῐ', 'Ῑ', 0x1FD2,0x1FD3,0x1FD4,0x1FD5,0x1FD6,0x1FD7, 'Ῐ', 'Ῑ', 'Ὶ', 'Ί',0x1FDC,0x1FDD,0x1FDE,0x1FDF, + 'Ῠ', 'Ῡ', 0x1FE2,0x1FE3,0x1FE4, 'Ῥ', 0x1FE6,0x1FE7, 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ῥ', 0x1FED,0x1FEE,0x1FEF, +0x1FF0,0x1FF1,0x1FF2, 'ῼ', 0x1FF4,0x1FF5,0x1FF6,0x1FF7, 'Ὸ', 'Ό', 'Ὼ', 'Ώ', 'ῼ' +]; + +// NOTE: Cyrillic is runtime calculable, no tables required! + + unittest { - immutable dstring unicodeTest = + immutable ushort[5] surrogates = [ 0xD800, 0xD800, 0xDC00, 0xD800, 0x0020 ]; + + // test uni_seq_len functions + assert(uni_seq_len("Hello, World!") == 1); + assert(uni_seq_len("ñowai!") == 2); + assert(uni_seq_len("你好") == 3); + assert(uni_seq_len("😊wow!") == 4); + assert(uni_seq_len("\xFFHello") == 1); + assert(uni_seq_len("\xC2") == 1); + assert(uni_seq_len("\xC2Hello") == 1); + assert(uni_seq_len("\xE2") == 1); + assert(uni_seq_len("\xE2Hello") == 1); + assert(uni_seq_len("\xE2\x82") == 2); + assert(uni_seq_len("\xE2\x82Hello") == 2); + assert(uni_seq_len("\xF0") == 1); + assert(uni_seq_len("\xF0Hello") == 1); + assert(uni_seq_len("\xF0\x9F") == 2); + assert(uni_seq_len("\xF0\x9FHello") == 2); + assert(uni_seq_len("\xF0\x9F\x98") == 3); + assert(uni_seq_len("\xF0\x9F\x98Hello") == 3); + assert(uni_seq_len("Hello, World!"w) == 1); + assert(uni_seq_len("ñowai!"w) == 1); + assert(uni_seq_len("你好"w) == 1); + assert(uni_seq_len("😊wow!"w) == 2); + assert(uni_seq_len(cast(wchar[])surrogates[0..1]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[0..2]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[2..3]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[3..5]) == 1); + assert(uni_seq_len("😊wow!"d) == 1); + + // test uni_strlen + assert(uni_strlen("Hello, World!") == 13); + assert(uni_strlen("ñowai!") == 6); + assert(uni_strlen("你好") == 2); + assert(uni_strlen("😊wow!") == 5); + assert(uni_strlen("\xFFHello") == 6); + assert(uni_strlen("\xC2") == 1); + assert(uni_strlen("\xC2Hello") == 6); + assert(uni_strlen("\xE2") == 1); + assert(uni_strlen("\xE2Hello") == 6); + assert(uni_strlen("\xE2\x82") == 1); + assert(uni_strlen("\xE2\x82Hello") == 6); + assert(uni_strlen("\xF0") == 1); + assert(uni_strlen("\xF0Hello") == 6); + assert(uni_strlen("\xF0\x9F") == 1); + assert(uni_strlen("\xF0\x9FHello") == 6); + assert(uni_strlen("\xF0\x9F\x98") == 1); + assert(uni_strlen("\xF0\x9F\x98Hello") == 6); + assert(uni_strlen("Hello, World!"w) == 13); + assert(uni_strlen("ñowai!"w) == 6); + assert(uni_strlen("你好"w) == 2); + assert(uni_strlen("😊wow!"w) == 5); + assert(uni_strlen(cast(wchar[])surrogates[0..1]) == 1); + assert(uni_strlen(cast(wchar[])surrogates[0..2]) == 2); + assert(uni_strlen(cast(wchar[])surrogates[2..3]) == 1); + assert(uni_strlen(cast(wchar[])surrogates[3..5]) == 2); + assert(uni_strlen("😊wow!"d) == 5); + + // test next_dchar functions + size_t sl; + assert(next_dchar("Hello, World!", sl) == 'H' && sl == 1); + assert(next_dchar("ñowai!", sl) == 'ñ' && sl == 2); + assert(next_dchar("你好", sl) == '你' && sl == 3); + assert(next_dchar("😊wow!", sl) == '😊' && sl == 4); + assert(next_dchar("\xFFHello", sl) == '�' && sl == 1); + assert(next_dchar("\xC2", sl) == '�' && sl == 1); + assert(next_dchar("\xC2Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xE2", sl) == '�' && sl == 1); + assert(next_dchar("\xE2Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xE2\x82", sl) == '�' && sl == 2); + assert(next_dchar("\xE2\x82Hello", sl) == '�' && sl == 2); + assert(next_dchar("\xF0", sl) == '�' && sl == 1); + assert(next_dchar("\xF0Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xF0\x9F", sl) == '�' && sl == 2); + assert(next_dchar("\xF0\x9FHello", sl) == '�' && sl == 2); + assert(next_dchar("\xF0\x9F\x98", sl) == '�' && sl == 3); + assert(next_dchar("\xF0\x9F\x98Hello", sl) == '�' && sl == 3); + assert(next_dchar("Hello, World!"w, sl) == 'H' && sl == 1); + assert(next_dchar("ñowai!"w, sl) == 'ñ' && sl == 1); + assert(next_dchar("你好"w, sl) == '你' && sl == 1); + assert(next_dchar("😊wow!"w, sl) == '😊' && sl == 2); + assert(next_dchar(cast(wchar[])surrogates[0..1], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[0..2], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[2..3], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[3..5], sl) == '�' && sl == 1); + assert(next_dchar("😊wow!"d, sl) == '😊' && sl == 1); + + immutable dstring unicode_test = "Basic ASCII: Hello, World!\n" ~ + "Extended Latin: Café, résumé, naïve, jalapeño\n" ~ "BMP Examples: 你好, مرحبا, שלום, 😊, ☂️\n" ~ "Supplementary Planes: 𐍈, 𝒜, 🀄, 🚀\n" ~ "Surrogate Pair Test: 😀👨‍👩‍👧‍👦\n" ~ @@ -257,20 +891,142 @@ unittest "U+E000–U+FFFF Range:  (U+E000), 豈 (U+F900)" ~ "Control Characters: \u0008 \u001B \u0000\n"; - char[1024] utf8Buffer; - wchar[512] utf16Buffer; - dchar[512] utf32Buffer; + char[1024] utf8_buffer; + wchar[512] utf16_buffer; + dchar[512] utf32_buffer; // test all conversions with characters in every significant value range - size_t utf8Len = uniConvert(unicodeTest, utf8Buffer); // D-C - size_t utf16Len = uniConvert(utf8Buffer[0..utf8Len], utf16Buffer); // C-W - size_t utf32Len = uniConvert(utf16Buffer[0..utf16Len], utf32Buffer); // W-D - utf16Len = uniConvert(utf32Buffer[0..utf32Len], utf16Buffer); // D-W - utf8Len = uniConvert(utf16Buffer[0..utf16Len], utf8Buffer); // W-C - utf32Len = uniConvert(utf8Buffer[0..utf8Len], utf32Buffer); // C-D - assert(unicodeTest[] == utf32Buffer[0..utf32Len]); + size_t utf8_len = uni_convert(unicode_test, utf8_buffer); // D-C + size_t utf16_len = uni_convert(utf8_buffer[0..utf8_len], utf16_buffer); // C-W + size_t utf32_len = uni_convert(utf16_buffer[0..utf16_len], utf32_buffer); // W-D + utf16_len = uni_convert(utf32_buffer[0..utf32_len], utf16_buffer); // D-W + utf8_len = uni_convert(utf16_buffer[0..utf16_len], utf8_buffer); // W-C + utf32_len = uni_convert(utf8_buffer[0..utf8_len], utf32_buffer); // C-D + assert(unicode_test[] == utf32_buffer[0..utf32_len]); // TODO: test all the error cases; invalid characters, buffer overflows, truncated inputs, etc... //... -} + // test uni_to_lower and uni_to_upper + assert(uni_to_lower('A') == 'a'); + assert(uni_to_lower('Z') == 'z'); + assert(uni_to_lower('a') == 'a'); + assert(uni_to_lower('z') == 'z'); + assert(uni_to_lower('À') == 'à'); + assert(uni_to_lower('Ý') == 'ý'); + assert(uni_to_lower('Ÿ') == 'ÿ'); + assert(uni_to_lower('ÿ') == 'ÿ'); + assert(uni_to_lower('ß') == 'ß'); + assert(uni_to_lower('ẞ') == 'ß'); + assert(uni_to_lower('Α') == 'α'); + assert(uni_to_lower('Ω') == 'ω'); + assert(uni_to_lower('α') == 'α'); + assert(uni_to_lower('ω') == 'ω'); + assert(uni_to_lower('Ḁ') == 'ḁ'); + assert(uni_to_lower('ḁ') == 'ḁ'); + assert(uni_to_lower('ẝ') == 'ẝ'); + assert(uni_to_lower('😊') == '😊'); + assert(uni_to_upper('a') == 'A'); + assert(uni_to_upper('z') == 'Z'); + assert(uni_to_upper('A') == 'A'); + assert(uni_to_upper('Z') == 'Z'); + assert(uni_to_upper('à') == 'À'); + assert(uni_to_upper('ý') == 'Ý'); + assert(uni_to_upper('ÿ') == 'Ÿ'); + assert(uni_to_upper('Ÿ') == 'Ÿ'); + assert(uni_to_upper('ß') == 'ẞ'); + assert(uni_to_upper('ẞ') == 'ẞ'); + assert(uni_to_upper('α') == 'Α'); + assert(uni_to_upper('ω') == 'Ω'); + assert(uni_to_upper('Α') == 'Α'); + assert(uni_to_upper('Ω') == 'Ω'); + assert(uni_to_upper('ḁ') == 'Ḁ'); + assert(uni_to_upper('Ḁ') == 'Ḁ'); + assert(uni_to_upper('😊') == '😊'); + assert(uni_to_upper('џ') == 'Џ'); + assert(uni_to_upper('Џ') == 'Џ'); + assert(uni_to_upper('д') == 'Д'); + assert(uni_to_upper('Д') == 'Д'); + assert(uni_to_upper('ѻ') == 'Ѻ'); + assert(uni_to_upper('Ѻ') == 'Ѻ'); + assert(uni_to_upper('ԫ') == 'Ԫ'); + assert(uni_to_upper('Ԫ') == 'Ԫ'); + assert(uni_to_upper('ա') == 'Ա'); + assert(uni_to_upper('Ա') == 'Ա'); + + // test uni_compare + assert(uni_compare("Hello", "Hello") == 0); + assert(uni_compare("Hello", "hello") < 0); + assert(uni_compare("hello", "Hello") > 0); + assert(uni_compare("Hello", "Hello, World!") < 0); + assert(uni_compare("Café", "Café") == 0); + assert(uni_compare("Café", "CafÉ") > 0); + assert(uni_compare("CafÉ", "Café") < 0); + assert(uni_compare("Hello, 世界", "Hello, 世界") == 0); + assert(uni_compare("Hello, 世界", "Hello, 世") > 0); + assert(uni_compare("Hello, 世", "Hello, 世界") < 0); + assert(uni_compare("Hello, 😊", "Hello, 😊") == 0); + assert(uni_compare("Hello, 😊", "Hello, 😢") < 0); + assert(uni_compare("😊A", "😊a") < 0); + + // test uni_compare_i + assert(uni_compare_i("Hello", "Hello") == 0); + assert(uni_compare_i("Hello", "hello") == 0); + assert(uni_compare_i("hello", "Hello") == 0); + assert(uni_compare_i("Hello", "Hello, World!") < 0); + assert(uni_compare_i("hello", "HORLD") < 0); + assert(uni_compare_i("Hello, 世界", "hello, 世界") == 0); + assert(uni_compare_i("Hello, 世界", "hello, 世") > 0); + assert(uni_compare_i("Hello, 世", "hello, 世界") < 0); + assert(uni_compare_i("Hello, 😊", "hello, 😊") == 0); + assert(uni_compare_i("Hello, 😊", "hello, 😢") < 0); + assert(uni_compare_i("😊a", "😊B") < 0); + assert(uni_compare_i("AZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ", "azàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ") == 0); // basic latin + latin-1 supplement + assert(uni_compare_i("ŸĀĦĬIJĹŁŇŊŒŠŦŶŹŽ", "ÿāħĭijĺłňŋœšŧŷźž") == 0); // more latin extended-a characters + assert(uni_compare_i("ḀṤẔẠỸỺỼỾ", "ḁṥẕạỹỻỽỿ") == 0); // just the extended latin + + // test various language pangrams! + assert(uni_compare_i("The quick brown fox jumps over the lazy dog", "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG") == 0); // english + assert(uni_compare_i("Sævör grét áðan því úlpan var ónýt", "SÆVÖR GRÉT ÁÐAN ÞVÍ ÚLPAN VAR ÓNÝT") == 0); // icelandic + assert(uni_compare_i("Příliš žluťoučký kůň úpěl ďábelské ódy.", "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY.") == 0); // czech + assert(uni_compare_i("Zażółć gęślą jaźń.", "ZAŻÓŁĆ GĘŚLĄ JAŹŃ.") == 0); // polish + assert(uni_compare_i("Φλεγματικά χρώματα που εξοβελίζουν ψευδαισθήσεις.", "ΦΛΕΓΜΑΤΙΚΆ ΧΡΏΜΑΤΑ ΠΟΥ ΕΞΟΒΕΛΊΖΟΥΝ ΨΕΥΔΑΙΣΘΉΣΕΙΣ.") == 0); // greek + assert(uni_compare_i("Любя, съешь щипцы, — вздохнёт мэр, — Кайф жгуч!", "ЛЮБЯ, СЪЕШЬ ЩИПЦЫ, — ВЗДОХНЁТ МЭР, — КАЙФ ЖГУЧ!") == 0); // russian + assert(uni_compare_i("Բել դղյակի ձախ ժամն օֆ ազգությանը ցպահանջ չճշտած վնաս էր եւ փառք։", "ԲԵԼ ԴՂՅԱԿԻ ՁԱԽ ԺԱՄՆ ՕՖ ԱԶԳՈՒԹՅԱՆԸ ՑՊԱՀԱՆՋ ՉՃՇՏԱԾ ՎՆԱՍ ԷՐ ԵՒ ՓԱՌՔ։") == 0); // armenian + assert(uni_compare_i("აბგად ევზეთ იკალ მანო, პაჟა რასტა უფქა ღაყაშ, ჩაცა ძაწა ჭახა ჯაჰო", "ᲐᲑᲒᲐᲓ ᲔᲕᲖᲔᲗ ᲘᲙᲐᲚ ᲛᲐᲜᲝ, ᲞᲐᲟᲐ ᲠᲐᲡᲢᲐ ᲣᲤᲥᲐ ᲦᲐᲧᲐᲨ, ᲩᲐᲪᲐ ᲫᲐᲬᲐ ᲭᲐᲮᲐ ᲯᲐᲰᲝ") == 0); // georgian modern + assert(uni_compare_i("ⴘⴄⴅ ⴟⴓ ⴠⴡⴢ ⴣⴤⴥ ⴇⴍⴚ ⴞⴐⴈ ⴝⴋⴊⴈ.", "ႸႤႥ ႿႳ ჀჁჂ ჃჄჅ ႧႭႺ ႾႰႨ ႽႫႪႨ.") == 0); // georgian ecclesiastical + assert(uni_compare_i("Ⲁⲛⲟⲕ ⲡⲉ ϣⲏⲙ ⲛ̄ⲕⲏⲙⲉ.", "ⲀⲚⲞⲔ ⲠⲈ ϢⲎⲘ Ⲛ̄ⲔⲎⲘⲈ.") == 0); // coptic + + // test the special-cases around german 'ß' (0xDF) and 'ẞ' (0x1E9E) + // check sort order + assert(uni_compare_i("ß", "sr") > 0); + assert(uni_compare_i("ß", "ss") == 0); + assert(uni_compare_i("ß", "st") < 0); + assert(uni_compare_i("sr", "ß") < 0); + assert(uni_compare_i("ss", "ß") == 0); + assert(uni_compare_i("st", "ß") > 0); + // check truncated comparisons + assert(uni_compare_i("ß", "s") > 0); + assert(uni_compare_i("ß", "r") > 0); + assert(uni_compare_i("ß", "t") < 0); + assert(uni_compare_i("s", "ß") < 0); + assert(uni_compare_i("r", "ß") < 0); + assert(uni_compare_i("t", "ß") > 0); + assert(uni_compare_i("ä", "ß") > 0); + assert(uni_compare_i("sß", "ss") > 0); + assert(uni_compare_i("sß", "ß") > 0); + assert(uni_compare_i("sß", "ßß") < 0); + assert(uni_compare_i("ss", "sß") < 0); + assert(uni_compare_i("ß", "sß") < 0); + assert(uni_compare_i("ßß", "sß") > 0); + // check uneven/recursive comparisons + assert(uni_compare_i("ßẞ", "ẞß") == 0); + assert(uni_compare_i("sß", "ẞs") == 0); + assert(uni_compare_i("ẞs", "sß") == 0); + assert(uni_compare_i("ẞß", "sßs") == 0); + assert(uni_compare_i("sẞs", "ẞß") == 0); + assert(uni_compare_i("sẞsß", "ßsßs") == 0); + assert(uni_compare_i("ẞsßs", "sßsß") == 0); + assert(uni_compare_i("ßßßs", "sßßß") == 0); + assert(uni_compare_i("sßßß", "ßßßs") == 0); +} diff --git a/src/urt/system.d b/src/urt/system.d index 84f97a1..903ce2a 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -39,17 +39,17 @@ void sleep(Duration duration) struct SystemInfo { - string osName; + string os_name; string processor; - ulong totalMemory; - ulong availableMemory; + ulong total_memory; + ulong available_memory; Duration uptime; } -SystemInfo getSysInfo() +SystemInfo get_sysinfo() { SystemInfo r; - r.osName = Platform; + r.os_name = Platform; r.processor = ProcessorFamily; version (Windows) { @@ -57,8 +57,8 @@ SystemInfo getSysInfo() mem.dwLength = MEMORYSTATUSEX.sizeof; if (GlobalMemoryStatusEx(&mem)) { - r.totalMemory = mem.ullTotalPhys; - r.availableMemory = mem.ullAvailPhys; + r.total_memory = mem.ullTotalPhys; + r.available_memory = mem.ullAvailPhys; } r.uptime = msecs(GetTickCount64()); } @@ -70,8 +70,8 @@ SystemInfo getSysInfo() if (sysinfo(&info) < 0) assert(false, "sysinfo() failed!"); - r.totalMemory = cast(ulong)info.totalram * info.mem_unit; - r.availableMemory = cast(ulong)info.freeram * info.mem_unit; + r.total_memory = cast(ulong)info.totalram * info.mem_unit; + r.available_memory = cast(ulong)info.freeram * info.mem_unit; r.uptime = seconds(info.uptime); } else version (Posix) @@ -83,13 +83,13 @@ SystemInfo getSysInfo() assert(pages >= 0 && page_size >= 0, "sysconf() failed!"); - r.totalMemory = cast(ulong)pages * page_size; - static assert(false, "TODO: need `availableMemory`"); + r.total_memory = cast(ulong)pages * page_size; + static assert(false, "TODO: need `available_memory`"); } return r; } -void setSystemIdleParams(IdleParams params) +void set_system_idle_params(IdleParams params) { version (Windows) { @@ -99,7 +99,7 @@ void setSystemIdleParams(IdleParams params) enum EXECUTION_STATE ES_DISPLAY_REQUIRED = 0x00000002; enum EXECUTION_STATE ES_CONTINUOUS = 0x80000000; - SetThreadExecutionState(ES_CONTINUOUS | (params.SystemRequired ? ES_SYSTEM_REQUIRED : 0) | (params.DisplayRequired ? ES_DISPLAY_REQUIRED : 0)); + SetThreadExecutionState(ES_CONTINUOUS | ((params & IdleParams.SystemRequired) ? ES_SYSTEM_REQUIRED : 0) | ((params & IdleParams.DisplayRequired) ? ES_DISPLAY_REQUIRED : 0)); } else version (Posix) { @@ -112,11 +112,11 @@ void setSystemIdleParams(IdleParams params) unittest { - SystemInfo info = getSysInfo(); + SystemInfo info = get_sysinfo(); assert(info.uptime > Duration.zero); import urt.io; - writelnf("System info: {0} - {1}, mem: {2}kb ({3}kb)", info.osName, info.processor, info.totalMemory / (1024), info.availableMemory / (1024)); + writelnf("System info: {0} - {1}, mem: {2}kb ({3}kb)", info.os_name, info.processor, info.total_memory / (1024), info.available_memory / (1024)); } diff --git a/src/urt/time.d b/src/urt/time.d index 646af39..7cca87f 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -1,6 +1,6 @@ module urt.time; -import urt.traits : isSomeFloat; +import urt.traits : is_some_float; version (Windows) { @@ -28,7 +28,8 @@ enum Day : ubyte enum Month : ubyte { - January = 1, + Unspecified = 0, + January, February, March, April, @@ -63,10 +64,15 @@ pure nothrow @nogc: T opCast(T)() const if (is(T == Time!c, Clock c) && c != clock) { - static if (clock == Clock.Monotonic && c == Clock.SystemTime) - return SysTime(ticks + ticksSinceBoot); + static if (is(T == Time!c, Clock c) && c != clock) + { + static if (clock == Clock.Monotonic && c == Clock.SystemTime) + return SysTime(ticks + ticksSinceBoot); + else + return MonoTime(ticks - ticksSinceBoot); + } else - return MonoTime(ticks - ticksSinceBoot); + static assert(false, "constraint out of sync"); } bool opEquals(Time!clock b) const @@ -100,14 +106,38 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - long ms = (ticks != 0 ? appTime(this) : Duration()).as!"msecs"; - if (!buffer.ptr) - return 2 + timeToString(ms, null); - if (buffer.length < 2) - return -1; - buffer[0..2] = "T+"; - ptrdiff_t len = timeToString(ms, buffer[2..$]); - return len < 0 ? len : 2 + len; + static if (clock == Clock.SystemTime) + { + DateTime dt = getDateTime(this); + return dt.toString(buffer, format, formatArgs); + } + else + { + long ns = (ticks != 0 ? appTime(this) : Duration()).as!"nsecs"; + if (!buffer.ptr) + return 2 + timeToString(ns, null); + if (buffer.length < 2) + return -1; + buffer[0..2] = "T+"; + ptrdiff_t len = timeToString(ns, buffer[2..$]); + return len < 0 ? len : 2 + len; + } + } + + ptrdiff_t fromString(const(char)[] s) + { + static if (clock == Clock.SystemTime) + { + DateTime dt; + ptrdiff_t len = dt.fromString(s); + if (len >= 0) + this = getSysTime(dt); + return len; + } + else + { + assert(false, "TODO: ???"); // what is the format we parse? + } } auto __debugOverview() const @@ -137,7 +167,7 @@ pure nothrow @nogc: bool opCast(T : bool)() const => ticks != 0; - T opCast(T)() const if (isSomeFloat!T) + T opCast(T)() const if (is_some_float!T) => cast(T)ticks / cast(T)ticksPerSecond; bool opEquals(Duration b) const @@ -183,7 +213,98 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - return timeToString(as!"msecs", buffer); + return timeToString(as!"nsecs", buffer); + } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int; + import urt.string.ascii : is_alpha, ieq; + + if (s.length == 0) + return -1; + + size_t offset = 0; + long total_nsecs = 0; + ubyte last_unit = 8; + + while (offset < s.length) + { + // Parse number + size_t len; + long value = s[offset..$].parse_int(&len); + if (len == 0) + return last_unit != 8 ? offset : -1; + offset += len; + + if (offset >= s.length) + return -1; + + // Parse unit + size_t unit_start = offset; + while (offset < s.length && s[offset].is_alpha) + offset++; + + if (offset == unit_start) + return -1; + + const(char)[] unit = s[unit_start..offset]; + + // Convert unit to nanoseconds and check for duplicates + ubyte this_unit; + if (unit.ieq("w")) + { + value *= 604_800_000_000_000; + this_unit = 7; + } + else if (unit.ieq("d")) + { + value *= 86_400_000_000_000; + this_unit = 6; + } + else if (unit.ieq("h")) + { + value *= 3_600_000_000_000; + this_unit = 5; + } + else if (unit.ieq("m")) + { + value *= 60_000_000_000; + this_unit = 4; + } + else if (unit.ieq("s")) + { + value *= 1_000_000_000; + this_unit = 3; + } + else if (unit.ieq("ms")) + { + value *= 1_000_000; + this_unit = 2; + } + else if (unit.ieq("us")) + { + value *= 1_000; + this_unit = 1; + } + else if (unit.ieq("ns")) + this_unit = 0; + else + return -1; + + // Check for ordering, duplicates, and only one of ms/us/ns may be present + if (this_unit >= last_unit || (this_unit < 2 && last_unit < 3)) + return -1; + last_unit = this_unit; + + total_nsecs += value; + } + + if (last_unit == 8) + return -1; + + ticks = total_nsecs / nsecMultiplier; + return offset; } auto __debugOverview() const @@ -276,55 +397,279 @@ pure nothrow @nogc: this = mixin("this " ~ op ~ " rhs;"); } + int opCmp(DateTime dt) const + { + int r = year - dt.year; + if (r != 0) return r; + r = month - dt.month; + if (r != 0) return r; + r = day - dt.day; + if (r != 0) return r; + r = hour - dt.hour; + if (r != 0) return r; + r = minute - dt.minute; + if (r != 0) return r; + r = second - dt.second; + if (r != 0) return r; + return ns - dt.ns; + } + import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - import urt.conv : formatInt; + import urt.conv : format_int, format_uint; - size_t offset = 0; - uint y = year; - if (year <= 0) + ptrdiff_t len; + if (!buffer.ptr) { - if (buffer.length < 3) - return -1; - y = -year + 1; - buffer[0 .. 3] = "BC "; - offset += 3; + len = 15; // all the fixed chars + len += year.format_int(null); + if (ns) + { + ++len; // the dot + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + ++len; + uint digit = nsecs / digit_multipliers[m]; + nsecs -= digit * digit_multipliers[m++]; + } + } + return len; } - ptrdiff_t len = year.formatInt(buffer[offset..$]); - if (len < 0 || len == buffer.length) + + len = year.format_int(buffer[]); + if (len < 0 || len + 15 > buffer.length) return -1; - offset += len; + size_t offset = len; buffer[offset++] = '-'; - len = month.formatInt(buffer[offset..$]); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (month / 10); + buffer[offset++] = '0' + (month % 10); buffer[offset++] = '-'; - len = day.formatInt(buffer[offset..$]); - if (len < 0 || len == buffer.length) + buffer[offset++] = '0' + (day / 10); + buffer[offset++] = '0' + (day % 10); + buffer[offset++] = 'T'; + buffer[offset++] = '0' + (hour / 10); + buffer[offset++] = '0' + (hour % 10); + buffer[offset++] = ':'; + buffer[offset++] = '0' + (minute / 10); + buffer[offset++] = '0' + (minute % 10); + buffer[offset++] = ':'; + buffer[offset++] = '0' + (second / 10); + buffer[offset++] = '0' + (second % 10); + if (ns) + { + if (offset == buffer.length) + return -1; + buffer[offset++] = '.'; + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + if (offset == buffer.length) + return -1; + int digit = nsecs / digit_multipliers[m]; + buffer[offset++] = cast(char)('0' + digit); + nsecs -= digit * digit_multipliers[m++]; + } + } + // TODO: timezone suffix? + return offset; + } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int, parse_uint; + import urt.string.ascii : ieq, is_numeric, is_whitespace, to_lower; + + month = Month.Unspecified; + day = 0; + hour = 0; + minute = 0; + second = 0; + ns = 0; + + size_t offset = 0; + + // parse year + if (s.length >= 2 && s[0..2].ieq("bc")) + { + offset = 2; + if (s.length >= 3 && s[2] == ' ') + ++offset; + } + size_t len; + long value = s[offset..$].parse_int(&len); + if (len == 0) return -1; + if (offset > 0) + { + if (value <= 0) + return -1; // no year 0, or negative years BC! + value = -value + 1; + } + if (value < short.min || value > short.max) + return -1; + year = cast(short)value; offset += len; - buffer[offset++] = ' '; - len = hour.formatInt(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; + + // parse month + value = s[++offset..$].parse_int(&len); + if (len == 0) + { + if (s.length < 3) + return -1; + foreach (i; 0..12) + { + if (s[offset..offset+3].ieq(g_month_names[i])) + { + value = i + 1; + len = 3; + goto got_month; + } + } + return -1; + got_month: + } + else if (value < 1 || value > 12) return -1; + month = cast(Month)value; offset += len; - buffer[offset++] = ':'; - len = minute.formatInt(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; + + // parse day + value = s[++offset..$].parse_int(&len); + if (len == 0 || value < 1 || value > 31) return -1; + day = cast(ubyte)value; offset += len; - buffer[offset++] = ':'; - len = second.formatInt(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + + if (offset == s.length || (s[offset] != 'T' && s[offset] != ' ')) + return offset; + + // parse hour + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 23) return -1; + hour = cast(ubyte)value; offset += len; - buffer[offset++] = '.'; - len = (ns / 1_000_000).formatInt(buffer[offset..$], 10, 3, '0'); - if (len < 0) - return len; - return offset + len; + + if (offset == s.length) + return offset; + + if (s[offset] == ':') + { + // parse minute + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + minute = cast(ubyte)value; + offset += len; + + if (offset == s.length) + return offset; + + if (s[offset] == ':') + { + // parse second + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + second = cast(ubyte)value; + offset += len; + + if (offset < s.length && s[offset] == '.') + { + // parse fractions + ++offset; + uint multiplier = 100_000_000; + while (offset < s.length && multiplier > 0 && s[offset].is_numeric) + { + ns += (s[offset++] - '0') * multiplier; + multiplier /= 10; + } + if (multiplier == 100_000_000) + return offset-1; // no number after the dot + } + } + else if (s[offset] == '.') + { + // fraction of minute + assert(false, "TODO"); + } + } + else if (s[offset] == '.') + { + // fraction of hour + assert(false, "TODO"); + } + + if (offset == s.length) + return offset; + + if (s[offset].to_lower == 'z') + { + // TODO: UTC timezone designator... +// assert(false, "TODO: we need to know our local timezone..."); + + return offset + 1; + } + + if (s[offset] != '-' && s[offset] != '+') + return offset; + + size_t tz_offset = offset + 1; + + // parse timezone (00:00) + int tz_hr, tz_min; + + value = s[tz_offset..$].parse_uint(&len); + if (len == 0) + return offset; + + if (len == 4) + { + if (value > 2359) + return -1; + tz_min = cast(int)(value % 100); + if (tz_min > 59) + return -1; + tz_hr = cast(int)(value / 100); + tz_offset += 4; + } + else + { + if (len != 2 || value > 59) + return -1; + + tz_hr = cast(int)value; + tz_offset += 2; + + if (tz_offset < s.length && s[tz_offset] == ':') + { + value = s[tz_offset+1..$].parse_uint(&len); + if (len != 0) + { + if (len != 2 || value > 59) + return -1; + tz_min = cast(int)value; + tz_offset += 3; + } + } + } + + if (s[offset] == '-') + tz_hr = -tz_hr; + +// assert(false, "TODO: we need to know our local timezone..."); + + return tz_offset; } } @@ -395,6 +740,17 @@ SysTime getSysTime() } } +SysTime getSysTime(DateTime time) pure +{ + version (Windows) + return dateTimeToFileTime(time); + else version (Posix) + { + assert(false, "TODO"); + } + else + static assert(false, "TODO"); +} DateTime getDateTime() { @@ -410,7 +766,7 @@ DateTime getDateTime() static assert(false, "TODO"); } -DateTime getDateTime(SysTime time) +DateTime getDateTime(SysTime time) pure { version (Windows) return fileTimeToDateTime(time); @@ -443,6 +799,16 @@ ulong unixTimeNs(SysTime t) pure static assert(false, "TODO"); } +SysTime from_unix_time_ns(ulong ns) pure +{ + version (Windows) + return SysTime(ns / 100UL + 116444736000000000UL); + else version (Posix) + return SysTime(ns); + else + static assert(false, "TODO"); +} + Duration abs(Duration d) pure => Duration(d.ticks < 0 ? -d.ticks : d.ticks); @@ -451,6 +817,9 @@ private: immutable MonoTime startTime; +__gshared immutable string[12] g_month_names = [ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" ]; +__gshared immutable uint[9] digit_multipliers = [ 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 ]; + version (Windows) { immutable uint ticksPerSecond; @@ -509,22 +878,39 @@ package(urt) void initClock() static assert(false, "TODO"); } -ptrdiff_t timeToString(long ms, char[] buffer) pure +ptrdiff_t timeToString(long ns, char[] buffer) pure { - import urt.conv : formatInt; + import urt.conv : format_int; - long hr = ms / 3_600_000; + int hr = cast(int)(ns / 3_600_000_000_000); + ns = ns < 0 ? -ns % 3_600_000_000_000 : ns % 3_600_000_000_000; + uint remainder = cast(uint)(ns % 1_000_000_000); if (!buffer.ptr) - return hr.formatInt(null, 10, 2, '0') + 10; + { + size_t tail = 6; + if (remainder) + { + ++tail; + uint m = 0; + do + { + ++tail; + uint digit = cast(uint)(remainder / digit_multipliers[m]); + remainder -= digit * digit_multipliers[m++]; + } + while (remainder); + } + return hr.format_int(null, 10, 2, '0') + tail; + } - ptrdiff_t len = hr.formatInt(buffer, 10, 2, '0'); - if (len < 0 || buffer.length < len + 10) + ptrdiff_t len = hr.format_int(buffer, 10, 2, '0'); + if (len < 0 || buffer.length < len + 6) return -1; - ubyte min = cast(ubyte)(ms / 60_000 % 60); - ubyte sec = cast(ubyte)(ms / 1000 % 60); - ms %= 1000; + uint min_sec = cast(uint)(ns / 1_000_000_000); + uint min = min_sec / 60; + uint sec = min_sec % 60; buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (min / 10)); @@ -532,10 +918,22 @@ ptrdiff_t timeToString(long ms, char[] buffer) pure buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (sec / 10)); buffer.ptr[len++] = cast(char)('0' + (sec % 10)); - buffer.ptr[len++] = '.'; - buffer.ptr[len++] = cast(char)('0' + (ms / 100)); - buffer.ptr[len++] = cast(char)('0' + ((ms/10) % 10)); - buffer.ptr[len++] = cast(char)('0' + (ms % 10)); + if (remainder) + { + if (buffer.length < len + 2) + return -1; + buffer.ptr[len++] = '.'; + uint i = 0; + while (remainder) + { + if (buffer.length <= len) + return -1; + uint m = digit_multipliers[i++]; + uint digit = cast(uint)(remainder / m); + buffer.ptr[len++] = cast(char)('0' + digit); + remainder -= digit * m; + } + } return len; } @@ -544,22 +942,79 @@ unittest import urt.mem.temp; assert(tconcat(msecs(3_600_000*3 + 60_000*47 + 1000*34 + 123))[] == "03:47:34.123"); - assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00.000"); - - assert(getTime().toString(null, null, null) == 14); + assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00"); + assert(tconcat(usecs(3_600_000_000*-123 + 1))[] == "-122:59:59.999999"); + assert(MonoTime().toString(null, null, null) == 10); assert(tconcat(getTime())[0..2] == "T+"); + + // Test Duration.fromString with compound formats + Duration d; + + // Simple single units + assert(d.fromString("5s") == 2 && d.as!"seconds" == 5); + assert(d.fromString("10m") == 3 && d.as!"minutes" == 10); + + // Compound durations + assert(d.fromString("1h30m") == 5 && d.as!"minutes" == 90); + assert(d.fromString("5m30s") == 5 && d.as!"seconds" == 330); + + // Duplicate units should fail + assert(d.fromString("30m30m") == -1); + assert(d.fromString("1h2h") == -1); + assert(d.fromString("5s10s") == -1); + + // Out-of-order units should fail + assert(d.fromString("30s5m") == -1); // s before m + assert(d.fromString("1m2h") == -1); // m before h + assert(d.fromString("5s10ms5m") == -1); // m after s + assert(d.fromString("5s10ms10ns") == -1); // ms and ns (only one sub-second unit allowed) + + // Improper units should fail + assert(d.fromString("30z") == -1); // not a time denomination + + // Correctly ordered units should work + assert(d.fromString("1d2h30m15s") == 10 && d.as!"seconds" == 86_400 + 7_200 + 1_800 + 15); + + // Test DateTime.fromString + DateTime dt; + assert(dt.fromString("2024-06-15T12:34:56") == 19); + assert(dt.fromString("-100/06/15 12:34:56") == 19); + assert(dt.fromString("BC100-AUG-15 12:34:56.789") == 25); + assert(dt.fromString("BC 10000-AUG-15 12:34:56.789123456") == 34); + assert(dt.fromString("1-1-1 01:01:01") == 14); + assert(dt.fromString("1-1-1 01:01:01.") == 14); + assert(dt.fromString("2025") == 4); + assert(dt.fromString("2025-10") == 7); + assert(dt.fromString("2025-01-01") == 10); + assert(dt.fromString("2025-01-01 00") == 13); + assert(dt.fromString("2025-01-01 00:10") == 16); + assert(dt.fromString("2025-01-01 00:10z") == 17); + assert(dt.fromString("2025-01-01 00+00:00") == 19); + assert(dt.fromString("2025-01-01 00-1030") == 18); + assert(dt.fromString("2025-01-01 00+08") == 16); + + assert(dt.fromString("BC -10") == -1); + assert(dt.fromString("2024-0-15 12:34:56") == -1); + assert(dt.fromString("2024-13-15 12:34:56") == -1); + assert(dt.fromString("2024-1-0 12:34:56") == -1); + assert(dt.fromString("2024-1-32 12:34:56") == -1); + assert(dt.fromString("2024-1-1 24:34:56") == -1); + assert(dt.fromString("2024-1-1 01:60:56") == -1); + assert(dt.fromString("2024-1-1 01:01:60") == -1); + assert(dt.fromString("10000-1-1 1:01:01") == -1); } version (Windows) { - DateTime fileTimeToDateTime(SysTime ftime) + DateTime fileTimeToDateTime(SysTime ftime) pure { version (BigEndian) static assert(false, "Only works in little endian!"); SYSTEMTIME stime; - FileTimeToSystemTime(cast(FILETIME*)&ftime.ticks, &stime); + alias PureHACK = extern(Windows) BOOL function(const(FILETIME)*, LPSYSTEMTIME) pure nothrow @nogc; + (cast(PureHACK)&FileTimeToSystemTime)(cast(FILETIME*)&ftime.ticks, &stime); DateTime dt; dt.year = stime.wYear; @@ -575,13 +1030,40 @@ version (Windows) return dt; } + + SysTime dateTimeToFileTime(DateTime dt) pure + { + version (BigEndian) + static assert(false, "Only works in little endian!"); + + SYSTEMTIME stime; + stime.wYear = dt.year; + stime.wMonth = cast(ushort)dt.month; + stime.wDayOfWeek = cast(ushort)dt.wday; + stime.wDay = cast(ushort)dt.day; + stime.wHour = cast(ushort)dt.hour; + stime.wMinute = cast(ushort)dt.minute; + stime.wSecond = cast(ushort)dt.second; + stime.wMilliseconds = cast(ushort)(dt.ns / 1_000_000); + + SysTime ftime; + alias PureHACK = extern(Windows) BOOL function(const(SYSTEMTIME)*, FILETIME*) pure nothrow @nogc; + if (!(cast(PureHACK)&SystemTimeToFileTime)(&stime, cast(FILETIME*)&ftime)) + assert(false, "TODO: WHAT TO DO?"); + + debug assert(ftime.ticks % 10_000_000 == (dt.ns / 1_000_000) * 10_000); + ftime.ticks = ftime.ticks - ftime.ticks % 10_000_000 + dt.ns / 100; + + return ftime; + } } else version (Posix) { - DateTime realtimeToDateTime(timespec ts) + DateTime realtimeToDateTime(timespec ts) pure { tm t; - gmtime_r(&ts.tv_sec, &t); + alias PureHACK = extern(C) tm* function(time_t* timer, tm* buf) pure nothrow @nogc; + (cast(PureHACK)&gmtime_r)(&ts.tv_sec, &t); DateTime dt; dt.year = cast(short)(t.tm_year + 1900); diff --git a/src/urt/traits.d b/src/urt/traits.d index 30ef675..6f8a761 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -3,67 +3,67 @@ module urt.traits; import urt.meta; -enum bool isType(alias X) = is(X); +enum bool is_type(alias X) = is(X); -enum bool isBoolean(T) = __traits(isUnsigned, T) && is(T : bool); +enum bool is_boolean(T) = __traits(isUnsigned, T) && is(T : bool); -enum bool isUnsignedInt(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); -enum bool isSignedInt(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); -enum bool isSomeInt(T) = isUnsignedInt!T || isSignedInt!T; -enum bool isUnsignedIntegral(T) = is(Unqual!T == bool) || isUnsignedInt!T || isSomeChar!T; -enum bool isSignedIntegral(T) = isSignedInt!T; -enum bool isIntegral(T) = isUnsignedIntegral!T || isSignedIntegral!T; -enum bool isSomeFloat(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); +enum bool is_unsigned_int(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); +enum bool is_signed_int(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); +enum bool is_some_int(T) = is_unsigned_int!T || is_signed_int!T; +enum bool is_unsigned_integral(T) = is_boolean!T || is_unsigned_int!T || is_some_char!T; +enum bool is_signed_integral(T) = is_signed_int!T; +enum bool is_integral(T) = is_unsigned_integral!T || is_signed_integral!T; +enum bool is_some_float(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); -enum bool isEnum(T) = is(T == enum); -template enumType(T) - if (isEnum!T) +enum bool is_enum(T) = is(T == enum); +template EnumType(T) + if (is_enum!T) { static if (is(T E == enum)) - alias enumType = E; + alias EnumType = E; else static assert(false, "How this?"); } -template isUnsigned(T) +template is_unsigned(T) { static if (!__traits(isUnsigned, T)) - enum isUnsigned = false; + enum is_unsigned = false; else static if (is(T U == enum)) - enum isUnsigned = isUnsigned!U; + enum is_unsigned = is_unsigned!U; else - enum isUnsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. + enum is_unsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. && !is(immutable T == immutable bool) && !is(T == __vector); } -enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T : real); +enum bool is_signed(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T : real); -template isSomeChar(T) +template is_some_char(T) { static if (!__traits(isUnsigned, T)) - enum isSomeChar = false; + enum is_some_char = false; else static if (is(T U == enum)) - enum isSomeChar = isSomeChar!U; + enum is_some_char = is_some_char!U; else - enum isSomeChar = !__traits(isZeroInit, T); + enum is_some_char = !__traits(isZeroInit, T); } -enum bool isSomeFunction(alias T) = is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return); -enum bool isFunctionPointer(alias T) = is(typeof(*T) == function); -enum bool isDelegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); +enum bool is_some_function(alias T) = is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return); +enum bool is_function_pointer(alias T) = is(typeof(*T) == function); +enum bool is_delegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); -template isCallable(alias callable) +template is_callable(alias callable) { static if (is(typeof(&callable.opCall) == delegate)) - enum bool isCallable = true; + enum bool is_callable = true; else static if (is(typeof(&callable.opCall) V : V*) && is(V == function)) - enum bool isCallable = true; + enum bool is_callable = true; else static if (is(typeof(&callable.opCall!()) TemplateInstanceType)) - enum bool isCallable = isCallable!TemplateInstanceType; + enum bool is_callable = is_callable!TemplateInstanceType; else static if (is(typeof(&callable!()) TemplateInstanceType)) - enum bool isCallable = isCallable!TemplateInstanceType; + enum bool is_callable = is_callable!TemplateInstanceType; else - enum bool isCallable = isSomeFunction!callable; + enum bool is_callable = is_some_function!callable; } @@ -79,7 +79,7 @@ template Unqual(T : const U, U) template Unsigned(T) { - static if (isUnsigned!T) + static if (is_unsigned!T) alias Unsigned = T; else static if (is(T == long)) alias Unsigned = ulong; @@ -113,7 +113,7 @@ template Unsigned(T) template Signed(T) { - static if (isSigned!T) + static if (is_signed!T) alias Unsigned = T; else static if (is(T == ulong)) alias Signed = long; @@ -144,7 +144,7 @@ template Signed(T) } template ReturnType(alias func) - if (isCallable!func) + if (is_callable!func) { static if (is(FunctionTypeOf!func R == return)) alias ReturnType = R; @@ -153,7 +153,7 @@ template ReturnType(alias func) } template Parameters(alias func) - if (isCallable!func) + if (is_callable!func) { static if (is(FunctionTypeOf!func P == function)) alias Parameters = P; @@ -161,26 +161,26 @@ template Parameters(alias func) static assert(0, "argument has no parameters"); } -template ParameterIdentifierTuple(alias func) - if (isCallable!func) +template parameter_identifier_tuple(alias func) + if (is_callable!func) { static if (is(FunctionTypeOf!func PT == __parameters)) { - alias ParameterIdentifierTuple = AliasSeq!(); + alias parameter_identifier_tuple = AliasSeq!(); static foreach (i; 0 .. PT.length) { - static if (!isFunctionPointer!func && !isDelegate!func + static if (!is_function_pointer!func && !is_delegate!func // Unnamed parameters yield CT error. && is(typeof(__traits(identifier, PT[i .. i+1]))) // Filter out unnamed args, which look like (Type) instead of (Type name). && PT[i].stringof != PT[i .. i+1].stringof[1..$-1]) { - ParameterIdentifierTuple = AliasSeq!(ParameterIdentifierTuple, + parameter_identifier_tuple = AliasSeq!(parameter_identifier_tuple, __traits(identifier, PT[i .. i+1])); } else { - ParameterIdentifierTuple = AliasSeq!(ParameterIdentifierTuple, ""); + parameter_identifier_tuple = AliasSeq!(parameter_identifier_tuple, ""); } } } @@ -188,12 +188,12 @@ template ParameterIdentifierTuple(alias func) { static assert(0, func.stringof ~ " is not a function"); // avoid pointless errors - alias ParameterIdentifierTuple = AliasSeq!(); + alias parameter_identifier_tuple = AliasSeq!(); } } template FunctionTypeOf(alias func) - if (isCallable!func) + if (is_callable!func) { static if ((is(typeof(& func) Fsym : Fsym*) && is(Fsym == function)) || is(typeof(& func) Fsym == delegate)) alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol @@ -218,27 +218,29 @@ template FunctionTypeOf(alias func) } // is T a primitive/builtin type? -enum isPrimitive(T) = isIntegral!T || isSomeFloat!T || (isEnum!T && isPrimitive!(enumType!T) || - is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && isPrimitive!A) || +enum is_primitive(T) = is_integral!T || is_some_float!T || (is_enum!T && is_primitive!(EnumType!T) || + is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && is_primitive!A) || is(T == R function(Args), R, Args...) || is(T == R delegate(Args), R, Args...)); -enum isDefaultConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T t; })); +enum is_trivial(T) = __traits(isPOD, T); -enum isConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || +enum is_default_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T t; })); + +enum is_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || (is(T == struct) && __traits(compiles, (Args args) { T x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? // TODO: we need to know it's not calling an elaborate constructor... -//enum isTriviallyConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || -// (is(T == struct) && __traits(compiles, (Args args) { auto x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? +//enum is_trivially_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || +// (is(T == struct) && __traits(compiles, (Args args) { auto x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? -//enum isCopyConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T u = lvalueOf!T; })); -//enum isMoveConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T u = rvalueOf!T; })); +//enum is_copy_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T u = lvalue_of!T; })); +//enum is_move_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T u = rvalue_of!T; })); -enum isTriviallyDefaultConstructible(T) = isDefaultConstructible!T; // dlang doesn't have elaborate default constructors (YET...) -//enum isTriviallyCopyConstructible(T) = isPrimitive!T; // TODO: somehow find out if there is no copy constructor -//enum isTriviallyMoveConstructible(T) = isPrimitive!T || is(T == struct); // TODO: somehow find out if there is no move constructor +enum is_trivially_default_constructible(T) = is_default_constructible!T; // dlang doesn't have elaborate default constructors (YET...) +//enum is_trivially_copy_constructible(T) = is_primitive!T; // TODO: somehow find out if there is no copy constructor +//enum is_trivially_move_constructible(T) = is_primitive!T || is(T == struct); // TODO: somehow find out if there is no move constructor // helpers to test certain expressions private struct __InoutWorkaroundStruct{} -@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; -@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; +@property T rvalue_of(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; +@property ref T lvalue_of(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; diff --git a/src/urt/util.d b/src/urt/util.d index 044f47f..fb8fe2d 100644 --- a/src/urt/util.d +++ b/src/urt/util.d @@ -40,23 +40,23 @@ auto max(T, U)(auto ref inout T a, auto ref inout U b) template Align(size_t value, size_t alignment = size_t.sizeof) { - static assert(isPowerOf2(alignment), "Alignment must be a power of two: ", alignment); + static assert(is_power_of_2(alignment), "Alignment must be a power of two: ", alignment); enum Align = alignTo(value, alignment); } -enum IsAligned(size_t value) = isAligned(value); -enum IsPowerOf2(size_t value) = isPowerOf2(value); -enum NextPowerOf2(size_t value) = nextPowerOf2(value); +enum IsAligned(size_t value) = is_aligned(value); +enum IsPowerOf2(size_t value) = is_power_of_2(value); +enum NextPowerOf2(size_t value) = next_power_of_2(value); -bool isPowerOf2(T)(T x) - if (isSomeInt!T) +bool is_power_of_2(T)(T x) + if (is_some_int!T) { return (x & (x - 1)) == 0; } -T nextPowerOf2(T)(T x) - if (isSomeInt!T) +T next_power_of_2(T)(T x) + if (is_some_int!T) { x -= 1; x |= x >> 1; @@ -71,40 +71,42 @@ T nextPowerOf2(T)(T x) return cast(T)(x + 1); } -T alignDown(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +T align_down(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { return cast(T)(cast(size_t)value & ~(alignment - 1)); } -T alignDown(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +T align_down(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { return cast(T)(cast(size_t)value & ~(alignment - 1)); } -T alignUp(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +T align_up(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { return cast(T)((cast(size_t)value + (alignment - 1)) & ~(alignment - 1)); } -T alignUp(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +T align_up(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { return cast(T)((cast(size_t)value + (alignment - 1)) & ~(alignment - 1)); } -bool isAligned(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +bool is_aligned(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { - static assert(IsPowerOf2!alignment, "Alignment must be a power of two!"); + static assert(IsPowerOf2!alignment, "Alignment must be a power of two"); + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } -bool isAligned(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +bool is_aligned(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } @@ -140,7 +142,7 @@ ubyte log2(ubyte val) } ubyte log2(T)(T val) - if (isSomeInt!T && T.sizeof > 1) + if (is_some_int!T && T.sizeof > 1) { if (T.sizeof > 4 && val >> 32) { @@ -172,7 +174,7 @@ ubyte log2(T)(T val) +/ ubyte log2(T)(T x) - if (isIntegral!T) + if (is_integral!T) { ubyte result = 0; static if (T.sizeof > 4) @@ -201,7 +203,7 @@ ubyte log2(T)(T x) } ubyte clz(bool nonZero = false, T)(T x) - if (isIntegral!T) + if (is_integral!T) { static if (nonZero) debug assert(x != 0); @@ -270,7 +272,7 @@ ubyte clz(T : bool)(T x) => x ? 7 : 8; ubyte ctz(bool nonZero = false, T)(T x) - if (isIntegral!T) + if (is_integral!T) { static if (nonZero) debug assert(x != 0); @@ -378,7 +380,7 @@ ubyte ctz(T : bool)(T x) => x ? 0 : 8; ubyte popcnt(T)(T x) - if (isIntegral!T) + if (is_integral!T) { if (__ctfe || !IS_LDC_OR_GDC) { @@ -410,9 +412,9 @@ ubyte popcnt(T)(T x) ubyte popcnt(T : bool)(T x) => x ? 1 : 0; -ubyte byteReverse(ubyte v) +ubyte byte_reverse(ubyte v) => v; -ushort byteReverse(ushort v) +ushort byte_reverse(ushort v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(ushort)((v << 8) | (v >> 8)); @@ -421,7 +423,7 @@ ushort byteReverse(ushort v) else assert(false, "Unreachable"); } -uint byteReverse(uint v) +uint byte_reverse(uint v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(uint)((v << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | (v >> 24)); @@ -430,7 +432,7 @@ uint byteReverse(uint v) else assert(false, "Unreachable"); } -ulong byteReverse(ulong v) +ulong byte_reverse(ulong v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(ulong)((v << 56) | ((v & 0xFF00) << 40) | ((v & 0xFF0000) << 24) | ((v & 0xFF000000) << 8) | ((v >> 8) & 0xFF000000) | ((v >> 24) & 0xFF0000) | ((v >> 40) & 0xFF00) | (v >> 56)); @@ -439,17 +441,17 @@ ulong byteReverse(ulong v) else assert(false, "Unreachable"); } -pragma(inline, true) T byteReverse(T)(T val) - if (!isIntegral!T) +pragma(inline, true) T byte_reverse(T)(T val) + if (!is_integral!T) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); - U r = byteReverse(*cast(U*)&val); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); + U r = byte_reverse(*cast(U*)&val); return *cast(T*)&r; } -T bitReverse(T)(T x) - if (isSomeInt!T) +T bit_reverse(T)(T x) + if (is_some_int!T) { if (__ctfe || !IS_LDC) { @@ -476,7 +478,7 @@ T bitReverse(T)(T x) static if (T.sizeof == 1) return x; else - return byteReverse(x); + return byte_reverse(x); } } else @@ -531,39 +533,39 @@ unittest assert(x.swap(y) == 10); assert(x.swap(30) == 20); - static assert(isPowerOf2(0) == true); - static assert(isPowerOf2(1) == true); - static assert(isPowerOf2(2) == true); - static assert(isPowerOf2(3) == false); - static assert(isPowerOf2(4) == true); - static assert(isPowerOf2(5) == false); - static assert(isPowerOf2(ulong(uint.max) + 1) == true); - static assert(isPowerOf2(ulong.max) == false); - assert(isPowerOf2(0) == true); - assert(isPowerOf2(1) == true); - assert(isPowerOf2(2) == true); - assert(isPowerOf2(3) == false); - assert(isPowerOf2(4) == true); - assert(isPowerOf2(5) == false); - assert(isPowerOf2(ulong(uint.max) + 1) == true); - assert(isPowerOf2(ulong.max) == false); - - static assert(nextPowerOf2(0) == 0); - static assert(nextPowerOf2(1) == 1); - static assert(nextPowerOf2(2) == 2); - static assert(nextPowerOf2(3) == 4); - static assert(nextPowerOf2(4) == 4); - static assert(nextPowerOf2(5) == 8); - static assert(nextPowerOf2(uint.max) == 0); - static assert(nextPowerOf2(ulong(uint.max)) == ulong(uint.max) + 1); - assert(nextPowerOf2(0) == 0); - assert(nextPowerOf2(1) == 1); - assert(nextPowerOf2(2) == 2); - assert(nextPowerOf2(3) == 4); - assert(nextPowerOf2(4) == 4); - assert(nextPowerOf2(5) == 8); - assert(nextPowerOf2(uint.max) == 0); - assert(nextPowerOf2(ulong(uint.max)) == ulong(uint.max) + 1); + static assert(is_power_of_2(0) == true); + static assert(is_power_of_2(1) == true); + static assert(is_power_of_2(2) == true); + static assert(is_power_of_2(3) == false); + static assert(is_power_of_2(4) == true); + static assert(is_power_of_2(5) == false); + static assert(is_power_of_2(ulong(uint.max) + 1) == true); + static assert(is_power_of_2(ulong.max) == false); + assert(is_power_of_2(0) == true); + assert(is_power_of_2(1) == true); + assert(is_power_of_2(2) == true); + assert(is_power_of_2(3) == false); + assert(is_power_of_2(4) == true); + assert(is_power_of_2(5) == false); + assert(is_power_of_2(ulong(uint.max) + 1) == true); + assert(is_power_of_2(ulong.max) == false); + + static assert(next_power_of_2(0) == 0); + static assert(next_power_of_2(1) == 1); + static assert(next_power_of_2(2) == 2); + static assert(next_power_of_2(3) == 4); + static assert(next_power_of_2(4) == 4); + static assert(next_power_of_2(5) == 8); + static assert(next_power_of_2(uint.max) == 0); + static assert(next_power_of_2(ulong(uint.max)) == ulong(uint.max) + 1); + assert(next_power_of_2(0) == 0); + assert(next_power_of_2(1) == 1); + assert(next_power_of_2(2) == 2); + assert(next_power_of_2(3) == 4); + assert(next_power_of_2(4) == 4); + assert(next_power_of_2(5) == 8); + assert(next_power_of_2(uint.max) == 0); + assert(next_power_of_2(ulong(uint.max)) == ulong(uint.max) + 1); static assert(log2(ubyte(0)) == 0); static assert(log2(ubyte(1)) == 0); @@ -677,50 +679,50 @@ unittest assert(popcnt(true) == 1); assert(popcnt('D') == 2); // 0x44 - static assert(bitReverse(ubyte(0)) == 0); - static assert(bitReverse(ubyte(1)) == 128); - static assert(bitReverse(ubyte(2)) == 64); - static assert(bitReverse(ubyte(3)) == 192); - static assert(bitReverse(ubyte(4)) == 32); - static assert(bitReverse(ubyte(5)) == 160); - static assert(bitReverse(ubyte(6)) == 96); - static assert(bitReverse(ubyte(7)) == 224); - static assert(bitReverse(ubyte(8)) == 16); - static assert(bitReverse(ubyte(255)) == 255); - static assert(bitReverse(ushort(0b1101100010000000)) == 0b0000000100011011); - static assert(bitReverse(uint(0x73810000)) == 0x000081CE); - static assert(bitReverse(ulong(0x7381000000000000)) == 0x00000000000081CE); - assert(bitReverse(ubyte(0)) == 0); - assert(bitReverse(ubyte(1)) == 128); - assert(bitReverse(ubyte(2)) == 64); - assert(bitReverse(ubyte(3)) == 192); - assert(bitReverse(ubyte(4)) == 32); - assert(bitReverse(ubyte(5)) == 160); - assert(bitReverse(ubyte(6)) == 96); - assert(bitReverse(ubyte(7)) == 224); - assert(bitReverse(ubyte(8)) == 16); - assert(bitReverse(ubyte(255)) == 255); - assert(bitReverse(ushort(0b1101100010000000)) == 0b0000000100011011); - assert(bitReverse(uint(0x73810000)) == 0x000081CE); - assert(bitReverse(ulong(0x7381000000000000)) == 0x00000000000081CE); - - static assert(byteReverse(0x12) == 0x12); - static assert(byteReverse(0x1234) == 0x3412); - static assert(byteReverse(0x12345678) == 0x78563412); - static assert(byteReverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); - static assert(byteReverse(true) == true); - static assert(byteReverse(char(0x12)) == char(0x12)); - static assert(byteReverse(wchar(0x1234)) == wchar(0x3412)); - static assert(byteReverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); - assert(byteReverse(0x12) == 0x12); - assert(byteReverse(0x1234) == 0x3412); - assert(byteReverse(0x12345678) == 0x78563412); - assert(byteReverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); - assert(byteReverse(true) == true); - assert(byteReverse(char(0x12)) == char(0x12)); - assert(byteReverse(wchar(0x1234)) == wchar(0x3412)); - assert(byteReverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); + static assert(bit_reverse(ubyte(0)) == 0); + static assert(bit_reverse(ubyte(1)) == 128); + static assert(bit_reverse(ubyte(2)) == 64); + static assert(bit_reverse(ubyte(3)) == 192); + static assert(bit_reverse(ubyte(4)) == 32); + static assert(bit_reverse(ubyte(5)) == 160); + static assert(bit_reverse(ubyte(6)) == 96); + static assert(bit_reverse(ubyte(7)) == 224); + static assert(bit_reverse(ubyte(8)) == 16); + static assert(bit_reverse(ubyte(255)) == 255); + static assert(bit_reverse(ushort(0b1101100010000000)) == 0b0000000100011011); + static assert(bit_reverse(uint(0x73810000)) == 0x000081CE); + static assert(bit_reverse(ulong(0x7381000000000000)) == 0x00000000000081CE); + assert(bit_reverse(ubyte(0)) == 0); + assert(bit_reverse(ubyte(1)) == 128); + assert(bit_reverse(ubyte(2)) == 64); + assert(bit_reverse(ubyte(3)) == 192); + assert(bit_reverse(ubyte(4)) == 32); + assert(bit_reverse(ubyte(5)) == 160); + assert(bit_reverse(ubyte(6)) == 96); + assert(bit_reverse(ubyte(7)) == 224); + assert(bit_reverse(ubyte(8)) == 16); + assert(bit_reverse(ubyte(255)) == 255); + assert(bit_reverse(ushort(0b1101100010000000)) == 0b0000000100011011); + assert(bit_reverse(uint(0x73810000)) == 0x000081CE); + assert(bit_reverse(ulong(0x7381000000000000)) == 0x00000000000081CE); + + static assert(byte_reverse(0x12) == 0x12); + static assert(byte_reverse(0x1234) == 0x3412); + static assert(byte_reverse(0x12345678) == 0x78563412); + static assert(byte_reverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); + static assert(byte_reverse(true) == true); + static assert(byte_reverse(char(0x12)) == char(0x12)); + static assert(byte_reverse(wchar(0x1234)) == wchar(0x3412)); + static assert(byte_reverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); + assert(byte_reverse(0x12) == 0x12); + assert(byte_reverse(0x1234) == 0x3412); + assert(byte_reverse(0x12345678) == 0x78563412); + assert(byte_reverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); + assert(byte_reverse(true) == true); + assert(byte_reverse(char(0x12)) == char(0x12)); + assert(byte_reverse(wchar(0x1234)) == wchar(0x3412)); + assert(byte_reverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); float frev; *cast(uint*)&frev = 0x0000803F; - assert(byteReverse(1.0f) is frev); + assert(byte_reverse(1.0f) is frev); } diff --git a/src/urt/variant.d b/src/urt/variant.d index 9f5fe2c..033f072 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1,22 +1,28 @@ module urt.variant; +import urt.algorithm : compare; import urt.array; +import urt.conv; import urt.kvp; import urt.lifetime; import urt.map; +import urt.mem.allocator; import urt.si.quantity; -import urt.si.unit : ScaledUnit; +import urt.si.unit : ScaledUnit, Second, Nanosecond; +import urt.time : Duration, dur; import urt.traits; nothrow @nogc: enum ValidUserType(T) = (is(T == struct) || is(T == class)) && + is(Unqual!T == T) && !is(T == Variant) && !is(T == VariantKVP) && !is(T == Array!U, U) && !is(T : const(char)[]) && - !is(T == Quantity!(T, U), T, alias U); + !is(T == Quantity!(T, U), T, alias U) && + !is(T == Duration); alias VariantKVP = KVP!(const(char)[], Variant); @@ -65,80 +71,112 @@ nothrow @nogc: } this(I)(I i) - if (is(I == byte) || is(I == short)) + if (is_some_int!I) { - flags = Flags.NumberInt; - value.l = i; - if (i >= 0) - flags |= Flags.UintFlag | Flags.Uint64Flag; - } - this(I)(I i) - if (is(I == ubyte) || is(I == ushort)) - { - flags = cast(Flags)(Flags.NumberUint | Flags.IntFlag); - value.ul = i; - } - - this(int i) - { - flags = Flags.NumberInt; - value.l = i; - if (i >= 0) - flags |= Flags.UintFlag | Flags.Uint64Flag; - } - - this(uint i) - { - flags = Flags.NumberUint; - value.ul = i; - if (i <= int.max) - flags |= Flags.IntFlag; - } + static if (is_signed_int!I) + value.l = i; + else + value.ul = i; - this(long i) - { - flags = Flags.NumberInt64; - value.l = i; - if (i >= 0) + static if (is(I == ubyte) || is(I == ushort)) + flags = cast(Flags)(Flags.NumberUint | Flags.IntFlag | Flags.Int64Flag); + else static if (is(I == byte) || is(I == short) || is(I == int)) + { + flags = Flags.NumberInt; + if (i >= 0) + flags |= Flags.UintFlag | Flags.Uint64Flag; + } + else static if (is(I == uint)) + { + flags = Flags.NumberUint; + if (i <= int.max) + flags |= Flags.IntFlag; + } + else static if (is(I == long)) + { + flags = Flags.NumberInt64; + if (i >= 0) + { + flags |= Flags.Uint64Flag; + if (i <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag; + else if (i <= uint.max) + flags |= Flags.UintFlag; + } + else if (i >= int.min) + flags |= Flags.IntFlag; + } + else static if (is(I == ulong)) { - flags |= Flags.Uint64Flag; + flags = Flags.NumberUint64; if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag; + flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; else if (i <= uint.max) - flags |= Flags.UintFlag; + flags |= Flags.UintFlag | Flags.Int64Flag; + else if (i <= long.max) + flags |= Flags.Int64Flag; } - else if (i >= int.min) - flags |= Flags.IntFlag; } - this(ulong i) + this(F)(F f) + if (is_some_float!F) { - flags = Flags.NumberUint64; - value.ul = i; - if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; - else if (i <= uint.max) - flags |= Flags.UintFlag | Flags.Int64Flag; - else if (i <= long.max) - flags |= Flags.Int64Flag; - } + import urt.math : float_is_integer; - this(float f) - { - flags = Flags.NumberFloat; - value.d = f; + if (int sign = float_is_integer(f, value.ul)) + { + if (sign < 0) + { + flags = Flags.NumberInt64; + if (value.l >= int.min) + flags |= Flags.IntFlag; + } + else + { + flags = Flags.NumberUint64; + if (value.ul <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; + else if (value.ul <= uint.max) + flags |= Flags.UintFlag | Flags.Int64Flag; + else if (value.ul <= long.max) + flags |= Flags.Int64Flag; + } + } + else + { + static if (is(F == float)) + flags = Flags.NumberFloat; + else + flags = Flags.NumberDouble; + value.d = f; + } } - this(double d) + + this(E)(E e) + if (is(E == enum)) { - flags = Flags.NumberDouble; - value.d = d; + static if (is(E T == enum)) + { + this(T(e)); + // TODO: do we keep a record of the enum keys for stringification? + } } this(U, ScaledUnit _U)(Quantity!(U, _U) q) { this(q.value); + if (q.unit.pack) + { + flags |= Flags.IsQuantity; + count = q.unit.pack; + } + } + + this(Duration dur) + { + this(dur.as!"nsecs"); flags |= Flags.IsQuantity; - count = q.unit.pack; + count = Nanosecond.pack; } this(const(char)[] s) // TODO: (S)(S s) @@ -156,13 +194,29 @@ nothrow @nogc: count = cast(uint)s.length; } + this(T)(T[] buffer) + if (is(Unqual!T == void)) + { + if (buffer.length < embed.length) + { + flags = Flags.ShortBuffer; + embed[0 .. buffer.length] = cast(char[])buffer[]; + embed[$-1] = cast(ubyte)buffer.length; + return; + } + flags = Flags.Buffer; + value.s = cast(char*)buffer.ptr; + count = cast(uint)buffer.length; + } + this(Variant[] a) { flags = Flags.Array; nodeArray = a[]; } + this(T)(T[] a) - if (!is(T == Variant) && !is(T == VariantKVP) && !is(T : dchar)) + if (!is(T == Variant) && !is(T == VariantKVP) && !is(T : dchar) && !is(Unqual!T == void)) { flags = Flags.Array; nodeArray.reserve(a.length); @@ -209,19 +263,29 @@ nothrow @nogc: static if (is(T == class)) { count = UserTypeId!T; - value.p = cast(void*)&thing; + alloc = type_detail_index!T(); + ptr = cast(void*)thing; } else static if (EmbedUserType!T) { + alloc = UserTypeId!T; flags |= Flags.Embedded; - alloc = UserTypeShortId!T; + + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + emplace(cast(T*)embed.ptr, forward!thing); } else { -// flags |= Flags.NeedDestruction; // if T has a destructor... count = UserTypeId!T; - assert(false, "TODO: alloc for the object..."); + alloc = type_detail_index!T(); + + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + + ptr = defaultAllocator().alloc(T.sizeof, T.alignof).ptr; + emplace(cast(T*)ptr, forward!thing); } } @@ -230,12 +294,26 @@ nothrow @nogc: destroy!false(); } + void opAssign(ref Variant value) + { + if (&this is &value) + return; // TODO: should this be an assert instead of a graceful handler? + destroy!false(); + new(this) Variant(value); + } + version (EnableMoveSemantics) { + void opAssign(Variant value) + { + destroy!false(); + new(this) Variant(__rvalue(value)); // TODO: value.move + } + } + // TODO: since this is a catch-all, the error messages will be a bit shit // maybe we can find a way to constrain it to valid inputs? void opAssign(T)(auto ref T value) { - destroy!false(); - emplace(&this, forward!value); + this = Variant(value); } // TODO: do we want Variant to support +=, ~=, etc...? @@ -266,6 +344,230 @@ nothrow @nogc: return nodeArray.pushBack(); } + bool opEquals(ref const Variant rhs) const pure + { + return opCmp(rhs) == 0; + } + bool opEquals(T)(auto ref const T rhs) const + if (!is(T == Variant)) + { + // TODO: handle short-cut array/map comparisons? + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()); + else static if (is(T == bool)) + { + if (!isBool) + return false; // do non-zero numbers evaluate true? what about non-zero strings? etc... + return asBool == rhs; + } + else static if (is_some_int!T || is_some_float!T) + { + if (!isNumber) + return false; + static if (is_some_int!T) if (!canFitInt!T) + return false; + if (isQuantity) + return asQuantity!double() == Quantity!T(rhs); + return as!T == rhs; + } + else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + if (!isNumber) + return false; + return asQuantity!double() == rhs; + } + else static if (is(T E == enum)) + { + // TODO: should we also do string key comparisons? + return opEquals(cast(E)rhs); + } + else static if (is(T : const(char)[])) + return isString && asString() == rhs[]; + else static if (ValidUserType!T) + return asUser!T == rhs; + else + static assert(false, "TODO: variant comparison with '", T.stringof, "' not supported"); + } + + int opCmp(ref const Variant rhs) const pure + { + const(Variant)* a, b; + bool invert = false; + if (this.type <= rhs.type) + a = &this, b = &rhs; + else + a = &rhs, b = &this, invert = true; + + int r = 0; + final switch (a.type) + { + case Type.Null: + if (b.type == Type.Null) + return 0; + else if ((b.type == Type.Buffer || b.type == Type.Array || b.type == Type.Map) && b.empty()) + return 0; + r = -1; // sort null before other things... + break; + + case Type.True: + case Type.False: + // if both sides are bool + if (b.type <= Type.False) + { + r = a.asBool - b.asBool; + break; + } + // TODO: maybe we don't want to accept bool/number comparison? + goto case; // we will compare bools with numbers... + + case Type.Number: + if (b.type <= Type.Number) + { + static double asDoubleWithBool(ref const Variant v) + => v.isBool() ? double(v.asBool()) : v.asDouble(); + + uint aunit = a.isQuantity ? a.count : 0; + uint bunit = b.isQuantity ? b.count : 0; + if (aunit || bunit) + { + // we can't compare different units + if ((aunit & 0xFFFFFF) != (bunit & 0xFFFFFF)) + { + r = (aunit & 0xFFFFFF) - (bunit & 0xFFFFFF); + break; + } + + // matching units, but we'll only do quantity comparison if there is some scaling + if ((aunit >> 24) != (bunit >> 24)) + { + Quantity!double aq = a.isQuantity ? a.asQuantity!double() : Quantity!double(asDoubleWithBool(*a)); + Quantity!double bq = b.isQuantity ? b.asQuantity!double() : Quantity!double(asDoubleWithBool(*b)); + r = aq.opCmp(bq); + break; + } + } + + if (a.flags & Flags.DoubleFlag || b.flags & Flags.DoubleFlag) + { + // float comparison + // TODO: determine if float/bool comparison seems right? is: -1 < false < 0.9 < true < 1.1? + double af = asDoubleWithBool(*a); + double bf = asDoubleWithBool(*b); + r = af < bf ? -1 : af > bf ? 1 : 0; + break; + } + + // TODO: this could be further optimised by comparing the value range flags... + if ((a.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + ulong aul = a.asUlong(); + if ((b.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + ulong bul = b.asUlong(); + r = aul < bul ? -1 : aul > bul ? 1 : 0; + } + else + r = 1; // a is in ulong range, rhs is not; a is larger... + break; + } + if ((b.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + r = -1; // b is in ulong range, lhs is not; b is larger... + break; + } + + long al = a.isBool() ? a.asBool() : a.asLong(); + long bl = b.isBool() ? b.asBool() : b.asLong(); + r = al < bl ? -1 : al > bl ? 1 : 0; + } + else + r = -1; // sort numbers before other things... + break; + + case Type.Buffer: + if (b.type != Type.Buffer) + { + r = -1; + break; + } + r = compare(cast(const(char)[])a.asBuffer(), cast(const(char)[])b.asBuffer()); + break; + + case Type.Array: + if (b.type != Type.Array) + { + r = -1; + break; + } + r = compare(a.asArray()[], b.asArray()[]); + break; + + case Type.Map: + if (b.type != Type.Map) + { + r = -1; + break; + } + assert(false, "TODO"); + break; + + case Type.User: + uint at = a.userType; + uint bt = b.userType; + if (at != bt) + { + r = at < bt ? -1 : at > bt ? 1 : 0; + break; + } + alias PureHack = ref TypeDetails function(uint index) pure nothrow @nogc; + if (flags & Flags.Embedded) + { + ref const TypeDetails td = (cast(PureHack)&find_type_details)(alloc); + r = td.cmp(a.embed.ptr, b.embed.ptr, 0); + } + else + { + ref const TypeDetails td = (cast(PureHack)&get_type_details)(alloc); + r = td.cmp(a.ptr, b.ptr, 0); + } + break; + } + return invert ? -r : r; + } + int opCmp(T)(auto ref const T rhs) const + if (!is(T == Variant)) + { + // TODO: handle short-cut string, array, map comparisons + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()) ? 0 : 1; + else static if (is(T : const(char)[])) + return isString() ? compare(asString(), rhs) : (type < Type.String ? -1 : 1); + static if (ValidUserType!T) + return compare(asUser!T, rhs); + else + return opCmp(Variant(rhs)); + } + + bool opBinary(string op)(ref const Variant rhs) const pure + if (op == "is") + { + // compare that Variant's are identical, not just equivalent! + assert(false, "TODO"); + } + bool opBinary(string op, T)(auto ref const T rhs) const + if (op == "is") + { + // TODO: handle short-cut array/map comparisons? + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()); + else static if (is(T : const(char)[])) + return isString && asString().ptr is rhs.ptr && length() == rhs.length; + else static if (ValidUserType!T) + return asUser!T is rhs; + else + return opBinary!"is"(Variant(rhs)); + } + bool isNull() const pure => flags == Flags.Null; bool isFalse() const pure @@ -290,54 +592,110 @@ nothrow @nogc: => (flags & Flags.DoubleFlag) != 0; bool isQuantity() const pure => (flags & Flags.IsQuantity) != 0; + bool isDuration() const pure + => isQuantity && (count & 0xFFFFFF) == Second.pack; + bool isBuffer() const pure + => type == Type.Buffer; bool isString() const pure => (flags & Flags.IsString) != 0; bool isArray() const pure => flags == Flags.Array; bool isObject() const pure => flags == Flags.Map; + bool isUserType() const pure + => (flags & Flags.TypeMask) == Type.User; bool isUser(T)() const pure - if (ValidUserType!T) + if (ValidUserType!(Unqual!T)) { + alias U = Unqual!T; if ((flags & Flags.TypeMask) != Type.User) return false; - static if (EmbedUserType!T) - return alloc == UserTypeShortId!T; + static if (EmbedUserType!U) + return alloc == UserTypeId!U; else - return count == UserTypeId!T; + { + if (count == UserTypeId!U) + return true; + static if (is(T == class)) + { + immutable(TypeDetails)* td = &get_type_details(alloc); + while (td.super_type_id) + { + if (td.super_type_id == UserTypeId!U) + return true; + td = &find_type_details(td.super_type_id); + } + } + return false; + } } + bool canFitInt(I)() const pure + if (is_some_int!I) + { + if (!isNumber || isFloat) + return false; + static if (is(I == ulong)) + return isUlong; + else static if (is(I == long)) + return isLong; + else static if (is(I == uint)) + return isUint; + else static if (is(I == int)) + return isInt; + else static if (is_signed_int!I) + { + if (!isInt) + return false; + int i = asInt(); + return i >= I.min && i <= I.max; + } + else + return isUlong && asUlong <= I.max; + } bool asBool() const pure @property { + if (isNull) + return false; assert(isBool()); return flags == Flags.True; } int asInt() const pure @property { - assert(isInt()); + if (isNull) + return 0; + assert(isInt(), "Value out of range for int"); return cast(int)value.l; } uint asUint() const pure @property { - assert(isUint()); + if (isNull) + return 0; + assert(isUint(), "Value out of range for uint"); return cast(uint)value.ul; } long asLong() const pure @property { - assert(isLong()); + if (isNull) + return 0; + assert(isLong(), "Value out of range for long"); return value.l; } ulong asUlong() const pure @property { - assert(isUlong()); + if (isNull) + return 0; + assert(isUlong(), "Value out of range for ulong"); return value.ul; } double asDouble() const pure @property { - assert(isNumber()); + if (isNull) + return 0; + assert(isNumber); if ((flags & Flags.DoubleFlag) != 0) return value.d; if ((flags & Flags.UintFlag) != 0) @@ -349,33 +707,79 @@ nothrow @nogc: return cast(double)cast(long)value.ul; } - Quantity!T asQuantity(T = double)() const pure @property + float asFloat() const pure @property { - assert(isNumber()); + if (isNull) + return 0; + assert(isNumber); + if ((flags & Flags.DoubleFlag) != 0) + return value.d; + if ((flags & Flags.UintFlag) != 0) + return cast(float)cast(uint)value.ul; + if ((flags & Flags.IntFlag) != 0) + return cast(float)cast(int)cast(long)value.ul; + if ((flags & Flags.Uint64Flag) != 0) + return cast(float)value.ul; + return cast(float)cast(long)value.ul; + } - Quantity!double r; - static if (is(T == double)) - r.value = asDouble(); -// else static if (is(T == float)) -// r.value = asFloat(); - else static if (is(T == int)) - r.value = asInt(); - else static if (is(T == uint)) - r.value = asUint(); - else static if (is(T == long)) - r.value = asLong(); - else static if (is(T == ulong)) - r.value = asUlong(); - else - assert(false, "Unsupported quantity type!"); - if (isQuantity()) + Quantity!T asQuantity(T = double)() const pure @property + if (is_some_float!T || isSomeInt!T) + { + if (isNull) + return Quantity!T(0); + assert(isNumber); + Quantity!T r; + r.value = as!T; + if (isQuantity) r.unit.pack = count; return r; } + Duration asDuration() const pure @property + { + assert(isDuration); + alias Nanoseconds = Quantity!(long, Nanosecond); + Nanoseconds ns; + if (size_t.sizeof < 8 && isFloat) // TODO: better way to detect if double is NOT supported in hardware? + { + Quantity!float q; + q.value = asFloat; + q.unit.pack = count; + ns = cast(Nanoseconds)q; + } + else if (isDouble) + { + Quantity!double q; + q.value = asDouble; + q.unit.pack = count; + ns = cast(Nanoseconds)q; + } + else + { + Quantity!long q; + q.value = asLong; + q.unit.pack = count; + ns = q; + } + return ns.value.dur!"nsecs"; + } + + const(void)[] asBuffer() const pure + { + if (isNull) + return null; + assert(isBuffer); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]]; + return value.s[0 .. count]; + } + const(char)[] asString() const pure { - assert(isString()); + if (isNull) + return null; + assert(isString); if (flags & Flags.Embedded) return embed[0 .. embed[$-1]]; return value.s[0 .. count]; @@ -397,29 +801,97 @@ nothrow @nogc: } ref inout(T) asUser(T)() inout pure - if (ValidUserType!T && UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) { - if (!isUser!T) - assert(false, "Variant is not a " ~ T.stringof); - static assert(!is(T == class), "Should be impossible?"); - static if (EmbedUserType!T) + alias U = Unqual!T; + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static assert(!is(U == class), "Should be impossible?"); + static if (EmbedUserType!U) return *cast(inout(T)*)embed.ptr; else return *cast(inout(T)*)ptr; } inout(T) asUser(T)() inout pure - if (ValidUserType!T && !UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && !UserTypeReturnByRef!T) { - if (!isUser!T) - assert(false, "Variant is not a " ~ T.stringof); - static if (is(T == class)) + alias U = Unqual!T; + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static if (is(U == class)) return cast(inout(T))ptr; - else static if (EmbedUserType!T) - static assert(false, "TODO: memcpy to a stack local and return that..."); + else static if (EmbedUserType!U) + { + // make a copy on the stack and return by value + U r = void; + TypeDetailsFor!U.copy_emplace(embed.ptr, &r, false); + return r; + } else static assert(false, "Should be impossible?"); } + auto as(T)() inout pure + if (!ValidUserType!(Unqual!T) || !UserTypeReturnByRef!T) + { + static if (is_some_int!T) + { + static if (is_signed_int!T) + { + static if (is(T == long)) + return asLong(); + else + { + int i = asInt(); + static if (!is(T == int)) + assert(i >= T.min && i <= T.max, "Value out of range for " ~ T.stringof); + return cast(T)i; + } + } + else + { + static if (is(T == ulong)) + return asUlong(); + else + { + uint u = asUint(); + static if (!is(T == uint)) + assert(u <= T.max, "Value out of range for " ~ T.stringof); + return cast(T)u; + } + } + } + else static if (is_some_float!T) + { + static if (is(T == float)) + return asFloat(); + else + return asDouble(); + } + else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + return asQuantity!U(); + } + else static if (is(T == Duration)) + { + return asDuration; + } + else static if (is(T : const(char)[])) + { + static if (is(T == struct)) // for String/MutableString/etc + return T(asString); // TODO: error? shouldn't this NRVO?! + else + return asString; + } + else static if (ValidUserType!(Unqual!T)) + return asUser!T; + else + static assert(false, "TODO!"); + } + ref inout(T) as(T)() inout pure + if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) + => asUser!T; + size_t length() const pure { if (flags == Flags.Null) @@ -429,7 +901,7 @@ nothrow @nogc: else if (isArray()) return count; else - assert(false); + assert(false, "Variant does not have `length`"); } bool empty() const pure @@ -446,6 +918,16 @@ nothrow @nogc: return null; } + void set_unit(ScaledUnit unit) + { + assert(isNumber()); + count = unit.pack; + if (count != 0) + flags |= Flags.IsQuantity; + else + flags &= ~Flags.IsQuantity; + } + // TODO: this seems to interfere with UFCS a lot... // ref inout(Variant) opDispatch(string member)() inout pure // { @@ -479,73 +961,164 @@ nothrow @nogc: { final switch (type) { - case Variant.Type.Null: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - - case Variant.Type.False: - if (!buffer.ptr) - return 5; - if (buffer.length < 5) - return -1; - buffer[0 .. 5] = "false"; - return 5; - - case Variant.Type.True: + case Variant.Type.Null: // assume type == 0 + case Variant.Type.True: // assume type == 1 + case Variant.Type.False: // assume type == 2 + __gshared immutable char** values = [ "null", "true", "false" ]; + size_t len = 4 + (type >> 1); if (!buffer.ptr) - return 4; - if (buffer.length < 4) + return len; + if (buffer.length < len) return -1; - buffer[0 .. 4] = "true"; - return 4; + buffer[0 .. len] = values[type][0 .. len]; + return len; case Variant.Type.Number: - import urt.conv; - if (isQuantity()) - assert(false, "TODO: implement quantity formatting for JSON"); + return asQuantity().toString(buffer, format, formatArgs); if (isDouble()) - return asDouble().formatFloat(buffer); + return asDouble().format_float(buffer); // TODO: parse args? - //format + assert(!format, "TODO"); if (flags & Flags.Uint64Flag) - return asUlong().formatUint(buffer); - return asLong().formatInt(buffer); + return asUlong().format_uint(buffer); + return asLong().format_int(buffer); - case Variant.Type.String: - const char[] s = asString(); - if (buffer.ptr) + case Variant.Type.Buffer: + if (isString) + { + const char[] s = asString(); + if (buffer.ptr) + { + // TODO: should we write out quotes? + // a string of a number won't be distinguishable from a number without quotes... + if (buffer.length < s.length) + return -1; + buffer[0 .. s.length] = s[]; + } + return s.length; + } + else { - // TODO: should we write out quotes? - // a string of a number won't be distinguishable from a number without quotes... - if (buffer.length < s.length) - return -1; - buffer[0 .. s.length] = s[]; + import urt.string.format : formatValue; + return formatValue(asBuffer(), buffer, format, formatArgs); } - return s.length; case Variant.Type.Map: case Variant.Type.Array: import urt.format.json; // should we just format this like JSON or something? - return writeJson(this, buffer); + return write_json(this, buffer); case Variant.Type.User: if (flags & Flags.Embedded) - return findTypeDetails(alloc).stringify(embed.ptr, buffer); + return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true, format, formatArgs); else - return findTypeDetails(count).stringify(ptr, buffer); + return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true, format, formatArgs); } } + ptrdiff_t fromString(const(char)[] s) + { + import urt.string.ascii : is_numeric; + + if (s.empty || s == "null") + { + this = null; + return s.length; + } + if (s == "true") + { + this = true; + return 4; + } + if (s == "false") + { + this = false; + return 5; + } + + if (s[0] == '"') + { + for (size_t i = 1; i < s.length; ++i) + { + if (s[i] == '"') + { + assert(i == s.length - 1, "String must end with a quote"); + this = s[1 .. i]; + return i + 1; + } + } + assert(false, "String has no closing quote"); + } + + if (s[0].is_numeric) + { + size_t taken; + ScaledUnit unit; + int e; + long i = s.parse_int_with_exponent(e, &taken, 10); + if (taken < s.length) + { + size_t t2 = unit.fromString(s[taken .. $]); + if (t2 > 0) + taken += t2; + } + if (taken == s.length) + { + if (e != 0) + this = i * 10.0^^e; + else + this = i; + if (unit.pack) + { + flags |= Flags.IsQuantity; + count = unit.pack; + } + return taken; + } + } + + align(64) void[256] buffer = void; + this = null; // clear the object since we'll probably use the embed buffer... + foreach (ushort i; 0 .. g_num_type_details) + { + ref immutable TypeDetails td = get_type_details(i); + debug assert(td.alignment <= 64 && td.size <= buffer.sizeof, "Buffer is too small for user type!"); + ptrdiff_t taken = td.stringify(td.embedded ? embed.ptr : buffer.ptr, cast(char[])s, false, null, null); + if (taken > 0) + { + flags = Flags.User; + if (td.destroy) + flags |= Flags.NeedDestruction; + if (td.embedded) + { + flags |= Flags.Embedded; + alloc = cast(ushort)td.type_id; + } + else + { + void* object = defaultAllocator().alloc(td.size, td.alignment).ptr; + td.copy_emplace(buffer.ptr, object, true); + if (td.destroy) + td.destroy(buffer.ptr); + ptr = object; + count = td.type_id; + alloc = i; + } + return taken; + } + } + + // what is this? + assert(false, "Can't parse variant from string"); + } + + package: union Value { @@ -586,6 +1159,19 @@ package: Type type() const pure => cast(Type)(flags & Flags.TypeMask); + uint userType() const pure + { + if (flags & Flags.Embedded) + return alloc; // short id + return count; // long id + } + inout(void)* userPtr() inout pure + { + if (flags & Flags.Embedded) + return embed.ptr; + return ptr; + } + ref inout(Array!Variant) nodeArray() @property inout pure => *cast(inout(Array!Variant)*)&value.n; void takeNodeArray(ref Array!Variant arr) @@ -611,20 +1197,21 @@ package: nodeArray.destroy!false(); else if (t == Type.User) { - if (flags & Flags.Embedded) - findTypeDetails(alloc).destroy(embed.ptr); - else - findTypeDetails(count).destroy(ptr); + ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : g_type_details[alloc]; + if (td.destroy) + td.destroy(userPtr); + if (!(flags & Flags.Embedded)) + defaultAllocator().free(ptr[0..td.size]); } } enum Type : ushort { Null = 0, - False = 1, - True = 2, + True = 1, + False = 2, Number = 3, - String = 4, + Buffer = 4, Array = 5, Map = 6, User = 7 @@ -641,8 +1228,10 @@ package: NumberUint64 = cast(Flags)Type.Number | Flags.IsNumber | Flags.Uint64Flag, NumberFloat = cast(Flags)Type.Number | Flags.IsNumber | Flags.FloatFlag | Flags.DoubleFlag, NumberDouble = cast(Flags)Type.Number | Flags.IsNumber | Flags.DoubleFlag, - String = cast(Flags)Type.String | Flags.IsString, - ShortString = cast(Flags)Type.String | Flags.IsString | Flags.Embedded, + Buffer = cast(Flags)Type.Buffer, + ShortBuffer = cast(Flags)Type.Buffer | Flags.Embedded, + String = cast(Flags)Type.Buffer | Flags.IsString, + ShortString = cast(Flags)Type.Buffer | Flags.IsString | Flags.Embedded, Array = cast(Flags)Type.Array | Flags.NeedDestruction, Map = cast(Flags)Type.Map | Flags.NeedDestruction, User = cast(Flags)Type.User, @@ -691,13 +1280,20 @@ unittest private: -import urt.hash : fnv1aHash; +import urt.hash : fnv1a; +import urt.string.format : formatValue, FormatArg; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); -enum uint UserTypeId(T) = fnv1aHash(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? -enum uint UserTypeShortId(T) = cast(ushort)UserTypeId!T ^ (UserTypeId!T >> 16); +template UserTypeId(T) +{ + enum uint Hash = fnv1a(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? + static if (!EmbedUserType!T) + enum uint UserTypeId = Hash; + else + enum ushort UserTypeId = cast(ushort)Hash ^ (Hash >> 16); +} enum bool EmbedUserType(T) = is(T == struct) && T.sizeof <= Variant.embed.sizeof - 2 && T.alignof <= Variant.alignof; enum bool UserTypeReturnByRef(T) = is(T == struct); @@ -713,46 +1309,176 @@ ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) template MakeTypeDetails(T) { + static assert(is(Unqual!T == T), "Only instantiate for mutable types"); + // this is a hack which populates an array of user type details when the program starts // TODO: we can probably NOT do this for class types, and just use RTTI instead... shared static this() { - assert(numTypeDetails < typeDetails.length, "Too many user types!"); - - TypeDetails* ty = &typeDetails[numTypeDetails++]; - static if (EmbedUserType!T) - ty.typeId = UserTypeShortId!T; - else - ty.typeId = UserTypeId!T; - // TODO: I'd like to not generate a destroy function if the data is POD - ty.destroy = (void* val) { - static if (!is(T == class)) - destroy!false(*cast(T*)val); - }; - ty.stringify = (const void* val, char[] buffer) { - import urt.string.format : toString; - return toString(*cast(T*)val, buffer); - }; + assert(g_num_type_details < g_type_details.length, "Too many user types!"); + g_type_details[g_num_type_details++] = TypeDetailsFor!T; } alias MakeTypeDetails = void; } +ushort type_detail_index(T)() pure + if (ValidUserType!T) +{ + ushort count = (cast(ushort function() pure nothrow @nogc)&num_type_details)(); + foreach (ushort i; 0 .. count) + if (get_type_details(i).type_id == UserTypeId!T) + return i; + assert(false, "Why wasn't the type registered?"); +} + struct TypeDetails { - uint typeId; + uint type_id; + uint super_type_id; + ushort size; + ubyte alignment; + bool embedded; + void function(void* src, void* dst, bool move) nothrow @nogc copy_emplace; void function(void* val) nothrow @nogc destroy; - ptrdiff_t function(const void* val, char[] buffer) nothrow @nogc stringify; + ptrdiff_t function(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc stringify; + int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } -TypeDetails[8] typeDetails; -size_t numTypeDetails = 0; +__gshared TypeDetails[8] g_type_details; +__gshared ushort g_num_type_details = 0; + +typeof(g_type_details)* type_details() => &g_type_details; +ushort num_type_details() => g_num_type_details; -ref TypeDetails findTypeDetails(uint typeId) +ref immutable(TypeDetails) find_type_details(uint type_id) pure { - foreach (i, ref td; typeDetails[0 .. numTypeDetails]) + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + ushort count = (cast(ushort function() pure nothrow @nogc)&num_type_details)(); + foreach (i, ref td; (*tds)[0 .. count]) { - if (td.typeId == typeId) + if (td.type_id == type_id) return td; } assert(false, "TypeDetails not found!"); } +ref immutable(TypeDetails) get_type_details(uint index) pure +{ + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + debug assert(index < g_num_type_details); + return (*tds)[index]; +} + +public template TypeDetailsFor(T) + if (is(Unqual!T == T) && (is(T == struct) || is(T == class))) +{ + static if (is(T == class) && is(T S == super)) + { + alias Super = Unqual!S; + static if (!is(Super == Object)) + { + alias dummy = MakeTypeDetails!Super; + enum SuperTypeId = UserTypeId!Super; + } + else + enum ushort SuperTypeId = 0; + } + else + enum ushort SuperTypeId = 0; + + static if (!is(T == class)) + { + static void move_emplace_impl(void* src, void* dst, bool move) nothrow @nogc + { + if (move) + moveEmplace(*cast(T*)src, *cast(T*)dst); + else + { + static if (__traits(compiles, { *cast(T*)dst = *cast(const T*)src; })) + *cast(T*)dst = *cast(const T*)src; + else + assert(false, "Can't copy " ~ T.stringof); + } + } + enum move_emplace = &move_emplace_impl; + } + else + enum move_emplace = null; + + static if (!is_trivial!T && is(typeof(destroy!(false, T)))) + { + static void destroy_impl(void* val) nothrow @nogc + { + destroy!false(*cast(T*)val); + } + enum destroy_fun = &destroy_impl; + } + else + enum destroy_fun = null; + + static ptrdiff_t stringify(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc + { + if (do_format) + { + static if (__traits(compiles, { formatValue(*cast(const T*)val, buffer, format_spec, format_args); })) + return formatValue(*cast(const T*)val, buffer, format_spec, format_args); + else + return -1; + } + else + { + static if (is(typeof(parse!T))) + return buffer.parse!T(*cast(T*)val); + else + return -1; + } + } + + int compare(const void* pa, const void* pb, int type) pure nothrow @nogc + { + ref const T a = *cast(const T*)pa; + ref const T b = *cast(const T*)pb; + switch (type) + { + case 0: + static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) + { + if (pa is pb) + return 0; + } + static if (__traits(compiles, { a.opCmp(b); })) + return a.opCmp(b); + else static if (__traits(compiles, { b.opCmp(a); })) + return -b.opCmp(a); + else + { + static if (is(T == class)) + { + ptrdiff_t r = cast(ptrdiff_t)pa - cast(ptrdiff_t)pb; + return r < 0 ? -1 : r > 0 ? 1 : 0; + } + else + return a < b ? -1 : a > b ? 1 : 0; + } + case 1: + static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) + { + if (pa is pb) + return 1; + } + static if (__traits(compiles, { a.opEquals(b); })) + return a.opEquals(b); + else static if (__traits(compiles, { b.opEquals(a); })) + return b.opEquals(a); + else static if (!is(T == class) && !is(T == U*, U) && !is(T == V[], V)) + return a == b ? 1 : 0; + else + return 0; + case 2: + return pa is pb ? 1 : 0; + default: + assert(false); + } + } + + enum TypeDetailsFor = TypeDetails(UserTypeId!T, SuperTypeId, T.sizeof, T.alignof, EmbedUserType!T, move_emplace, destroy_fun, &stringify, &compare); +} diff --git a/src/urt/zip.d b/src/urt/zip.d index 4bf2521..040a056 100644 --- a/src/urt/zip.d +++ b/src/urt/zip.d @@ -4,164 +4,158 @@ import urt.crc; import urt.endian; import urt.hash; import urt.mem.allocator; +import urt.result; -alias zlib_crc = calculateCRC!(Algorithm.CRC32_ISO_HDLC); +alias zlib_crc = calculate_crc!(Algorithm.crc32_iso_hdlc); nothrow @nogc: // this is a port of tinflate (tiny inflate) -enum error_code : int +enum GzipFlag : ubyte { - OK = 0, /**< Success */ - DATA_ERROR = -3, /**< Input error */ - BUF_ERROR = -5 /**< Not enough room for output */ + ftext = 1, + fhcrc = 2, + fextra = 4, + fname = 8, + fcomment = 16 } -enum gzip_flag : ubyte -{ - FTEXT = 1, - FHCRC = 2, - FEXTRA = 4, - FNAME = 8, - FCOMMENT = 16 -} - -error_code zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 6) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte cmf = src[0]; ubyte flg = src[1]; // check checksum if ((256 * cmf + flg) % 31) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if ((cmf & 0x0F) != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check window size is valid if ((cmf >> 4) > 7) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check there is no preset dictionary if (flg & 0x20) - return error_code.DATA_ERROR; + return InternalResult.data_error; - if (uncompress((src + 2)[0 .. sourceLen - 6], dest, destLen) != error_code.OK) - return error_code.DATA_ERROR; + if (!uncompress((src + 2)[0 .. sourceLen - 6], dest, destLen)) + return InternalResult.data_error; if (adler32(dest[0 .. destLen]) != loadBigEndian!uint(cast(uint*)&src[sourceLen - 4])) - return error_code.DATA_ERROR; + return InternalResult.data_error; - return error_code.OK; + return InternalResult.success; } -error_code gzip_uncompressed_length(const(void)[] source, out size_t destLen) +Result gzip_uncompressed_length(const(void)[] source, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 18) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check id bytes if (src[0] != 0x1F || src[1] != 0x8B) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if (src[2] != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte flg = src[3]; // check that reserved bits are zero if (flg & 0xE0) - return error_code.DATA_ERROR; + return InternalResult.data_error; // get decompressed length destLen = loadLittleEndian!uint(cast(uint*)(src + sourceLen - 4)); - return error_code.OK; + return InternalResult.success; } -error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 18) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check id bytes if (src[0] != 0x1F || src[1] != 0x8B) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if (src[2] != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte flg = src[3]; // check that reserved bits are zero if (flg & 0xE0) - return error_code.DATA_ERROR; + return InternalResult.data_error; // skip base header of 10 bytes const(ubyte)* start = src + 10; // skip extra data if present - if (flg & gzip_flag.FEXTRA) + if (flg & GzipFlag.fextra) { uint xlen = loadLittleEndian!ushort(cast(ushort*)start); if (xlen > sourceLen - 12) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += xlen + 2; } // skip file name if present - if (flg & gzip_flag.FNAME) + if (flg & GzipFlag.fname) { do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } // skip file comment if present - if (flg & gzip_flag.FCOMMENT) + if (flg & GzipFlag.fcomment) { do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } // check header crc if present - if (flg & gzip_flag.FHCRC) + if (flg & GzipFlag.fhcrc) { uint hcrc; if (start - src > sourceLen - 2) - return error_code.DATA_ERROR; + return InternalResult.data_error; hcrc = loadLittleEndian!ushort(cast(ushort*)start); if (hcrc != (zlib_crc(src[0 .. start - src]) & 0x0000FFFF)) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += 2; } @@ -169,25 +163,25 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen // get decompressed length uint dlen = loadLittleEndian!uint(cast(uint*)(src + sourceLen - 4)); if (dlen > dest.length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; if ((src + sourceLen) - start < 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; - if (uncompress(start[0 .. (src + sourceLen) - start - 8], dest, destLen) != error_code.OK) - return error_code.DATA_ERROR; + if (!uncompress(start[0 .. (src + sourceLen) - start - 8], dest, destLen)) + return InternalResult.data_error; if (destLen != dlen) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check CRC32 checksum if (zlib_crc(dest[0..dlen]) != loadLittleEndian!uint(cast(uint*)(src + sourceLen - 8))) - return error_code.DATA_ERROR; + return InternalResult.data_error; - return error_code.OK; + return InternalResult.success; } -error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result uncompress(const(void)[] source, void[] dest, out size_t destLen) { data d; @@ -211,7 +205,7 @@ error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) uint btype = getbits(&d, 2); // Decompress block - error_code res; + Result res; switch (btype) { case 0: @@ -227,20 +221,20 @@ error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) res = inflate_dynamic_block(&d); break; default: - res = error_code.DATA_ERROR; + res = InternalResult.data_error; break; } - if (res != error_code.OK) + if (!res) return res; } while (!bfinal); if (d.overflow) - return error_code.DATA_ERROR; + return InternalResult.data_error; destLen = d.dest - d.dest_start; - return error_code.OK; + return InternalResult.success; } @@ -606,7 +600,7 @@ void build_fixed_trees(tree *lt, tree *dt) } /* Given an array of code lengths, build a tree */ -error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) +Result build_tree(tree *t, const(ubyte)* lengths, ushort num) { ushort[16] offs = void; uint available; @@ -638,7 +632,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) /* Check length contains no more codes than available */ if (used > available) - return error_code.DATA_ERROR; + return InternalResult.data_error; available = 2 * (available - used); offs[i] = num_codes; @@ -650,7 +644,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) * code that it has length 1 */ if ((num_codes > 1 && available > 0) || (num_codes == 1 && t.counts[1] != 1)) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Fill in symbols sorted by code */ for (i = 0; i < num; ++i) @@ -669,7 +663,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) t.symbols[1] = cast(ushort)(t.max_sym + 1); } - return error_code.OK; + return InternalResult.success; } /* -- Decode functions -- */ @@ -749,7 +743,7 @@ int decode_symbol(data *d, const tree *t) return t.symbols[base + offs]; } -error_code decode_trees(data *d, tree *lt, tree *dt) +Result decode_trees(data *d, tree *lt, tree *dt) { /* Special ordering of code length codes */ __gshared immutable ubyte[19] clcidx = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; @@ -776,7 +770,7 @@ error_code decode_trees(data *d, tree *lt, tree *dt) * See also: https://github.com/madler/zlib/issues/82 */ if (hlit > 286 || hdist > 30) - return error_code.DATA_ERROR; + return InternalResult.data_error; for (i = 0; i < 19; ++i) lengths[i] = 0; @@ -791,13 +785,13 @@ error_code decode_trees(data *d, tree *lt, tree *dt) } /* Build code length tree (in literal/length tree to save space) */ - error_code res = build_tree(lt, lengths.ptr, 19); - if (res != error_code.OK) + Result res = build_tree(lt, lengths.ptr, 19); + if (res != InternalResult.success) return res; /* Check code length tree is not empty */ if (lt.max_sym == -1) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Decode code lengths for the dynamic trees */ for (num = 0; num < hlit + hdist; ) @@ -805,14 +799,14 @@ error_code decode_trees(data *d, tree *lt, tree *dt) int sym = decode_symbol(d, lt); if (sym > lt.max_sym) - return error_code.DATA_ERROR; + return InternalResult.data_error; switch (sym) { case 16: /* Copy previous code length 3-6 times (read 2 bits) */ if (num == 0) { - return error_code.DATA_ERROR; + return InternalResult.data_error; } sym = lengths[num - 1]; length = getbits_base(d, 2, 3); @@ -834,7 +828,7 @@ error_code decode_trees(data *d, tree *lt, tree *dt) } if (length > hlit + hdist - num) - return error_code.DATA_ERROR; + return InternalResult.data_error; while (length--) lengths[num++] = cast(ubyte)sym; @@ -842,26 +836,26 @@ error_code decode_trees(data *d, tree *lt, tree *dt) /* Check EOB symbol is present */ if (lengths[256] == 0) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Build dynamic trees */ res = build_tree(lt, lengths.ptr, hlit); - if (res != error_code.OK) + if (res != InternalResult.success) return res; res = build_tree(dt, lengths.ptr + hlit, hdist); - if (res != error_code.OK) + if (res != InternalResult.success) return res; - return error_code.OK; + return InternalResult.success; } /* -- Block inflate functions -- */ /* Given a stream and two trees, inflate a block of data */ -error_code inflate_block_data(data *d, tree *lt, tree *dt) +Result inflate_block_data(data *d, tree *lt, tree *dt) { /* Extra bits and base tables for length codes */ __gshared immutable ubyte[30] length_bits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 127 ]; @@ -877,12 +871,12 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check for overflow in bit reader */ if (d.overflow) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (sym < 256) { if (d.dest == d.dest_end) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; *d.dest++ = cast(ubyte)sym; } else @@ -892,11 +886,11 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check for end of block */ if (sym == 256) - return error_code.OK; + return InternalResult.success; /* Check sym is within range and distance tree is not empty */ if (sym > lt.max_sym || sym - 257 > 28 || dt.max_sym == -1) - return error_code.DATA_ERROR; + return InternalResult.data_error; sym -= 257; @@ -907,16 +901,16 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check dist is within range */ if (dist > dt.max_sym || dist > 29) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Possibly get more bits from distance code */ offs = getbits_base(d, dist_bits[dist], dist_base[dist]); if (offs > d.dest - d.dest_start) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (d.dest_end - d.dest < length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; /* Copy match */ for (i = 0; i < length; ++i) @@ -928,10 +922,10 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) } /* Inflate an uncompressed block of data */ -error_code inflate_uncompressed_block(data *d) +Result inflate_uncompressed_block(data *d) { if (d.source_end - d.source < 4) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Get length */ uint length = loadLittleEndian!ushort(cast(ushort*)d.source); @@ -941,15 +935,15 @@ error_code inflate_uncompressed_block(data *d) /* Check length */ if (length != (~invlength & 0x0000FFFF)) - return error_code.DATA_ERROR; + return InternalResult.data_error; d.source += 4; if (d.source_end - d.source < length) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (d.dest_end - d.dest < length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; /* Copy block */ while (length--) @@ -959,19 +953,19 @@ error_code inflate_uncompressed_block(data *d) d.tag = 0; d.bitcount = 0; - return error_code.OK; + return InternalResult.success; } -error_code inflate_fixed_block(data *d) +Result inflate_fixed_block(data *d) { build_fixed_trees(&d.ltree, &d.dtree); return inflate_block_data(d, &d.ltree, &d.dtree); } -error_code inflate_dynamic_block(data *d) +Result inflate_dynamic_block(data *d) { - error_code res = decode_trees(d, &d.ltree, &d.dtree); - if (res != error_code.OK) + Result res = decode_trees(d, &d.ltree, &d.dtree); + if (res != InternalResult.success) return res; return inflate_block_data(d, &d.ltree, &d.dtree); }