From 64bb371db70cc68257113e7d84922becd0d7df2e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 16:57:00 +1000 Subject: [PATCH 01/73] Add main function --- Makefile | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) 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 From 084fa962c9d62892b684ddfbdb9901f6b1c90913 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 17:25:17 +1000 Subject: [PATCH 02/73] Migrate to lower_snake case... --- src/urt/algorithm.d | 10 +- src/urt/conv.d | 134 ++++---- src/urt/crc.d | 44 +-- src/urt/dbg.d | 2 +- src/urt/digest/md5.d | 28 +- src/urt/digest/sha.d | 34 +- src/urt/endian.d | 8 +- src/urt/file.d | 128 ++++---- src/urt/format/json.d | 44 +-- src/urt/hash.d | 8 +- src/urt/inet.d | 38 +-- src/urt/mem/alloc.d | 16 +- src/urt/mem/allocator.d | 6 +- src/urt/mem/scratchpad.d | 44 +-- src/urt/mem/string.d | 4 +- src/urt/mem/temp.d | 48 +-- src/urt/meta/package.d | 54 +-- src/urt/meta/tuple.d | 10 +- src/urt/package.d | 6 +- src/urt/rand.d | 2 +- src/urt/range/package.d | 14 +- src/urt/result.d | 55 ++-- src/urt/socket.d | 632 ++++++++++++++++++------------------ src/urt/string/ansi.d | 14 +- src/urt/string/format.d | 28 +- src/urt/string/string.d | 6 +- src/urt/string/tailstring.d | 4 +- src/urt/system.d | 28 +- src/urt/time.d | 22 +- src/urt/util.d | 8 +- src/urt/variant.d | 12 +- src/urt/zip.d | 156 +++++---- 32 files changed, 812 insertions(+), 835 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 14ecc1f..a742393 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -48,7 +48,7 @@ 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(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) { T* p = arr.ptr; size_t low = 0; @@ -165,12 +165,12 @@ unittest 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); } diff --git a/src/urt/conv.d b/src/urt/conv.d index 91f39ab..ebce388 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -8,7 +8,7 @@ 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 +long parse_int(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -21,13 +21,13 @@ long parseInt(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure i++; } - ulong value = str.ptr[i .. str.length].parseUint(bytesTaken, base); + ulong value = str.ptr[i .. str.length].parse_uint(bytesTaken, base); if (bytesTaken && *bytesTaken != 0) *bytesTaken += i; return neg ? -cast(long)value : cast(long)value; } -long parseIntWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +long parse_int_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -40,13 +40,13 @@ long parseIntWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* i++; } - ulong value = str[i .. str.length].parseUintWithDecimal(fixedPointDivisor, bytesTaken, base); + ulong value = str[i .. str.length].parse_uint_with_decimal(fixedPointDivisor, bytesTaken, base); if (bytesTaken && *bytesTaken != 0) *bytesTaken += i; return neg ? -cast(long)value : cast(long)value; } -ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -69,7 +69,7 @@ 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; @@ -81,7 +81,7 @@ ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pur return value; } -ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -103,7 +103,7 @@ ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_ goto parse_decimal; } - uint digit = getDigit(c); + uint digit = get_digit(c); if (digit >= base) break; value = value*base + digit; @@ -113,7 +113,7 @@ ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_ parse_decimal: for (; s < e; ++s) { - uint digit = getDigit(*s); + uint digit = get_digit(*s); if (digit >= base) { // if i == 1, then the first char was a '.' and the next was not numeric, so this is not a number! @@ -132,11 +132,11 @@ done: return value; } -ulong parseUintWithBase(const(char)[] str, size_t* bytesTaken = null) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytesTaken = null) pure { const(char)* p = str.ptr; - int base = parseBasePrefix(str); - ulong i = parseUint(str, bytesTaken, base); + int base = parse_base_prefix(str); + ulong i = parse_uint(str, bytesTaken, base); if (bytesTaken && *bytesTaken != 0) *bytesTaken += str.ptr - p; return i; @@ -147,21 +147,21 @@ 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_uint_with_decimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); + assert(parse_int_with_decimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); + assert(parse_int("11001", null, 2) == 25); + assert(parse_int_with_decimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); + 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); } -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,25 +210,25 @@ 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 +double parse_float(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure { // TODO: E-notation... size_t taken = void; ulong div = void; - long value = str.parseIntWithDecimal(div, &taken, base); + long value = str.parse_int_with_decimal(div, &taken, base); if (bytesTaken) *bytesTaken = taken; if (taken == 0) @@ -245,15 +245,15 @@ 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("1101.11", &taken, 2), 13.75) && taken == 7); + assert(parse_float("xyz", &taken) is double.nan && taken == 0); } -ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure +ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure { const bool neg = value < 0; showSign |= neg; @@ -263,7 +263,7 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c ulong i = neg ? -value : value; - ptrdiff_t r = formatUint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); + ptrdiff_t r = format_uint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); if (r < 0 || !showSign) return r; @@ -309,7 +309,7 @@ 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; @@ -360,36 +360,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 +424,9 @@ template to(T) { long to(const(char)[] str) { - int base = parseBasePrefix(str); + int 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,9 +435,9 @@ template to(T) { double to(const(char)[] str) { - int base = parseBasePrefix(str); + int 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; } @@ -479,7 +479,7 @@ template to(T) private: -uint getDigit(char c) pure +uint get_digit(char c) pure { uint zeroBase = c - '0'; if (zeroBase < 10) @@ -493,7 +493,7 @@ uint getDigit(char c) pure return -1; } -int parseBasePrefix(ref const(char)[] str) pure +int parse_base_prefix(ref const(char)[] str) pure { int base = 10; if (str.length >= 2) @@ -510,7 +510,7 @@ int parseBasePrefix(ref const(char)[] str) pure /+ -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; @@ -530,7 +530,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..74f266b 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -1,6 +1,6 @@ module urt.crc; -import urt.meta : intForWidth; +import urt.meta : IntForWidth; import urt.traits : isUnsignedInt; nothrow @nogc: @@ -41,10 +41,10 @@ struct CRCParams } 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!(paramTable[algo].width); // compute a CRC with runtime parameters -T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure +T calculate_crc(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure if (isUnsignedInt!T) { assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); @@ -68,7 +68,7 @@ T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref cons } // 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 +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)paramTable[algo].initial^paramTable[algo].finalXor) pure if (isUnsignedInt!T) { enum CRCParams params = paramTable[algo]; @@ -98,7 +98,7 @@ T calculateCRC(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = } // 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 +T calculate_crc_2(Algorithm algo, T = IntForWidth!(paramTable[algo].width*2))(const void[] data, uint earlyOffset) pure if (isUnsignedInt!T) { enum CRCParams params = paramTable[algo]; @@ -151,7 +151,7 @@ done: } -T[256] generateCRCTable(T)(ref const CRCParams params) pure +T[256] generate_crc_table(T)(ref const CRCParams params) pure if (isUnsignedInt!T) { enum typeWidth = T.sizeof * 8; @@ -189,23 +189,23 @@ 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[]) == paramTable[Algorithm.CRC16_MODBUS].check); + assert(calculate_crc!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); + assert(calculate_crc!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); + assert(calculate_crc!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); + assert(calculate_crc!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); + assert(calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); + assert(calculate_crc!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); + assert(calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); + assert(calculate_crc!(Algorithm.CRC32_CASTAGNOLI)(checkData[]) == paramTable[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) == paramTable[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) == paramTable[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) == paramTable[Algorithm.CRC32_ISO_HDLC].check); } @@ -238,5 +238,5 @@ T reflect(T)(T value, ubyte bits) // this minimises the number of table instantiations template CRCTable(uint width, uint poly, bool reflect) { - __gshared immutable CRCTable = generateCRCTable!(intForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); + __gshared immutable CRCTable = generate_crc_table!(IntForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); } 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..ba1c4c7 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,13 +91,13 @@ unittest import urt.encoding; MD5Context ctx; - md5Init(ctx); - auto digest = md5Finalise(ctx); + md5_init(ctx); + auto digest = md5_finalise(ctx); assert(digest == Hex!"d41d8cd98f00b204e9800998ecf8427e"); - md5Init(ctx); - md5Update(ctx, "Hello, World!"); - digest = md5Finalise(ctx); + md5_init(ctx); + md5_update(ctx, "Hello, World!"); + digest = md5_finalise(ctx); assert(digest == Hex!"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..fc64067 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; @@ -115,23 +115,23 @@ unittest import urt.encoding; SHA1Context ctx; - shaInit(ctx); - auto digest = shaFinalise(ctx); + sha_init(ctx); + auto digest = sha_finalise(ctx); assert(digest == Hex!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); - shaInit(ctx); - shaUpdate(ctx, "Hello, World!"); - digest = shaFinalise(ctx); + sha_init(ctx); + sha_update(ctx, "Hello, World!"); + digest = sha_finalise(ctx); assert(digest == Hex!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); SHA256Context ctx2; - shaInit(ctx2); - auto digest2 = shaFinalise(ctx2); + sha_init(ctx2); + auto digest2 = sha_finalise(ctx2); assert(digest2 == Hex!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - shaInit(ctx2); - shaUpdate(ctx2, "Hello, World!"); - digest2 = shaFinalise(ctx2); + sha_init(ctx2); + sha_update(ctx2, "Hello, World!"); + digest2 = sha_finalise(ctx2); assert(digest2 == Hex!"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/endian.d b/src/urt/endian.d index e76f671..879b666 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -82,8 +82,8 @@ ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) 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)) { - 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; } @@ -206,8 +206,8 @@ 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)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U r = nativeToEndian!little(*cast(U*)&val); return *cast(T*)&r; } diff --git a/src/urt/file.d b/src/urt/file.d index e7ec4d3..8d3995c 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); 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); 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..997b9e1 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -13,12 +13,12 @@ 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) { @@ -76,9 +76,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 +99,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 +107,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; @@ -138,14 +138,14 @@ 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 @@ -179,7 +179,7 @@ unittest ] }`; - Variant root = parseJson(doc); + Variant root = parse_json(doc); // check the data was parsed correctly... assert(root["nothing"].isNull); @@ -197,18 +197,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 +220,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 +244,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 +307,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 +315,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); @@ -331,7 +331,7 @@ Variant parseNode(ref const(char)[] text) bool neg = text[0] == '-'; size_t taken = void; ulong div = void; - ulong value = text[neg .. $].parseUintWithDecimal(div, &taken, 10); + ulong value = text[neg .. $].parse_uint_with_decimal(div, &taken, 10); assert(taken > 0); text = text[taken + neg .. $]; 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..1c2e019 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -97,11 +97,11 @@ 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 @@ -113,7 +113,7 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = '.'; - offset += b[i].formatInt(tmp[offset..$]); + offset += b[i].format_int(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stackBuffer.ptr) @@ -129,22 +129,22 @@ nothrow @nogc: { ubyte[4] t; size_t offset = 0, len; - ulong i = s[offset..$].parseInt(&len); + ulong i = s[offset..$].parse_int(&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_int(&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_int(&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_int(&len); offset += len; if (len == 0 || i > 255) return -1; @@ -224,11 +224,11 @@ 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 @@ -275,7 +275,7 @@ nothrow @nogc: tmp[offset++] = ':'; continue; } - offset += s[i].formatInt(tmp[offset..$], 16); + offset += s[i].format_int(tmp[offset..$], 16); ++i; } @@ -360,7 +360,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefixLen.format_int(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stackBuffer.ptr) { @@ -378,7 +378,7 @@ 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_int(&t); if (t == 0 || plen > 32) return -1; addr = a; @@ -449,7 +449,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefixLen.format_int(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stackBuffer.ptr) { @@ -467,7 +467,7 @@ 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_int(&t); if (t == 0 || plen > 32) return -1; addr = a; @@ -547,7 +547,7 @@ nothrow @nogc: { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; - offset += _a.ipv4.port.formatInt(tmp[offset..$]); + offset += _a.ipv4.port.format_int(tmp[offset..$]); } else { @@ -555,7 +555,7 @@ 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_int(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stackBuffer.ptr) @@ -602,7 +602,7 @@ nothrow @nogc: if (s.length > taken && s[taken] == ':') { size_t t; - ulong p = s[++taken..$].parseInt(&t); + ulong p = s[++taken..$].parse_int(&t); if (t == 0 || p > 0xFFFF) return -1; taken += t; diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 7408808..5ec148a 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -11,7 +11,7 @@ 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; alignment = max(alignment, (void*).sizeof); @@ -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; alignment = max(alignment, (void*).sizeof); assert(isPowerOf2(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/scratchpad.d b/src/urt/mem/scratchpad.d index 83a3abb..dcc681b 100644 --- a/src/urt/mem/scratchpad.d +++ b/src/urt/mem/scratchpad.d @@ -11,7 +11,7 @@ enum size_t NumScratchBuffers = 4; static assert(MaxScratchpadSize.isPowerOf2, "Scratchpad size must be a power of 2"); -void[] allocScratchpad(size_t size = MaxScratchpadSize) +void[] alloc_scratchpad(size_t size = MaxScratchpadSize) { if (size > MaxScratchpadSize) { @@ -23,13 +23,13 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) 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..b00618a 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -56,9 +56,9 @@ struct CacheString 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/mem/temp.d b/src/urt/mem/temp.d index 6f2d28b..aabe847 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -22,16 +22,16 @@ void[] talloc(size_t size) nothrow @nogc return null; } - 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 +50,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]; } @@ -77,23 +77,23 @@ char* tstringz(const(char)[] str) nothrow @nogc return null; size_t len = str.length; - if (allocOffset + len + 1 > TempMemSize) - allocOffset = 0; + if (alloc_offset + len + 1 > TempMemSize) + alloc_offset = 0; - char* r = cast(char*)tempMem.ptr + allocOffset; + char* r = cast(char*)tempMem.ptr + alloc_offset; r[0 .. len] = str[]; r[len] = '\0'; - allocOffset += len + 1; + alloc_offset += len + 1; 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 +101,34 @@ 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; } 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 +170,4 @@ private: private: static void[TempMemSize] tempMem; -static ushort allocOffset = 0; +static ushort alloc_offset = 0; diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index faa2941..bec2f81 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -6,39 +6,47 @@ alias Alias(T) = T; alias AliasSeq(TList...) = TList; -template intForWidth(size_t width, bool signed = false) +template IntForWidth(size_t width, bool signed = false) { static if (width <= 8 && !signed) - alias intForWidth = ubyte; + alias IntForWidth = ubyte; else static if (width <= 8 && signed) - alias intForWidth = byte; + alias IntForWidth = byte; else static if (width <= 16 && !signed) - alias intForWidth = ushort; + alias IntForWidth = ushort; else static if (width <= 16 && signed) - alias intForWidth = short; + alias IntForWidth = short; else static if (width <= 32 && !signed) - alias intForWidth = uint; + alias IntForWidth = uint; else static if (width <= 32 && signed) - alias intForWidth = int; + alias IntForWidth = int; else static if (width <= 64 && !signed) - alias intForWidth = ulong; + alias IntForWidth = ulong; else static if (width <= 64 && signed) - alias intForWidth = long; + alias IntForWidth = long; } -template staticMap(alias fun, args...) +template STATIC_MAP(alias fun, args...) { - alias staticMap = AliasSeq!(); + alias STATIC_MAP = AliasSeq!(); static foreach (arg; args) - staticMap = AliasSeq!(staticMap, fun!arg); + STATIC_MAP = AliasSeq!(STATIC_MAP, fun!arg); } -template staticIndexOf(args...) +template STATIC_FILTER(alias filter, args...) +{ + alias STATIC_FILTER = AliasSeq!(); + static foreach (arg; args) + static if (filter!arg) + STATIC_FILTER = AliasSeq!(STATIC_FILTER, arg); +} + +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; @@ -63,8 +71,8 @@ template EnumKeys(E) private alias EnumStrings = __traits(allMembers, E); } -E enumFromString(E)(const(char)[] key) -if (is(E == enum)) +E enum_from_string(E)(const(char)[] key) + if (is(E == enum)) { foreach (i, k; EnumKeys!E) if (key[] == k[]) @@ -75,16 +83,16 @@ if (is(E == enum)) private: -template isSame(alias a, alias b) +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 isSame = is(A == B); + enum is_same = is(A == 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..4d69c46 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -16,15 +16,15 @@ template map(fun...) auto map(Range)(Range r) if (isInputRange!(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 @@ -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; @@ -190,7 +190,7 @@ template reduce(fun...) { import std.exception : enforce; alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); - alias Args = staticMap!(ReduceSeedType!E, binfuns); + alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); static if (isInputRange!R) { @@ -245,7 +245,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 diff --git a/src/urt/result.d b/src/urt/result.d index 564b15c..3769e8b 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; @@ -32,44 +24,35 @@ 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/socket.d b/src/urt/socket.d index 800f72d..e61e29c 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -57,119 +57,119 @@ 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, // IPv6 options - FirstIpv6Option, + first_ipv6_option, // ICMP options - FirstIcmpOption = FirstIpv6Option, + first_icmp_option = first_ipv6_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, } @@ -195,7 +195,7 @@ Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Sock 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 +214,7 @@ Result close(Socket socket) // s_noSignal.Erase(socket); // } - return Result.Success; + return Result.success; } Result shutdown(Socket socket, SocketShutdownMode how) @@ -224,15 +224,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,7 +240,7 @@ 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) @@ -252,14 +252,14 @@ Result bind(Socket socket, ref const InetAddress address) if (_bind(socket.handle, sockAddr, 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) @@ -271,7 +271,7 @@ Result connect(Socket socket, ref const InetAddress address) if (_connect(socket.handle, sockAddr, cast(int)addrLen) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result accept(Socket socket, out Socket connection, InetAddress* connectingSocketAddress = null) @@ -285,12 +285,12 @@ Result accept(Socket socket, out Socket connection, InetAddress* connectingSocke return socket_getlasterror(); else if (connectingSocketAddress) *connectingSocketAddress = make_InetAddress(addr); - return Result.Success; + 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* bytesSent = 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) @@ -303,7 +303,7 @@ Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None 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* bytesSent = null) { ubyte[sockaddr_storage.sizeof] tmp = void; size_t addrLen; @@ -314,7 +314,7 @@ Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.No assert(sockAddr, "Invalid socket address"); } - Result r = Result.Success; + Result r = Result.success; ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sockAddr, cast(int)addrLen); if (sent < 0) { @@ -326,9 +326,9 @@ Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.No 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* bytesReceived) { - 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; @@ -351,21 +351,21 @@ 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* senderAddress = null, size_t* bytesReceived) { char[sockaddr_storage.sizeof] addrBuffer = void; sockaddr* addr = cast(sockaddr*)addrBuffer.ptr; socklen_t size = addrBuffer.sizeof; - Result r = Result.Success; + 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; @@ -374,10 +374,10 @@ Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, In *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 + 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 r = error; } if (r && senderAddress) @@ -387,16 +387,16 @@ Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, In 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!"); + assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(optlen == s_optTypeRtSize[optInfo.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,7 +420,7 @@ 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 (optInfo.platform_type == OptType.unsupported) // return r; // } @@ -433,17 +433,17 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval const(void)* arg = optval; int itmp = void; linger ling = void; - if (optInfo.rtType != optInfo.platformType) + if (optInfo.rt_type != optInfo.platform_type) { - switch (optInfo.rtType) + switch (optInfo.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 (optInfo.platform_type) { - case OptType.Int: + case OptType.int_: itmp = value ? 1 : 0; arg = &itmp; break; @@ -451,20 +451,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 (optInfo.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,7 +479,7 @@ 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], optInfo.option, cast(const(char)*)arg, s_optTypePlatformSize[optInfo.platform_type]); return r; } @@ -487,80 +487,80 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval 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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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!"); + assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(outputlen == s_optTypeRtSize[optInfo.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 (optInfo.rt_type != optInfo.platform_type) { - switch (optInfo.platformType) + switch (optInfo.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 +570,39 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - socklen_t writtenLen = s_optTypePlatformSize[optInfo.platformType]; + socklen_t writtenLen = s_optTypePlatformSize[optInfo.platform_type]; // get the option r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(char*)arg, &writtenLen); - if (optInfo.rtType != optInfo.platformType) + if (optInfo.rt_type != optInfo.platform_type) { - switch (optInfo.rtType) + switch (optInfo.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 (optInfo.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 (optInfo.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 +614,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(optInfo.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 (optInfo.rt_type) { case OptType.INAddress: { @@ -635,36 +635,36 @@ 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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.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"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); return get_socket_option(socket, option, &output, IPAddr.sizeof); } @@ -680,27 +680,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 +716,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 +730,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,7 +738,7 @@ 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) @@ -760,9 +760,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 +780,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 +795,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 +812,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 +831,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 +878,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 +893,9 @@ nothrow @nogc: struct PollFd { Socket socket; - PollEvents requestEvents; - PollEvents returnEvents; - void* userData; + PollEvents request_events; + PollEvents return_events; + void* user_data; } @@ -920,56 +920,56 @@ 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; } @@ -1136,28 +1136,28 @@ 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 +1169,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 = [ @@ -1201,13 +1201,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 +1220,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 +1261,68 @@ 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( -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( 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( -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 +1331,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 +1344,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; } diff --git a/src/urt/string/ansi.d b/src/urt/string/ansi.d index eac1150..e17b489 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -68,7 +68,7 @@ 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; @@ -84,17 +84,17 @@ size_t parseANSICode(const(char)[] text) 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/format.d b/src/urt/string/format.d index 6016615..8175c42 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -1,6 +1,6 @@ module urt.string.format; -import urt.conv : parseIntFast; +import urt.conv : parse_int_fast; import urt.string; import urt.traits; import urt.util; @@ -269,24 +269,24 @@ 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? @@ -318,7 +318,7 @@ struct DefFormat(T) if (format.length && format[0].isNumeric) { bool success; - padding = format.parseIntFast(success); + padding = format.parse_int_fast(success); if (varLen) { if (padding < 0 || !formatArgs[padding].canInt) @@ -344,9 +344,9 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = formatInt(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); + size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); else - size_t len = formatUint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + size_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); if (toLower && len > 0) { @@ -397,7 +397,7 @@ struct DefFormat(T) if (format.length && format[0].isNumeric) { bool success; - width = format.parseIntFast(success); + width = format.parse_int_fast(success); if (varLen) { if (width < 0 || !formatArgs[width].canInt) @@ -453,12 +453,12 @@ struct DefFormat(T) if (format.length && format[0].isNumeric) { 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.popFront(); - grp2 = cast(int)format.parseIntFast(success); + grp2 = cast(int)format.parse_int_fast(success); } if (!success) return -2; @@ -744,7 +744,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 +807,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) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index b1281cd..f2747fa 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; @@ -240,9 +240,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 diff --git a/src/urt/string/tailstring.d b/src/urt/string/tailstring.d index 3664342..c9d8751 100644 --- a/src/urt/string/tailstring.d +++ b/src/urt/string/tailstring.d @@ -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/system.d b/src/urt/system.d index 84f97a1..2bb58ec 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) { @@ -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..33a7693 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -279,7 +279,7 @@ pure nothrow @nogc: 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; size_t offset = 0; uint y = year; @@ -291,37 +291,37 @@ pure nothrow @nogc: buffer[0 .. 3] = "BC "; offset += 3; } - ptrdiff_t len = year.formatInt(buffer[offset..$]); + ptrdiff_t len = year.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '-'; - len = month.formatInt(buffer[offset..$]); + len = month.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '-'; - len = day.formatInt(buffer[offset..$]); + len = day.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = ' '; - len = hour.formatInt(buffer[offset..$], 10, 2, '0'); + len = hour.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = ':'; - len = minute.formatInt(buffer[offset..$], 10, 2, '0'); + len = minute.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = ':'; - len = second.formatInt(buffer[offset..$], 10, 2, '0'); + len = second.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '.'; - len = (ns / 1_000_000).formatInt(buffer[offset..$], 10, 3, '0'); + len = (ns / 1_000_000).format_int(buffer[offset..$], 10, 3, '0'); if (len < 0) return len; return offset + len; @@ -511,14 +511,14 @@ package(urt) void initClock() ptrdiff_t timeToString(long ms, char[] buffer) pure { - import urt.conv : formatInt; + import urt.conv : format_int; long hr = ms / 3_600_000; if (!buffer.ptr) - return hr.formatInt(null, 10, 2, '0') + 10; + return hr.format_int(null, 10, 2, '0') + 10; - ptrdiff_t len = hr.formatInt(buffer, 10, 2, '0'); + ptrdiff_t len = hr.format_int(buffer, 10, 2, '0'); if (len < 0 || buffer.length < len + 10) return -1; diff --git a/src/urt/util.d b/src/urt/util.d index 044f47f..6eb457d 100644 --- a/src/urt/util.d +++ b/src/urt/util.d @@ -98,13 +98,15 @@ T alignUp(T)(T value, size_t alignment) bool isAligned(size_t alignment, T)(T value) if (isSomeInt!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)) { + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } @@ -442,8 +444,8 @@ ulong byteReverse(ulong v) pragma(inline, true) T byteReverse(T)(T val) if (!isIntegral!T) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U r = byteReverse(*cast(U*)&val); return *cast(T*)&r; } diff --git a/src/urt/variant.d b/src/urt/variant.d index 9f5fe2c..8567b46 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -510,14 +510,14 @@ nothrow @nogc: assert(false, "TODO: implement quantity formatting for JSON"); if (isDouble()) - return asDouble().formatFloat(buffer); + return asDouble().format_float(buffer); // TODO: parse args? //format 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(); @@ -536,7 +536,7 @@ nothrow @nogc: 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) @@ -691,12 +691,12 @@ unittest private: -import urt.hash : fnv1aHash; +import urt.hash : fnv1a; 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 UserTypeId(T) = fnv1a(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? enum uint UserTypeShortId(T) = cast(ushort)UserTypeId!T ^ (UserTypeId!T >> 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); diff --git a/src/urt/zip.d b/src/urt/zip.d index 4bf2521..62f4fda 100644 --- a/src/urt/zip.d +++ b/src/urt/zip.d @@ -4,21 +4,15 @@ 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 -{ - OK = 0, /**< Success */ - DATA_ERROR = -3, /**< Input error */ - BUF_ERROR = -5 /**< Not enough room for output */ -} - enum gzip_flag : ubyte { FTEXT = 1, @@ -28,91 +22,91 @@ enum gzip_flag : ubyte 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; @@ -123,7 +117,7 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen uint xlen = loadLittleEndian!ushort(cast(ushort*)start); if (xlen > sourceLen - 12) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += xlen + 2; } @@ -134,7 +128,7 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } @@ -145,7 +139,7 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } @@ -156,12 +150,12 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen 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); } From e683e62aefe684b1211d113dfbe0dfb79eedd5d7 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 21 Aug 2025 15:48:17 +1000 Subject: [PATCH 03/73] More snake_case migration... --- src/urt/algorithm.d | 38 +++---- src/urt/async.d | 2 +- src/urt/conv.d | 104 ++++++++--------- src/urt/crc.d | 164 +++++++++++++-------------- src/urt/digest/sha.d | 2 +- src/urt/encoding.d | 28 ++--- src/urt/endian.d | 42 +++---- src/urt/fibre.d | 20 ++-- src/urt/file.d | 4 +- src/urt/format/json.d | 2 +- src/urt/inet.d | 2 +- src/urt/mem/alloc.d | 12 +- src/urt/mem/region.d | 4 +- src/urt/mem/scratchpad.d | 4 +- src/urt/meta/nullable.d | 100 ++++++++--------- src/urt/meta/package.d | 18 +-- src/urt/range/package.d | 18 +-- src/urt/range/primitives.d | 38 +++---- src/urt/string/ansi.d | 4 +- src/urt/string/ascii.d | 50 ++++----- src/urt/string/format.d | 30 ++--- src/urt/string/package.d | 16 +-- src/urt/string/string.d | 8 +- src/urt/string/uni.d | 74 ++++++------ src/urt/time.d | 4 +- src/urt/traits.d | 112 +++++++++---------- src/urt/util.d | 224 ++++++++++++++++++------------------- src/urt/zip.d | 22 ++-- 28 files changed, 573 insertions(+), 573 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index a742393..9191f28 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -1,6 +1,6 @@ module urt.algorithm; -import urt.traits : lvalueOf; +import urt.traits : lvalue_of; import urt.util : swap; version = SmallSize; @@ -10,18 +10,18 @@ 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, lvalue_of!T.opCmp(lvalue_of!U))) return a.opCmp(b); - else static if (__traits(compiles, lvalueOf!U.opCmp(lvalueOf!T))) + else static if (__traits(compiles, lvalue_of!U.opCmp(lvalue_of!T))) return -b.opCmp(a); else static if (is(T : A[], A)) { - import urt.traits : isPrimitive; + import urt.traits : is_primitive; auto ai = a.ptr; auto bi = b.ptr; size_t len = a.length < b.length ? a.length : b.length; - static if (isPrimitive!A) + static if (is_primitive!A) { // compare strings foreach (i; 0 .. len) @@ -48,7 +48,7 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } -size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) +size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) { T* p = arr.ptr; size_t low = 0; @@ -59,7 +59,7 @@ size_t binary_search(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,7 +67,7 @@ size_t binary_search(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 @@ -76,12 +76,12 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs } 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; @@ -93,7 +93,7 @@ void qsort(alias pred = void, T)(T[] arr) version (SmallSize) { static if (is(pred == void)) - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!T))) + static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!T))) static int compare(const void* a, const void* b) nothrow @nogc => (*cast(const T*)a).opCmp(*cast(const T*)b); else @@ -181,34 +181,34 @@ 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) nothrow @nogc compare, void function(void* a, void* b) nothrow @nogc swap) { void* p = arr.ptr; - size_t length = arr.length / elementSize; + size_t length = arr.length / element_size; if (length > 1) { size_t pivotIndex = length / 2; - void* pivot = p + pivotIndex*elementSize; + void* pivot = p + pivotIndex*element_size; size_t i = 0; size_t j = length - 1; while (i <= j) { - while (compare(p + i*elementSize, pivot) < 0) i++; - while (compare(p + j*elementSize, pivot) > 0) j--; + while (compare(p + i*element_size, pivot) < 0) i++; + while (compare(p + j*element_size, pivot) > 0) j--; if (i <= j) { - swap(p + i*elementSize, p + j*elementSize); + swap(p + i*element_size, p + j*element_size); i++; j--; } } if (j > 0) - qsort(p[0 .. (j + 1)*elementSize], elementSize, compare, swap); + qsort(p[0 .. (j + 1)*element_size], element_size, compare, swap); if (i < length) - qsort(p[i*elementSize .. length*elementSize], elementSize, compare, swap); + qsort(p[i*element_size .. length*element_size], element_size, compare, swap); } } } diff --git a/src/urt/async.d b/src/urt/async.d index fee575f..5da7e61 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); diff --git a/src/urt/conv.d b/src/urt/conv.d index ebce388..a20b6ee 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -7,8 +7,8 @@ public import urt.string.format : toString; nothrow @nogc: -// on error or not-a-number cases, bytesTaken will contain 0 -long parse_int(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +// on error or not-a-number cases, bytes_taken will contain 0 +long parse_int(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -21,13 +21,13 @@ long parse_int(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure i++; } - ulong value = str.ptr[i .. str.length].parse_uint(bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; + ulong value = str.ptr[i .. str.length].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += i; return neg ? -cast(long)value : cast(long)value; } -long parse_int_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +long parse_int_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -40,13 +40,13 @@ long parse_int_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size i++; } - ulong value = str[i .. str.length].parse_uint_with_decimal(fixedPointDivisor, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; + ulong value = str[i .. str.length].parse_uint_with_decimal(fixed_point_divisor, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += i; return neg ? -cast(long)value : cast(long)value; } -ulong parse_uint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -76,12 +76,12 @@ ulong parse_uint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pu } } - if (bytesTaken) - *bytesTaken = s - str.ptr; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; } -ulong parse_uint_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -126,19 +126,19 @@ parse_decimal: } done: - fixedPointDivisor = divisor; - if (bytesTaken) - *bytesTaken = s - str.ptr; + fixed_point_divisor = divisor; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; } -ulong parse_uint_with_base(const(char)[] str, size_t* bytesTaken = null) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { const(char)* p = str.ptr; int base = parse_base_prefix(str); - ulong i = parse_uint(str, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += str.ptr - p; + ulong i = parse_uint(str, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += str.ptr - p; return i; } @@ -222,15 +222,15 @@ unittest } -// on error or not-a-number, result will be nan and bytesTaken will contain 0 -double parse_float(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, int base = 10) pure { // TODO: E-notation... size_t taken = void; ulong div = void; long value = str.parse_int_with_decimal(div, &taken, base); - if (bytesTaken) - *bytesTaken = taken; + if (bytes_taken) + *bytes_taken = taken; if (taken == 0) return double.nan; return cast(double)value / div; @@ -253,18 +253,18 @@ unittest } -ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure +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 = format_uint(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 +286,10 @@ ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, 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; } @@ -316,13 +316,13 @@ ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0 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 +334,14 @@ ptrdiff_t format_uint(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 +351,7 @@ ptrdiff_t format_uint(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; @@ -442,12 +442,12 @@ template to(T) 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); @@ -481,15 +481,15 @@ private: 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; + uint zero_base = c - '0'; + if (zero_base < 10) + return zero_base; + uint A_base = c - 'A'; + if (A_base < 26) + return A_base + 10; + uint a_base = c - 'a'; + if (a_base < 26) + return a_base + 10; return -1; } @@ -518,7 +518,7 @@ size_t format_struct(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; } diff --git a/src/urt/crc.d b/src/urt/crc.d index 74f266b..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.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 calculate_crc(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 calculate_crc(T = uint)(const void[] data, ref const CRCParams params, ref con 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 calculate_crc(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 calculate_crc(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 calculate_crc_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 calculate_crc_2(Algorithm algo, T = IntForWidth!(paramTable[algo].width*2))(co } 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] generate_crc_table(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] generate_crc_table(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(calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[]) == paramTable[Algorithm.CRC16_MODBUS].check); - assert(calculate_crc!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); - assert(calculate_crc!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); - assert(calculate_crc!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); - assert(calculate_crc!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); - assert(calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - assert(calculate_crc!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); - assert(calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); - assert(calculate_crc!(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 = calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[5 .. 9], crc) == paramTable[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) == paramTable[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) == 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 = generate_crc_table!(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/digest/sha.d b/src/urt/digest/sha.d index fc64067..197b7f4 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -105,7 +105,7 @@ ubyte[Context.DigestLen] sha_finalise(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; } diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 36ced1b..54eb70d 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -7,8 +7,8 @@ enum Hex(const char[] s) = (){ ubyte[s.length / 2] r; ptrdiff_t len = hex_decode 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; }(); -ptrdiff_t base64_encode_length(size_t sourceLength) pure - => (sourceLength + 2) / 3 * 4; +ptrdiff_t base64_encode_length(size_t source_length) pure + => (source_length + 2) / 3 * 4; ptrdiff_t base64_encode(const void[] data, char[] result) pure { @@ -54,8 +54,8 @@ 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; +ptrdiff_t base64_decode_length(size_t source_length) pure +=> source_length / 4 * 3; ptrdiff_t base64_decode(const char[] data, void[] result) pure { @@ -144,7 +144,7 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure 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 +157,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') @@ -193,12 +193,12 @@ unittest ptrdiff_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 +208,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,8 +226,8 @@ 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]; } } @@ -250,7 +250,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 +268,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; diff --git a/src/urt/endian.d b/src/urt/endian.d index 879b666..bb806d9 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,12 +75,12 @@ 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); @@ -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,7 +204,7 @@ 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); @@ -261,36 +261,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)) { 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)) { 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)) { 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)) { 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..2e736d2 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; @@ -104,7 +104,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 +151,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 +163,7 @@ struct Fibre this.fibreEntry = fibreEntry; this.userData = userData; - isDelegate = false; + is_delegate = false; abortRequested = false; finished = false; aborted = false; @@ -198,7 +198,7 @@ private: YieldHandler yieldHandler; cothread_t fibre; - bool isDelegate; + bool is_delegate; bool abortRequested; bool finished; bool aborted; @@ -570,7 +570,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 +741,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 +804,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 8d3995c..37dfad0 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -195,7 +195,7 @@ Result get_path(ref const File file, ref char[] buffer) if (result == 0 || result > dwPathLen) return getlasterror_result(); - size_t pathLen = tmp[0..result].uniConvert(buffer); + size_t pathLen = tmp[0..result].uni_convert(buffer); if (!pathLen) return InternalResult.buffer_too_small; if (buffer.length >= 4 && buffer[0..4] == `\\?\`) @@ -774,7 +774,7 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] if (!GetTempFileNameW(dstDir.twstringz, prefix.twstringz, 0, tmp.ptr)) 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); diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 997b9e1..a48faed 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -326,7 +326,7 @@ Variant parse_node(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; diff --git a/src/urt/inet.d b/src/urt/inet.d index 1c2e019..6dccd11 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -284,7 +284,7 @@ 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; } diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 5ec148a..a87c8dd 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -13,13 +13,13 @@ void[] alloc(size_t size) 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[] alloc_aligned(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]; @@ -62,10 +62,10 @@ void[] realloc(void[] mem, size_t newSize) 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 ? alloc_aligned(newSize, alignment) : null; if (newAlloc !is null && mem !is null) 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 dcc681b..2a20c30 100644 --- a/src/urt/mem/scratchpad.d +++ b/src/urt/mem/scratchpad.d @@ -8,7 +8,7 @@ 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[] alloc_scratchpad(size_t size = MaxScratchpadSize) @@ -19,7 +19,7 @@ void[] alloc_scratchpad(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; 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 bec2f81..e71470a 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -53,28 +53,28 @@ template static_index_of(args...) }(); } -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) +template enum_keys(E) { - static assert(is(E == enum), "EnumKeys only works with enums!"); - __gshared immutable string[EnumStrings.length] EnumKeys = [ EnumStrings ]; - private alias EnumStrings = __traits(allMembers, E); + static assert(is(E == enum), "enum_keys only works with enums!"); + __gshared immutable string[enum_strings.length] enum_keys = [ enum_strings ]; + private alias enum_strings = __traits(allMembers, E); } E enum_from_string(E)(const(char)[] key) if (is(E == enum)) { - foreach (i, k; EnumKeys!E) + foreach (i, k; enum_keys!E) if (key[] == k[]) return cast(E)i; return cast(E)-1; diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 4d69c46..3592b11 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -8,13 +8,13 @@ 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, STATIC_MAP; @@ -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() { @@ -189,10 +189,10 @@ template reduce(fun...) if (isIterable!R) { import std.exception : enforce; - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + 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, @@ -259,7 +259,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; @@ -309,7 +309,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 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/string/ansi.d b/src/urt/string/ansi.d index e17b489..931d60d 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -70,14 +70,14 @@ nothrow @nogc: 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; diff --git a/src/urt/string/ascii.d b/src/urt/string/ascii.d index 5541d3a..9edab53 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,26 @@ char toUpper(char c) pure return c; } -char[] toLower(const(char)[] str, char[] buffer) pure +char[] to_lower(const(char)[] str, char[] buffer) pure { foreach (i; 0 .. str.length) - buffer[i] = toLower(str[i]); + buffer[i] = to_lower(str[i]); return buffer; } -char[] toUpper(const(char)[] str, char[] buffer) pure +char[] to_upper(const(char)[] str, char[] buffer) pure { foreach (i; 0 .. str.length) - buffer[i] = toUpper(str[i]); + buffer[i] = to_upper(str[i]); return buffer; } -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); } diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 8175c42..410432e 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -291,17 +291,17 @@ struct DefFormat(T) // 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,7 +315,7 @@ struct DefFormat(T) varLen = true; format.popFront; } - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; padding = format.parse_int_fast(success); @@ -332,7 +332,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 +344,11 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); + size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); else size_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,9 +392,9 @@ 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.parse_int_fast(success); @@ -450,12 +450,12 @@ 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.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.parse_int_fast(success); @@ -634,7 +634,7 @@ struct DefFormat(T) static assert(false, "Not implemented for type: ", T.stringof); } - static if (isSomeInt!T || is(T == bool)) + static if (is_some_int!T || is(T == bool)) { ptrdiff_t toInt() const pure nothrow @nogc { @@ -922,9 +922,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 +943,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..6a103d0 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -69,7 +69,7 @@ ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc return a.length - b.length; for (size_t i = 0; i < a.length; ++i) { - ptrdiff_t diff = toLower(a[i]) - toLower(b[i]); + ptrdiff_t diff = to_lower(a[i]) - to_lower(b[i]); if (diff) return diff; } @@ -98,12 +98,12 @@ inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure no size_t first = 0, last = s.length; static if (Front) { - while (first < s.length && isWhitespace(s.ptr[first])) + while (first < s.length && is_whitespace(s.ptr[first])) ++first; } static if (Back) { - while (last > first && isWhitespace(s.ptr[last - 1])) + while (last > first && is_whitespace(s.ptr[last - 1])) --last; } return s.ptr[first .. last]; @@ -274,9 +274,9 @@ char[] unEscape(char[] s) pure nothrow @nogc char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure nothrow @nogc { - 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 +296,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) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index f2747fa..a6d73d4 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -626,7 +626,7 @@ nothrow @nogc: 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 +638,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); @@ -654,7 +654,7 @@ nothrow @nogc: ref MutableString!Embed insertFormat(Things...)(size_t offset, auto ref Things things) { 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(); @@ -666,7 +666,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); _format(ptr[offset .. offset + insertLen], forward!things); writeLength(newLen); diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index ee9b551..bc3e44c 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -3,16 +3,16 @@ module urt.string.uni; nothrow @nogc: -size_t uniConvert(const(char)[] s, wchar[] buffer) +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 +32,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 +47,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 +84,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 +103,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 +120,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 +129,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 +140,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 +168,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 +191,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 +200,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 +214,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); @@ -244,7 +244,7 @@ size_t uniConvert(const(dchar)[] s, wchar[] buffer) unittest { - immutable dstring unicodeTest = + immutable dstring unicode_test = "Basic ASCII: Hello, World!\n" ~ "BMP Examples: 你好, مرحبا, שלום, 😊, ☂️\n" ~ "Supplementary Planes: 𐍈, 𝒜, 🀄, 🚀\n" ~ @@ -257,18 +257,18 @@ 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... //... diff --git a/src/urt/time.d b/src/urt/time.d index 33a7693..901884a 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) { @@ -137,7 +137,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 diff --git a/src/urt/traits.d b/src/urt/traits.d index 30ef675..bfa8073 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(Unqual!T == bool) || 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,27 @@ 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_default_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T t; })); -enum isConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : 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 6eb457d..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,40 @@ 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(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; @@ -142,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) { @@ -174,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) @@ -203,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); @@ -272,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); @@ -380,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) { @@ -412,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)); @@ -423,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)); @@ -432,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)); @@ -441,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); + 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) { @@ -478,7 +478,7 @@ T bitReverse(T)(T x) static if (T.sizeof == 1) return x; else - return byteReverse(x); + return byte_reverse(x); } } else @@ -533,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); @@ -679,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/zip.d b/src/urt/zip.d index 62f4fda..040a056 100644 --- a/src/urt/zip.d +++ b/src/urt/zip.d @@ -6,20 +6,20 @@ import urt.hash; import urt.mem.allocator; import urt.result; -alias zlib_crc = calculate_crc!(Algorithm.CRC32_ISO_HDLC); +alias zlib_crc = calculate_crc!(Algorithm.crc32_iso_hdlc); nothrow @nogc: // this is a port of tinflate (tiny inflate) -enum gzip_flag : ubyte +enum GzipFlag : ubyte { - FTEXT = 1, - FHCRC = 2, - FEXTRA = 4, - FNAME = 8, - FCOMMENT = 16 + ftext = 1, + fhcrc = 2, + fextra = 4, + fname = 8, + fcomment = 16 } Result zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) @@ -112,7 +112,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) 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); @@ -123,7 +123,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) } // skip file name if present - if (flg & gzip_flag.FNAME) + if (flg & GzipFlag.fname) { do { @@ -134,7 +134,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) } // skip file comment if present - if (flg & gzip_flag.FCOMMENT) + if (flg & GzipFlag.fcomment) { do { @@ -145,7 +145,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) } // check header crc if present - if (flg & gzip_flag.FHCRC) + if (flg & GzipFlag.fhcrc) { uint hcrc; From 8374c4f71de2a16083936ccf613be07c97d8fcfa Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 16:18:37 +1000 Subject: [PATCH 04/73] Improve SI unit/quantity --- src/urt/meta/package.d | 23 +- src/urt/si/quantity.d | 202 ++++++++++++--- src/urt/si/unit.d | 571 +++++++++++++++++++++++++++++++++-------- 3 files changed, 644 insertions(+), 152 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index e71470a..19f10f3 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -6,26 +6,29 @@ alias Alias(T) = T; alias AliasSeq(TList...) = TList; -template IntForWidth(size_t width, bool signed = false) +template IntForWidth(size_t bits, bool signed = false) { - static if (width <= 8 && !signed) + static if (bits <= 8 && !signed) alias IntForWidth = ubyte; - else static if (width <= 8 && signed) + else static if (bits <= 8 && signed) alias IntForWidth = byte; - else static if (width <= 16 && !signed) + else static if (bits <= 16 && !signed) alias IntForWidth = ushort; - else static if (width <= 16 && signed) + else static if (bits <= 16 && signed) alias IntForWidth = short; - else static if (width <= 32 && !signed) + else static if (bits <= 32 && !signed) alias IntForWidth = uint; - else static if (width <= 32 && signed) + else static if (bits <= 32 && signed) alias IntForWidth = int; - else static if (width <= 64 && !signed) + else static if (bits <= 64 && !signed) alias IntForWidth = ulong; - else static if (width <= 64 && signed) + 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!(); @@ -72,7 +75,7 @@ template enum_keys(E) } E enum_from_string(E)(const(char)[] key) - if (is(E == enum)) +if (is(E == enum)) { foreach (i, k; enum_keys!E) if (key[] == k[]) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 33462df..8fcecc6 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -1,6 +1,8 @@ module urt.si.quantity; +import urt.meta : TypeForOp; import urt.si.unit; +import urt.traits; nothrow @nogc: @@ -9,6 +11,12 @@ 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,25 +66,26 @@ 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); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); value = adjustScale(b); } } - + void opAssign()(T value) pure { 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,36 +94,44 @@ 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); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); value = adjustScale(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; + Quantity!(TypeForOp!(op, T, U), _unit) r; r.value = mixin("value " ~ op ~ " adjustScale(b)"); static if (Dynamic) r.unit = unit; @@ -120,7 +146,7 @@ nothrow @nogc: return mixin("this.value " ~ op ~ " value"); else { - This r; + Quantity!(TypeForOp!(op, T, U), unit) r; r.value = mixin("this.value " ~ op ~ " value"); static if (Dynamic) r.unit = unit; @@ -135,33 +161,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 (isSomeFloat!T || isSomeInt!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.adjustScale(this); + static if (T.Dynamic) + r.unit = unit; + return r; + } + } + bool opEquals(U)(U value) const pure if (is(U : T)) { @@ -185,14 +241,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,7 +256,7 @@ nothrow @nogc: auto rTrans = rh.unit.offset(); lhs = lhs*lScale + lTrans; rhs = rhs*rScale + rTrans; - } + }} else rhs = adjustScale(rh); @@ -222,6 +278,80 @@ nothrow @nogc: } } + auto normalise() const pure + { + static if (Dynamic) + { + Quantity!T r; + r.unit = ScaledUnit(unit.unit); + } + else + Quantity!(T, ScaledUnit(unit.unit)) r; + r.value = r.adjustScale(this); + return r; + } + + ptrdiff_t toString(char[] buffer) const + { + import urt.conv : format_float; + + double v = value; + ScaledUnit u = unit; + + if (u.pack) + { + // 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; + } + } + } + } + + ptrdiff_t l = format_float(v, buffer); + if (l < 0) + return l; + + if (u.pack) + { + ptrdiff_t l2 = u.toString(buffer[l .. $]); + if (l2 < 0) + return l2; + l += l2; + } + + return l; + } + + ptrdiff_t fromString(const(char)[] s) + { + return -1; + } + private: T adjustScale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure { diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index cf40a9e..1db2668 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: @@ -39,6 +42,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); @@ -61,6 +67,7 @@ enum Litre = ScaledUnit(CubicMetre, -3); enum Gram = ScaledUnit(Kilogram, -3); enum Milligram = ScaledUnit(Kilogram, -6); 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 +103,18 @@ enum UnitType : ubyte struct Unit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t; + ptrdiff_t l = toString(t); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -225,82 +243,39 @@ nothrow @nogc: this = this.opBinary!op(rh); } - ptrdiff_t toString(char[] buffer) const + ptrdiff_t toString(char[] buffer) 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.takePower(); + 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 +356,18 @@ enum ExtendedScaleFactor : ubyte struct ScaledUnit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t; + ptrdiff_t l = toString(t); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -574,6 +560,244 @@ nothrow @nogc: bool opEquals(Unit rh) const pure => (pack & 0xFF000000) ? false : unit == rh; + ptrdiff_t parseUnit(const(char)[] s, out float preScale) pure + { + preScale = 1; + + if (s.length == 0) + { + pack = 0; + return 0; + } + + size_t len = s.length; + if (s[0] == '-') + { + if (s.length == 1) + return -1; + preScale = -1; + s = s[1 .. $]; + } + + ScaledUnit r; + bool invert; + char sep; + while (const(char)[] term = s.split!('/', '*')(sep)) + { + int p = term.takePower(); + if (p == 0) + return -1; // invalid exponent + + if (const ScaledUnit* su = term in noScaleUnitMap) + r *= (*su) ^^ (invert ? -p : p); + else + { + size_t offset = 0; + + // parse the exponent + int e = 0; + if (term[0].is_numeric) + { + if (term[0] == '0') + { + if (term.length < 2 || term[1] != '.') + return -1; + e = 1; + offset = 2; + while (offset < term.length) + { + if (term[offset] == '1') + break; + if (term[offset] != '0') + return -1; + ++e; + ++offset; + } + ++offset; + e = -e; + } + else if (term[0] == '1') + { + offset = 1; + while (offset < term.length) + { + if (term[offset] != '0') + break; + ++e; + ++offset; + } + } + else + return -1; + } + + if (offset == term.length) + r *= ScaledUnit(Unit(), 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' + 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 noScaleUnitMap) + { + r *= (*su) ^^ (invert ? -p : p); + preScale *= 10^^e; + } + else + return -1; // string was not taken? + } + } + + if (sep == '/') + invert = true; + } + this = r; + return len; + } + + ptrdiff_t toString(char[] buffer) const pure + { + if (!unit.pack) + { + if (siScale && exp == -2) + { + if (buffer.length == 0) + return -1; + buffer[0] = '%'; + return 1; + } + else + assert(false, "TODO!"); + } + + 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.length < 2) + return -1; + --x; + buffer[0..2] = "10"; + len += 2; + } + else + { + if (buffer.length < 3) + return -1; + x -= 2; + buffer[0..3] = "100"; + len += 3; + } + } + assert(x >= -30, "TODO: handle this very small case"); + + if (x != 0) + { + if (buffer.length <= len) + return -1; + buffer[len++] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + } + } + + if (const string* name = unit in unitNames) + { + 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.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 = parseUnit(s, scale); + if (scale != 1) + return -1; + return r; + } + + size_t toHash() const pure => pack; @@ -688,39 +912,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 +971,136 @@ 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, + "Ah" : AmpereHour, + "Wh" : WattHour, + "cy" : Cycle, + "Hz" : Hertz, + "psi" : PSI, + + // questionable... :/ + "VAh" : WattHour, + "varh" : WattHour, +]; + +immutable ScaledUnit[string] scaledUnitMap = [ + "%" : Percent, + "‰" : Permille, + "l" : Litre, + "g" : Gram, +]; + +int takePower(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; +} From f0fc1600bfff67ec1cab377927829b83336e6a5c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 19 Aug 2025 20:47:19 +1000 Subject: [PATCH 05/73] Improve variant support for Quantity Also improve user type support... which is kinda unrelated, but ya-know! --- src/urt/conv.d | 88 +++++ src/urt/format/json.d | 23 +- src/urt/inet.d | 10 + src/urt/si/quantity.d | 2 +- src/urt/variant.d | 805 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 758 insertions(+), 170 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index a20b6ee..dbbd3f9 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -253,6 +253,94 @@ unittest } +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; diff --git a/src/urt/format/json.d b/src/urt/format/json.d index a48faed..ca2cd26 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -23,28 +23,9 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u 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: diff --git a/src/urt/inet.d b/src/urt/inet.d index 6dccd11..4b41981 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -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; diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 8fcecc6..c8da35e 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -194,7 +194,7 @@ nothrow @nogc: // not clear if this should return the raw value, or the normalised value...? // T opCast(T)() const pure -// if (isSomeFloat!T || isSomeInt!T) +// 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?"); diff --git a/src/urt/variant.d b/src/urt/variant.d index 8567b46..cc89cf5 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1,9 +1,12 @@ 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.traits; @@ -65,80 +68,81 @@ 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.Uint64Flag; + flags = Flags.NumberUint; if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag; + 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.NumberUint64; + if (i <= int.max) + 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; - } - - this(float f) - { - flags = Flags.NumberFloat; + 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); - flags |= Flags.IsQuantity; - count = q.unit.pack; + if (q.unit.pack) + { + flags |= Flags.IsQuantity; + count = q.unit.pack; + } } this(const(char)[] s) // TODO: (S)(S s) @@ -209,19 +213,28 @@ nothrow @nogc: static if (is(T == class)) { count = UserTypeId!T; - value.p = cast(void*)&thing; + 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 +243,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 +293,230 @@ nothrow @nogc: return nodeArray.pushBack(); } + bool opEquals(T)(ref const Variant rhs) const pure + { + return opCmp(rhs) == 0; + } + bool opEquals(T)(auto ref const T rhs) const + { + // 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.String || 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(); + + if (a.isQuantity || b.isQuantity) + { + // we can't compare different units + uint aunit = a.isQuantity ? (a.count & 0xFFFFFF) : 0; + uint bunit = b.isQuantity ? (b.count & 0xFFFFFF) : 0; + if (aunit != bunit) + { + r = aunit - bunit; + break; + } + + // matching units, but we'll only do quantity comparison if there is some scaling + ubyte ascale = a.isQuantity ? (a.count >> 24) : 0; + ubyte bscale = b.isQuantity ? (b.count >> 24) : 0; + if (ascale || bscale) + { + 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.FloatFlag || b.flags & Flags.FloatFlag) + { + // 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.String: + if (b.type != Type.String) + { + r = -1; + break; + } + r = compare(a.asString(), b.asString()); + 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 + { + // 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 @@ -302,42 +553,77 @@ nothrow @nogc: if ((flags & Flags.TypeMask) != Type.User) return false; static if (EmbedUserType!T) - return alloc == UserTypeShortId!T; + return alloc == UserTypeId!T; else return count == UserTypeId!T; } + 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 +635,40 @@ nothrow @nogc: return cast(double)cast(long)value.ul; } + float asFloat() const pure @property + { + 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!T asQuantity(T = double)() const pure @property + if (is_some_float!T || isSomeInt!T) { - assert(isNumber()); - - 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()) + if (isNull) + return Quantity!T(0); + assert(isNumber); + Quantity!T r; + r.value = as!T; + if (isQuantity) r.unit.pack = count; return r; } 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]; @@ -415,11 +708,72 @@ nothrow @nogc: static if (is(T == class)) return cast(inout(T))ptr; else static if (EmbedUserType!T) - static assert(false, "TODO: memcpy to a stack local and return that..."); + { + T r = void; + TypeDetailsFor!T.copy_emplace(embed.ptr, &r, false); + return r; + } else static assert(false, "Should be impossible?"); } + auto as(T)() inout pure + if (!ValidUserType!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 = asInt(); + 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 : 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!T) + return asUser!T; + else + static assert(false, "TODO!"); + } + ref inout(T) as(T)() inout pure + if (ValidUserType!T && UserTypeReturnByRef!T) + => asUser!T; + size_t length() const pure { if (flags == Flags.Null) @@ -429,7 +783,7 @@ nothrow @nogc: else if (isArray()) return count; else - assert(false); + assert(false, "Variant does not have `length`"); } bool empty() const pure @@ -479,41 +833,27 @@ nothrow @nogc: { final switch (type) { - case Variant.Type.Null: + 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] = "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; + 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().format_float(buffer); // TODO: parse args? - //format + assert(!format, "TODO"); if (flags & Flags.Uint64Flag) return asUlong().format_uint(buffer); @@ -540,12 +880,108 @@ nothrow @nogc: 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); else - return findTypeDetails(count).stringify(ptr, buffer); + return type_details[alloc].stringify(cast(void*)ptr, buffer, true); } } + 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; + ulong div; + long i = s.parse_int_with_decimal(div, &taken, 10); + if (taken < s.length) + { + size_t t2 = unit.fromString(s[taken .. $]); + if (t2 > 0) + taken += t2; + } + if (taken == s.length) + { + if (div != 1) + this = double(i) / div; + 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 .. num_type_details) + { + debug assert(type_details[i].alignment <= 64 && type_details[i].size <= buffer.sizeof, "Buffer is too small for user type!"); + ptrdiff_t taken = type_details[i].stringify(type_details[i].embedded ? embed.ptr : buffer.ptr, cast(char[])s, false); + if (taken > 0) + { + flags = Flags.User; + if (type_details[i].destroy) + flags |= Flags.NeedDestruction; + if (type_details[i].embedded) + { + flags |= Flags.Embedded; + alloc = cast(ushort)type_details[i].type_id; + } + else + { + void* object = defaultAllocator().alloc(type_details[i].size, type_details[i].alignment).ptr; + type_details[i].copy_emplace(buffer.ptr, object, true); + if (type_details[i].destroy) + type_details[i].destroy(buffer.ptr); + ptr = object; + count = type_details[i].type_id; + alloc = i; + } + return taken; + } + } + + // what is this? + assert(false, "Can't parse variant from string"); + } + + package: union Value { @@ -586,6 +1022,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,18 +1060,19 @@ 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) : 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, Array = 5, @@ -696,8 +1146,14 @@ import urt.hash : fnv1a; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); -enum uint UserTypeId(T) = fnv1a(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); @@ -717,42 +1173,95 @@ template MakeTypeDetails(T) // 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(num_type_details < type_details.length, "Too many user types!"); + type_details[num_type_details++] = TypeDetailsFor!T; } alias MakeTypeDetails = void; } +ushort type_detail_index(T)() + if (ValidUserType!T) +{ + foreach (i; 0 .. num_type_details) + if (type_details[i].type_id == UserTypeId!T) + return i; + assert(false, "Why wasn't the type registered?"); +} + struct TypeDetails { - uint typeId; + uint 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 format) 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] type_details; +__gshared ushort num_type_details = 0; -ref TypeDetails findTypeDetails(uint typeId) +ref TypeDetails find_type_details(uint type_id) { - foreach (i, ref td; typeDetails[0 .. numTypeDetails]) + foreach (i, ref td; type_details[0 .. num_type_details]) { - if (td.typeId == typeId) + if (td.type_id == type_id) return td; } assert(false, "TypeDetails not found!"); } +ref TypeDetails get_type_details(uint index) +{ + debug assert(index < num_type_details); + return type_details[index]; +} + +enum TypeDetailsFor(T) = TypeDetails(UserTypeId!T, + T.sizeof, + T.alignof, + EmbedUserType!T, + // moveEmplace + is(T == class) ? null : (void* src, void* dst, bool move) { + if (move) + moveEmplace(*cast(T*)src, *cast(T*)dst); + else + *cast(T*)dst = *cast(const T*)src; + }, + // destroy + is(T == class) ? null : (void* val) { + destroy!false(*cast(T*)val); + }, + // stringify + (void* val, char[] buffer, bool format) { + import urt.string.format : toString; + if (format) + return toString(*cast(const T*)val, buffer); + else + { + static if (__traits(compiles, { buffer.parse!T(*cast(T*)val); })) + return buffer.parse!T(*cast(T*)val); + else + return -1; + } + }, + // cmp + (const void* pa, const void* pb, int type) { + ref const T a = *cast(const T*)pa; + ref const T b = *cast(const T*)pb; + switch (type) + { + case 0: + static if (__traits(compiles, { a.opCmp(b); })) + return a.opCmp(b); + else + return a < b ? -1 : a > b ? 1 : 0; + case 1: + return a == b ? 1 : 0; + case 2: + return a is b ? 1 : 0; + default: + assert(false); + } + }); From 225132d41428c14baef7e7243f65f645fa197867 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 17:26:03 +1000 Subject: [PATCH 06/73] Added ChaCha stream cypher --- src/urt/crypto/chacha.d | 299 ++++++++++++++++++++++++++++++++++++++++ src/urt/digest/md5.d | 4 +- src/urt/digest/sha.d | 8 +- src/urt/encoding.d | 45 ++++-- 4 files changed, 339 insertions(+), 17 deletions(-) create mode 100644 src/urt/crypto/chacha.d 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/digest/md5.d b/src/urt/digest/md5.d index ba1c4c7..e0a59d6 100644 --- a/src/urt/digest/md5.d +++ b/src/urt/digest/md5.d @@ -93,12 +93,12 @@ unittest MD5Context ctx; md5_init(ctx); auto digest = md5_finalise(ctx); - assert(digest == Hex!"d41d8cd98f00b204e9800998ecf8427e"); + assert(digest == HexDecode!"d41d8cd98f00b204e9800998ecf8427e"); md5_init(ctx); md5_update(ctx, "Hello, World!"); digest = md5_finalise(ctx); - assert(digest == Hex!"65a8e27d8879283831b664bd8b7f0ad4"); + assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 197b7f4..671a6fa 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -117,22 +117,22 @@ unittest SHA1Context ctx; sha_init(ctx); auto digest = sha_finalise(ctx); - assert(digest == Hex!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(digest == HexDecode!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); sha_init(ctx); sha_update(ctx, "Hello, World!"); digest = sha_finalise(ctx); - assert(digest == Hex!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); SHA256Context ctx2; sha_init(ctx2); auto digest2 = sha_finalise(ctx2); - assert(digest2 == Hex!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + assert(digest2 == HexDecode!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); sha_init(ctx2); sha_update(ctx2, "Hello, World!"); digest2 = sha_finalise(ctx2); - assert(digest2 == Hex!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); } diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 54eb70d..43fa85e 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -3,13 +3,17 @@ 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 source_length) pure => (source_length + 2) / 3 * 4; +ptrdiff_t base64_encode_length(const void[] data) pure + => base64_encode_length(data.length); + ptrdiff_t base64_encode(const void[] data, char[] result) pure { auto src = cast(const(ubyte)[])data; @@ -55,7 +59,10 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure } ptrdiff_t base64_decode_length(size_t source_length) pure -=> source_length / 4 * 3; + => source_length / 4 * 3; + +ptrdiff_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]); } +ptrdiff_t hex_encode_length(size_t sourceLength) pure + => sourceLength * 2; + +ptrdiff_t hex_encode_length(const void[] data) pure + => data.length * 2; ptrdiff_t hex_encode(const void[] data, char[] result) pure { @@ -142,6 +157,12 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure return toHexString(data, result).length; } +ptrdiff_t hex_decode_length(size_t sourceLength) pure + => sourceLength / 2; + +ptrdiff_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 : is_hex; @@ -180,14 +201,14 @@ 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]); } @@ -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; From fa63396fa3a486fe40afd455c78fc758e54f099a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 26 Aug 2025 11:23:00 +1000 Subject: [PATCH 07/73] Remove a couple of dangling phobos references. --- src/urt/lifetime.d | 12 ++++++------ src/urt/math.d | 1 - src/urt/range/package.d | 9 +++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/urt/lifetime.d b/src/urt/lifetime.d index acb04d1..cb6cf40 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -3,8 +3,6 @@ module urt.lifetime; T* emplace(T)(T* chunk) @safe pure { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk); return chunk; } @@ -12,8 +10,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 +69,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."); @@ -84,6 +79,11 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) } +// HACK: we should port this to our lib... +static import core.internal.lifetime; +alias emplaceRef = core.internal.lifetime.emplaceRef; + + /+ void copyEmplace(S, T)(ref S source, ref T target) @system if (is(immutable S == immutable T)) diff --git a/src/urt/math.d b/src/urt/math.d index 236c867..66217d9 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -2,7 +2,6 @@ module urt.math; import urt.intrinsic; import core.stdc.stdio; // For writeDebugf -import std.format; // For format version (LDC) version = GDC_OR_LDC; version (GNU) version = GDC_OR_LDC; diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 3592b11..94af160 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -182,13 +182,10 @@ 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!(is_input_range!R, ElementType!R, ForeachType!R); alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); @@ -199,7 +196,7 @@ template reduce(fun...) { 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(); @@ -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; @@ -323,7 +320,7 @@ template fold(fun...) } else { - import std.typecons : tuple; + import urt.meta : tuple; return reduce!fun(tuple(seeds), r); } } From da335b59da5351da6836dd9dc8a888607953930a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 2 Sep 2025 13:10:33 +1000 Subject: [PATCH 08/73] Tweak the Promise API --- src/urt/async.d | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/urt/async.d b/src/urt/async.d index 5da7e61..81e0a19 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -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; } @@ -84,15 +84,15 @@ void asyncUpdate() } -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); From c50deb8756eb449657cee93c812331e5732f14de Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 3 Sep 2025 11:53:53 +1000 Subject: [PATCH 09/73] Add StringResult --- src/urt/result.d | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/urt/result.d b/src/urt/result.d index 3769e8b..2462386 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -19,6 +19,24 @@ 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) { From cccff2615a6c227f9af5ebf7234ed6e8fa93b83c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 3 Sep 2025 17:00:17 +1000 Subject: [PATCH 10/73] Minor improvements to urt.time --- src/urt/time.d | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 901884a..d1d0e00 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -100,14 +100,22 @@ 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 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; + } } auto __debugOverview() const @@ -410,7 +418,7 @@ DateTime getDateTime() static assert(false, "TODO"); } -DateTime getDateTime(SysTime time) +DateTime getDateTime(SysTime time) pure { version (Windows) return fileTimeToDateTime(time); @@ -553,13 +561,14 @@ unittest 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; @@ -578,10 +587,11 @@ version (Windows) } 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); From aa707f58c62f5043634bc77f4c64bbd95c629c5c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Sep 2025 10:37:44 +1000 Subject: [PATCH 11/73] Improved the compare function a bit --- src/urt/algorithm.d | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 9191f28..1e603fa 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -10,36 +10,44 @@ nothrow @nogc: auto compare(T, U)(auto ref T a, auto ref U b) { - static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!U))) + static if (__traits(compiles, a.opCmp(b))) return a.opCmp(b); - else static if (__traits(compiles, lvalue_of!U.opCmp(lvalue_of!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 : 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 (is_primitive!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; From f2311afaac943e95ede4a16ca9cec75f992affd3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Sep 2025 10:54:17 +1000 Subject: [PATCH 12/73] Made CacheString pure, since the cache is immutable. --- src/urt/mem/string.d | 91 ++++++++++++++++++++++------------------- src/urt/string/string.d | 7 ++-- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index b00618a..1cefa4e 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -18,40 +18,48 @@ 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; @@ -79,13 +87,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 +110,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 +199,6 @@ private: __gshared bool stringHeapInitialised = false; __gshared char[] stringHeap = null; __gshared ushort stringHeapCursor = 0; -__gshared uint numStrings = 0; unittest diff --git a/src/urt/string/string.d b/src/urt/string/string.d index a6d73d4..b2f7abf 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -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 @@ -87,11 +86,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"); From 72fc423fb18cb0257d62585f2eea51fad4c1de82 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Sep 2025 11:45:59 +1000 Subject: [PATCH 13/73] Improved user variants. - can resolve class variants to base classes - added pure hack for TypeDetails lookup, since the table is immutable after construction - const-correctness fixes - readability improvements --- src/urt/variant.d | 283 +++++++++++++++++++++++++++++++--------------- 1 file changed, 192 insertions(+), 91 deletions(-) diff --git a/src/urt/variant.d b/src/urt/variant.d index cc89cf5..e95a557 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -15,6 +15,7 @@ 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) && @@ -213,6 +214,7 @@ nothrow @nogc: static if (is(T == class)) { count = UserTypeId!T; + alloc = type_detail_index!T(); ptr = cast(void*)thing; } else static if (EmbedUserType!T) @@ -547,15 +549,32 @@ nothrow @nogc: => 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 == UserTypeId!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 @@ -690,27 +709,30 @@ 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) + else static if (EmbedUserType!U) { - T r = void; - TypeDetailsFor!T.copy_emplace(embed.ptr, &r, false); + // make a copy on the stack and return by value + U r = void; + TypeDetailsFor!U.copy_emplace(embed.ptr, &r, false); return r; } else @@ -718,7 +740,7 @@ nothrow @nogc: } auto as(T)() inout pure - if (!ValidUserType!T || !UserTypeReturnByRef!T) + if (!ValidUserType!(Unqual!T) || !UserTypeReturnByRef!T) { static if (is_some_int!T) { @@ -765,13 +787,13 @@ nothrow @nogc: else return asString; } - else static if (ValidUserType!T) + else static if (ValidUserType!(Unqual!T)) return asUser!T; else static assert(false, "TODO!"); } ref inout(T) as(T)() inout pure - if (ValidUserType!T && UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) => asUser!T; size_t length() const pure @@ -882,7 +904,7 @@ nothrow @nogc: if (flags & Flags.Embedded) return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true); else - return type_details[alloc].stringify(cast(void*)ptr, buffer, true); + return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true); } } @@ -949,28 +971,29 @@ nothrow @nogc: align(64) void[256] buffer = void; this = null; // clear the object since we'll probably use the embed buffer... - foreach (ushort i; 0 .. num_type_details) + foreach (ushort i; 0 .. g_num_type_details) { - debug assert(type_details[i].alignment <= 64 && type_details[i].size <= buffer.sizeof, "Buffer is too small for user type!"); - ptrdiff_t taken = type_details[i].stringify(type_details[i].embedded ? embed.ptr : buffer.ptr, cast(char[])s, false); + 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); if (taken > 0) { flags = Flags.User; - if (type_details[i].destroy) + if (td.destroy) flags |= Flags.NeedDestruction; - if (type_details[i].embedded) + if (td.embedded) { flags |= Flags.Embedded; - alloc = cast(ushort)type_details[i].type_id; + alloc = cast(ushort)td.type_id; } else { - void* object = defaultAllocator().alloc(type_details[i].size, type_details[i].alignment).ptr; - type_details[i].copy_emplace(buffer.ptr, object, true); - if (type_details[i].destroy) - type_details[i].destroy(buffer.ptr); + 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 = type_details[i].type_id; + count = td.type_id; alloc = i; } return taken; @@ -1060,7 +1083,7 @@ package: nodeArray.destroy!false(); else if (t == Type.User) { - ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : type_details[alloc]; + 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)) @@ -1169,22 +1192,25 @@ 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(num_type_details < type_details.length, "Too many user types!"); - type_details[num_type_details++] = TypeDetailsFor!T; + 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)() +ushort type_detail_index(T)() pure if (ValidUserType!T) { - foreach (i; 0 .. num_type_details) - if (type_details[i].type_id == UserTypeId!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?"); } @@ -1192,6 +1218,7 @@ ushort type_detail_index(T)() struct TypeDetails { uint type_id; + uint super_type_id; ushort size; ubyte alignment; bool embedded; @@ -1200,68 +1227,142 @@ struct TypeDetails ptrdiff_t function(void* val, char[] buffer, bool format) nothrow @nogc stringify; int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } -__gshared TypeDetails[8] type_details; -__gshared ushort num_type_details = 0; +__gshared TypeDetails[8] g_type_details; +__gshared ushort g_num_type_details = 0; -ref TypeDetails find_type_details(uint type_id) +typeof(g_type_details)* type_details() => &g_type_details; +ushort num_type_details() => g_num_type_details; + +ref immutable(TypeDetails) find_type_details(uint type_id) pure { - foreach (i, ref td; type_details[0 .. num_type_details]) + 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.type_id == type_id) return td; } assert(false, "TypeDetails not found!"); } -ref TypeDetails get_type_details(uint index) +ref immutable(TypeDetails) get_type_details(uint index) pure { - debug assert(index < num_type_details); - return type_details[index]; + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + debug assert(index < g_num_type_details); + return (*tds)[index]; } -enum TypeDetailsFor(T) = TypeDetails(UserTypeId!T, - T.sizeof, - T.alignof, - EmbedUserType!T, - // moveEmplace - is(T == class) ? null : (void* src, void* dst, bool move) { - if (move) - moveEmplace(*cast(T*)src, *cast(T*)dst); - else - *cast(T*)dst = *cast(const T*)src; - }, - // destroy - is(T == class) ? null : (void* val) { - destroy!false(*cast(T*)val); - }, - // stringify - (void* val, char[] buffer, bool format) { - import urt.string.format : toString; - if (format) - return toString(*cast(const T*)val, buffer); - else - { - static if (__traits(compiles, { buffer.parse!T(*cast(T*)val); })) - return buffer.parse!T(*cast(T*)val); - else - return -1; - } - }, - // cmp - (const void* pa, const void* pb, int type) { - ref const T a = *cast(const T*)pa; - ref const T b = *cast(const T*)pb; - switch (type) - { - case 0: - static if (__traits(compiles, { a.opCmp(b); })) - return a.opCmp(b); - else - return a < b ? -1 : a > b ? 1 : 0; - case 1: - return a == b ? 1 : 0; - case 2: - return a is b ? 1 : 0; - default: - assert(false); - } - }); +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(T == class) && 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 format) nothrow @nogc + { + import urt.string.format : toString; + if (format) + { + static if (is(typeof(toString!T))) + return toString(*cast(const T*)val, buffer); + 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); +} From d1b1cfea3b369cc5f17ea73b2f7894fada4153ae Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 5 Sep 2025 15:13:24 +1000 Subject: [PATCH 14/73] The body of Array.concat was just completely missing! --- src/urt/array.d | 82 ++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 8d9858c..b83a65a 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -288,7 +288,18 @@ nothrow @nogc: } // manipulation - ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things); + ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + { + reserve(_length + things.length); + static foreach (i; 0 .. things.length) + { + static if (is(T == class) || is(T == interface)) + ptr[_length++] = things[i]; + else + emplace!T(&ptr[_length++], forward!(things[i])); + } + return this; + } bool empty() const => _length == 0; @@ -318,26 +329,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 +356,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 +404,7 @@ nothrow @nogc: moveEmplace(ptr[i], ptr[i-1]); } destroy!false(ptr[--_length]); - return copy.move; + return copy; } } @@ -424,19 +414,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; } } From d2da65be3aca1384325b7cddab68adcd39eaee15 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 10 Sep 2025 17:41:25 +1000 Subject: [PATCH 15/73] Fix map range --- src/urt/map.d | 75 +++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/urt/map.d b/src/urt/map.d index 732fc79..97a6f99 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -292,13 +292,13 @@ 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); private: nothrow: @@ -657,9 +657,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 +898,7 @@ unittest assert(map.get(2) is null); } - // Iteration (opApply) + // Iteration (range) { TestAVLTree map; map.insert(3, 30); @@ -908,25 +906,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 +951,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 +972,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); From 760585b07c2c2bbffdb9883844b4ffd8a1cedb49 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 12 Sep 2025 10:01:56 +1000 Subject: [PATCH 16/73] Override accept inheriting blocking from listening socket. --- src/urt/socket.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/urt/socket.d b/src/urt/socket.d index e61e29c..304edee 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -285,6 +285,9 @@ Result accept(Socket socket, out Socket connection, InetAddress* connectingSocke return socket_getlasterror(); else if (connectingSocketAddress) *connectingSocketAddress = 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; } From cc271b9f6a3112bfbf4604908a140301a5048fd8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 16 Sep 2025 13:57:28 +1000 Subject: [PATCH 17/73] Fixes to tstringz functions, which seemed to have 2 separate implementations! --- src/urt/mem/temp.d | 40 +++++++++++++------- src/urt/string/package.d | 80 +++++++++++----------------------------- 2 files changed, 47 insertions(+), 73 deletions(-) diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index aabe847..7c28cfc 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -16,11 +16,7 @@ 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 (alloc_offset + size > TempMemSize) alloc_offset = 0; @@ -73,17 +69,33 @@ 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 (alloc_offset + len + 1 > TempMemSize) - alloc_offset = 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 + alloc_offset; - 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'; - alloc_offset += 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; } diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 6a103d0..4a807e2 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -7,50 +7,12 @@ public import urt.string.tailstring; // seful string operations defined elsewhere public import urt.array : empty, popFront, popBack, takeFront, takeBack; public import urt.mem : strlen; +public import urt.mem.temp : tstringz, twstringz; +nothrow @nogc: -enum TempStringBufferLen = 1024; -enum TempStringMaxLen = TempStringBufferLen / 2; -static char[TempStringBufferLen] s_tempStringBuffer; -static size_t s_tempStringBufferPos = 0; - - -char[] allocTempString(size_t len) nothrow @nogc -{ - assert(len <= TempStringMaxLen); - - if (len <= TempStringBufferLen - s_tempStringBufferPos) - { - char[] s = s_tempStringBuffer[s_tempStringBufferPos .. s_tempStringBufferPos + len]; - s_tempStringBufferPos += len; - return s; - } - 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; -} - -wchar* twstringz(const(char)[] str) nothrow @nogc -{ - wchar[] buffer = cast(wchar[])allocTempString((str.length + 1) * 2); - - // TODO: actually decode UTF8 into UTF16!! - - foreach (i, c; str) - buffer[i] = c; - buffer[str.length] = 0; - return buffer.ptr; -} - -ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure { if (a.length != b.length) return a.length - b.length; @@ -63,7 +25,7 @@ ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure nothrow @nogc return 0; } -ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure { if (a.length != b.length) return a.length - b.length; @@ -76,24 +38,24 @@ ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc return 0; } -bool ieq(const(char)[] a, const(char)[] b) pure nothrow @nogc +bool ieq(const(char)[] a, const(char)[] b) pure => icmp(a, b) == 0; -bool startsWith(const(char)[] s, const(char)[] prefix) pure nothrow @nogc +bool startsWith(const(char)[] s, const(char)[] prefix) pure { if (s.length < prefix.length) return false; return s[0 .. prefix.length] == prefix[]; } -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[]; } -inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure nothrow @nogc +inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure { size_t first = 0, last = s.length; static if (Front) @@ -113,7 +75,7 @@ alias trimFront = trim!(true, false); alias trimBack = trim!(false, true); -inout(char)[] trimComment(char Delimiter)(inout(char)[] s) +inout(char)[] trimComment(char Delimiter)(inout(char)[] s) pure { size_t i = 0; for (; i < s.length; ++i) @@ -126,7 +88,7 @@ inout(char)[] trimComment(char Delimiter)(inout(char)[] s) 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,7 +110,7 @@ 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 Separator, bool HandleQuotes = true)(ref inout(char)[] s) pure { static if (HandleQuotes) int inQuotes = 0; @@ -176,7 +138,7 @@ inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] return t; } -inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) +inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) pure { sep = '\0'; int inQuotes = 0; @@ -204,7 +166,7 @@ inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) return t; } -char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc +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 +185,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,13 +228,13 @@ 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 : is_power_of_2; assert(group.is_power_of_2); @@ -307,7 +269,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 +281,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,7 +291,7 @@ unittest } -bool wildcardMatch(const(char)[] wildcard, const(char)[] value) +bool wildcardMatch(const(char)[] wildcard, const(char)[] value) pure { // TODO: write this function... From d225b3ac22fc768cd7ef882844bbc613e61ca778 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 15 Sep 2025 19:31:52 +1000 Subject: [PATCH 18/73] Properly detect integer variants fed via float (like numbers from command line). --- src/urt/math.d | 82 ++++++++++++++++++++++++++++++++++++++++++ src/urt/meta/package.d | 15 ++++++++ src/urt/variant.d | 32 ++++++++++++++--- 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/urt/math.d b/src/urt/math.d index 66217d9..3c66be8 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -3,6 +3,9 @@ module urt.math; import urt.intrinsic; import core.stdc.stdio; // For writeDebugf +// 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; @@ -63,6 +66,85 @@ extern(C) double acos(double x); } +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/meta/package.d b/src/urt/meta/package.d index 19f10f3..599d6c9 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -1,11 +1,26 @@ module urt.meta; +pure nothrow @nogc: alias Alias(alias a) = a; alias Alias(T) = T; alias AliasSeq(TList...) = TList; +ulong bit_mask(size_t bits) +{ + 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) diff --git a/src/urt/variant.d b/src/urt/variant.d index e95a557..52c004c 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -119,11 +119,35 @@ nothrow @nogc: this(F)(F f) if (is_some_float!F) { - static if (is(F == float)) - flags = Flags.NumberFloat; + import urt.math : float_is_integer; + + 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 - flags = Flags.NumberDouble; - value.d = f; + { + static if (is(F == float)) + flags = Flags.NumberFloat; + else + flags = Flags.NumberDouble; + value.d = f; + } } this(E)(E e) From c54e6da75611e005f751984bf7e6ab5cd2188646 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 23 Sep 2025 02:18:28 +1000 Subject: [PATCH 19/73] Array was missing slice-assign operators. Added remove-range function. --- src/urt/array.d | 63 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index b83a65a..1ef1377 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -273,6 +273,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); @@ -452,8 +454,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 @@ -461,13 +462,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)); } @@ -504,31 +529,43 @@ 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(i[1] <= _length && i[1] - i[0] == rh.length, "Range error"); + ptr[i[0] .. i[1]] = rh[]; + } + + size_t[2] opSlice(size_t dim : 0)(size_t x, size_t y) const pure { debug assert(y <= _length, "Range error"); - return [cast(uint)x, cast(uint)y]; + return [x, y]; } void opOpAssign(string op : "~", U)(auto ref U el) @@ -631,14 +668,14 @@ private: 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) @@ -659,7 +696,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; From a7690671588b4e19f462b04b6fcb3c0389ef5517 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 25 Sep 2025 09:02:19 +1000 Subject: [PATCH 20/73] Added comparison functions for InetAddress --- src/urt/inet.d | 155 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 3 deletions(-) diff --git a/src/urt/inet.d b/src/urt/inet.d index 4b41981..9f90c55 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -208,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; @@ -428,8 +440,11 @@ nothrow @nogc: IPv6Addr r; int i, j = prefixLen / 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 - (prefixLen % 16))); + while (i < 8) r.s[i++] = 0; + } return r; } @@ -449,6 +464,11 @@ nothrow @nogc: return true; } + IPv6Addr getNetwork(IPv6Addr ip) const pure + => ip & netMask(); + IPv6Addr getLocal(IPv6Addr ip) const pure + => ip & ~netMask(); + size_t toHash() const pure => addr.toHash() ^ prefixLen; @@ -532,11 +552,57 @@ nothrow @nogc: this._a.ipv4.port = port; } - this(IPv6Addr addr, ushort port) + this(IPv6Addr addr, ushort port, int flowInfo = 0, uint scopeId = 0) { family = AddressFamily.IPv6; this._a.ipv6.addr = addr; this._a.ipv6.port = port; + this._a.ipv6.flowInfo = flowInfo; + this._a.ipv6.scopeId = scopeId; + } + + 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.flowInfo == rhs._a.ipv6.flowInfo) + return _a.ipv6.scopeId - rhs._a.ipv6.scopeId; + return _a.ipv6.flowInfo - rhs._a.ipv6.flowInfo; + } + return _a.ipv6.port - rhs._a.ipv6.port; + default: + return 0; + } + return 0; } size_t toHash() const pure @@ -651,8 +717,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(IPSubnet(IPAddr(192, 168, 0, 0), 24).getNetwork(IPAddr(192, 168, 0, 10)) == IPAddr(192, 168, 0, 0)); + assert(IPSubnet(IPAddr(192, 168, 0, 0), 24).getLocal(IPAddr(192, 168, 0, 10)) == 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"); @@ -671,8 +741,13 @@ unittest assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPSubnet(IPAddr(0, 0, 0, 0), 0)); 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(IPv6Subnet(IPv6Addr.any, 64).getNetwork(IPv6Addr.loopback) == IPv6Addr.any); + assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getNetwork(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); + assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getLocal(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == 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"); @@ -704,4 +779,78 @@ unittest // 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)); + + // 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"); + } + } } From 42c8de0b91dad846bce7cb22fadcf8b82b0ab76a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 7 Oct 2025 15:35:48 +1000 Subject: [PATCH 21/73] Flesh out unicode implementation, including functions to perform case-insensitive comparison. --- src/urt/array.d | 60 +++- src/urt/mem/temp.d | 12 + src/urt/socket.d | 2 +- src/urt/string/package.d | 230 ++++++++++-- src/urt/string/uni.d | 755 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 1017 insertions(+), 42 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 1ef1377..702c861 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; 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) diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index 7c28cfc..f470dff 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -118,6 +118,18 @@ char[] tstring(T)(auto ref T value) 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; diff --git a/src/urt/socket.d b/src/urt/socket.d index 304edee..f27664c 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -746,7 +746,7 @@ Result get_hostname(char* name, size_t len) 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(':'); diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 4a807e2..3210e03 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -1,58 +1,218 @@ 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; +public import urt.mem : strlen, wcslen; public import urt.mem.temp : tstringz, twstringz; nothrow @nogc: -ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure +ptrdiff_t cmp(bool case_insensitive = false, T, U)(const(T)[] a, const(U)[] b) pure { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + static if (case_insensitive) + return uni_compare_i(a, b); + else { - ptrdiff_t diff = a[i] - b[i]; - if (diff) - return diff; + static if (is(T == U)) + { + if (a.length != b.length) + return a.length - b.length; + } + return uni_compare(a, b); } - return 0; } -ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure +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; + +size_t findFirst(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 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: 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; + + size_t i = 0; + while (i < s.length) { - ptrdiff_t diff = to_lower(a[i]) - to_lower(b[i]); - if (diff) - return diff; + 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 0; + return i; } -bool ieq(const(char)[] a, const(char)[] b) pure - => icmp(a, b) == 0; +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) +{ + 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) + { + 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 s.length; +} + +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 (t.length == 0) + return 0; + + // fast-path for one-length tokens + size_t l = t.uni_seq_len(); + if (l == t.length) + { + 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); + } + + 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"); +} + +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 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 { 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 @@ -305,3 +465,31 @@ bool wildcardMatch(const(char)[] wildcard, const(char)[] value) pure } return wildcard.length == value.length; } + + +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")); +} diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index bc3e44c..ce7f7d1 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -1,7 +1,142 @@ 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 uni_seq_len(const(char)[] s) +{ + if (s.length == 0) + return 0; + if (s[0] < 0x80) // 1-byte sequence: 0xxxxxxx + return 1; + else if ((s[0] & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + return (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + return (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return (s.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : + (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return 1; // Invalid UTF-8 sequence +} + +size_t uni_seq_len(const(wchar)[] s) +{ + if (s.length == 0) + return 0; + if (s[0] >= 0xD800 && s[0] < 0xDC00 && s.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) + => s.length > 0; + +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) { @@ -242,10 +377,504 @@ size_t uni_convert(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 (p1 < p1end && p2 < p2end) + { + dchar a = *p1; + if (a < 0x80) + { + dchar b = *p2; + if (a != b) + { + if (b >= 0x80) + { + size_t _; + b = next_dchar(p2[0 .. p2end - p2], _); + } + return cast(int)a - cast(int)b; + } + ++p1; + ++p2; + } + else + { + size_t al, bl; + a = next_dchar(p1[0 .. p1end - p1], al); + dchar b = next_dchar(p2[0 .. p2end - p2], bl); + if (a != b) + return cast(int)a - cast(int)b; + p1 += al; + p2 += al; + } + } + + // 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; +} + +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 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" ~ @@ -272,5 +901,127 @@ unittest // 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); +} From 78124e8a4511921f495e05e75d01ff0ca2934ead Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 14 Oct 2025 10:47:54 +1000 Subject: [PATCH 22/73] Several socket/inet improvements. --- src/urt/inet.d | 94 ++++++++-- src/urt/socket.d | 447 +++++++++++++++++++++++++++++++---------------- 2 files changed, 375 insertions(+), 166 deletions(-) diff --git a/src/urt/inet.d b/src/urt/inet.d index 9f90c55..e4c6d1a 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -35,7 +35,7 @@ enum WellKnownPort : ushort } 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 IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv6 address"); return a; }(); struct IPAddr { @@ -313,9 +313,45 @@ nothrow @nogc: 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_int(&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; } @@ -498,7 +534,7 @@ nothrow @nogc: return -1; size_t t; ulong plen = s[taken..$].parse_int(&t); - if (t == 0 || plen > 32) + if (t == 0 || plen > 128) return -1; addr = a; prefixLen = cast(ubyte)plen; @@ -679,7 +715,7 @@ nothrow @nogc: { size_t t; ulong p = s[++taken..$].parse_int(&t); - if (t == 0 || p > 0xFFFF) + if (t == 0 || p > ushort.max) return -1; taken += t; port = cast(ushort)p; @@ -739,6 +775,9 @@ unittest 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)); + 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)); @@ -755,17 +794,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)); + 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 .. 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)); + 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)); + 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"); @@ -777,8 +839,10 @@ 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 { diff --git a/src/urt/socket.d b/src/urt/socket.d index 304edee..7123163 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, @@ -116,12 +119,14 @@ enum SocketOption : ubyte multicast = first_ip_option, multicast_loopback, multicast_ttl, + ip_pktinfo, // IPv6 options first_ipv6_option, + ipv6_pktinfo = first_ipv6_option, // ICMP options - first_icmp_option = first_ipv6_option, + first_icmp_option, // ICMPv6 options first_icmpv6_option = first_icmp_option, @@ -134,7 +139,6 @@ enum SocketOption : ubyte tcp_keep_alive, // Apple: similar to KeepIdle tcp_no_delay, - // UDP options first_udp_option, } @@ -195,6 +199,7 @@ Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Sock socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); if (socket == Socket.invalid) return socket_getlasterror(); + return Result.success; } @@ -247,10 +252,10 @@ 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; } @@ -266,15 +271,15 @@ 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; } -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,15 +288,21 @@ 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); + 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; @@ -301,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); + 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; 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 @@ -362,30 +373,89 @@ Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t 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 = 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 - 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) @@ -393,9 +463,9 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval Result r = Result.success; // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); - assert(optlen == s_optTypeRtSize[optInfo.rt_type], "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 @@ -423,7 +493,7 @@ 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.platform_type == OptType.unsupported) +// if (opt_info.platform_type == OptType.unsupported) // return r; // } @@ -436,15 +506,15 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval const(void)* arg = optval; int itmp = void; linger ling = void; - if (optInfo.rt_type != optInfo.platform_type) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.rt_type) + switch (opt_info.rt_type) { // TODO: there are more converstions necessary as options/platforms are added case OptType.bool_: { const bool value = *cast(const(bool)*)optval; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.int_: itmp = value ? 1 : 0; @@ -457,7 +527,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval case OptType.duration: { const Duration value = *cast(const(Duration)*)optval; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.seconds: itmp = cast(int)value.as!"seconds"; @@ -482,53 +552,53 @@ 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.platform_type]); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.bool_, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.int_, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.duration, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.multicast_group, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.multicast_group, "Incorrect value type for option"); return set_socket_option(socket, option, &value, MulticastGroup.sizeof); } @@ -537,9 +607,9 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ Result r = Result.success; // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); - assert(outputlen == s_optTypeRtSize[optInfo.rt_type], "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.non_blocking, "Socket option NonBlocking cannot be get"); @@ -552,9 +622,9 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ void* arg = output; int itmp = 0; linger ling = { 0, 0 }; - if (optInfo.rt_type != optInfo.platform_type) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.int_: case OptType.seconds: @@ -573,19 +643,19 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - socklen_t writtenLen = s_optTypePlatformSize[optInfo.platform_type]; + 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.rt_type != optInfo.platform_type) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.rt_type) + switch (opt_info.rt_type) { // TODO: there are more converstions necessary as options/platforms are added case OptType.bool_: { bool* value = cast(bool*)output; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.int_: *value = !!itmp; @@ -597,7 +667,7 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ case OptType.duration: { Duration* value = cast(Duration*)output; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.seconds: *value = seconds(itmp); @@ -617,10 +687,10 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - assert(optInfo.rt_type != OptType.inet_addr, "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.rt_type) + switch (opt_info.rt_type) { case OptType.INAddress: { @@ -637,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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.bool_, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.int_, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.duration, "Incorrect value type for option"); + 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.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); return get_socket_option(socket, option, &output, IPAddr.sizeof); } @@ -978,7 +1048,7 @@ SocketResult socket_result(Result result) 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) { @@ -988,7 +1058,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ 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]; version (Windows) @@ -1013,7 +1083,7 @@ 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]; storeBigEndian(&ain6.sin6_port, cast(ushort)address._a.ipv6.port); @@ -1041,7 +1111,7 @@ 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]; // @@ -1053,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"); @@ -1061,52 +1131,33 @@ 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: { - 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: { 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.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!"); @@ -1116,7 +1167,7 @@ InetAddress make_InetAddress(const(sockaddr)* sockAddress) { // 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) @@ -1134,6 +1185,37 @@ 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: @@ -1277,6 +1359,8 @@ version (Windows) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA 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 ), @@ -1299,6 +1383,8 @@ else version (linux) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA 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_ ), @@ -1321,6 +1407,8 @@ else version (Darwin) 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 ), @@ -1379,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) @@ -1397,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); } From bf85e900f6de66d072e340bcce29b75784758439 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 19 Oct 2025 14:23:38 +1000 Subject: [PATCH 23/73] Renamed IPSubnet to IPNetworkAddress (because it stores a full address!) Also snake_case fixes, and some other minor tweaks. --- src/urt/inet.d | 242 ++++++++++++++++++++++++----------------------- src/urt/socket.d | 38 ++++---- 2 files changed, 143 insertions(+), 137 deletions(-) diff --git a/src/urt/inet.d b/src/urt/inet.d index e4c6d1a..b2d37bb 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 @@ -114,10 +114,10 @@ nothrow @nogc: return fnv1a(b[]); } - 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[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++) { @@ -126,7 +126,7 @@ nothrow @nogc: offset += b[i].format_int(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -190,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 @@ -228,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; @@ -253,12 +253,12 @@ nothrow @nogc: 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++) { @@ -269,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; } } @@ -288,11 +288,11 @@ 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; @@ -365,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; @@ -391,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.format_int(tmp[offset..$]); + offset += prefix_len.format_int(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -440,7 +440,7 @@ nothrow @nogc: if (t == 0 || plen > 32) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } @@ -453,32 +453,32 @@ 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; if (j < 8) { - r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefixLen % 16))); + r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefix_len % 16))); while (i < 8) r.s[i++] = 0; } return r; @@ -486,38 +486,38 @@ nothrow @nogc: 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 getNetwork(IPv6Addr ip) const pure - => ip & netMask(); - IPv6Addr getLocal(IPv6Addr ip) const pure - => ip & ~netMask(); + 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.format_int(tmp[offset..$]); + offset += prefix_len.format_int(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -537,7 +537,7 @@ nothrow @nogc: if (t == 0 || plen > 128) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } @@ -569,7 +569,7 @@ nothrow @nogc: { IPv6Addr addr; ushort port; - uint flowInfo; + uint flow_info; uint scopeId; } struct Addr @@ -583,20 +583,26 @@ 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, int flowInfo = 0, uint scopeId = 0) + 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.flowInfo = flowInfo; + 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; @@ -606,9 +612,9 @@ nothrow @nogc: return false; switch (family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: return _a.ipv4 == rhs._a.ipv4; - case AddressFamily.IPv6: + case AddressFamily.ipv6: return _a.ipv6 == rhs._a.ipv6; default: return true; @@ -621,18 +627,18 @@ nothrow @nogc: return family < rhs.family ? -1 : 1; switch (family) { - case AddressFamily.IPv4: + 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: + 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.flowInfo == rhs._a.ipv6.flowInfo) + if (_a.ipv6.flow_info == rhs._a.ipv6.flow_info) return _a.ipv6.scopeId - rhs._a.ipv6.scopeId; - return _a.ipv6.flowInfo - rhs._a.ipv6.flowInfo; + return _a.ipv6.flow_info - rhs._a.ipv6.flow_info; } return _a.ipv6.port - rhs._a.ipv6.port; default: @@ -643,19 +649,19 @@ nothrow @nogc: 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++] = ':'; @@ -670,7 +676,7 @@ nothrow @nogc: offset += _a.ipv6.port.format_int(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -689,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) @@ -723,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; @@ -732,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; @@ -756,9 +762,9 @@ unittest 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(IPSubnet(IPAddr(192, 168, 0, 0), 24).getNetwork(IPAddr(192, 168, 0, 10)) == IPAddr(192, 168, 0, 0)); - assert(IPSubnet(IPAddr(192, 168, 0, 0), 24).getLocal(IPAddr(192, 168, 0, 10)) == IPAddr(0, 0, 0, 10)); + 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"); @@ -769,12 +775,12 @@ 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); @@ -783,10 +789,10 @@ unittest 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(IPv6Subnet(IPv6Addr.any, 64).getNetwork(IPv6Addr.loopback) == IPv6Addr.any); - assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getNetwork(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); - assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getLocal(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0, 0, 0, 1, 0, 0, 0, 1)); + 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"); @@ -819,12 +825,12 @@ unittest assert(addr6.fromString("2001:db8::1/24") == 11 && 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"); + 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"); - 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)); + 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); @@ -895,13 +901,13 @@ unittest // InetAddress sorting tests { InetAddress[10] expected = [ - // IPv4 sorted first + // 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 + // 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), diff --git a/src/urt/socket.d b/src/urt/socket.d index 52bf820..7902c74 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -194,7 +194,7 @@ 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) @@ -500,7 +500,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval // 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; @@ -1052,7 +1052,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ switch (address.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { addrLen = sockaddr_in.sizeof; if (buffer.length < sockaddr_in.sizeof) @@ -1060,7 +1060,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ 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]; @@ -1075,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) { @@ -1085,9 +1085,9 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ 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) { @@ -1103,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) // { @@ -1113,7 +1113,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ // // 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; @@ -1140,7 +1140,7 @@ InetAddress make_InetAddress(const(sockaddr)* sock_address) 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)*)sock_address; @@ -1148,14 +1148,14 @@ InetAddress make_InetAddress(const(sockaddr)* sock_address) 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)*)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); addr._a.ipv6.addr = make_IPv6Addr(ain6.sin6_addr); } @@ -1163,7 +1163,7 @@ InetAddress make_InetAddress(const(sockaddr)* sock_address) assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { @@ -1267,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 = [ @@ -1475,7 +1475,7 @@ version (Windows) Socket dummy; uint bytes = 0; - if (!create_socket(AddressFamily.IPv4, SocketType.datagram, Protocol.udp, dummy)) + 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) From e345a97b992e6335c27c212c3b8193b2869b8b72 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 30 Oct 2025 11:09:39 +1000 Subject: [PATCH 24/73] Fix a really dumb bug. --- src/urt/system.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/system.d b/src/urt/system.d index 2bb58ec..903ce2a 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -99,7 +99,7 @@ void set_system_idle_params(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) { From 970bcaaef41c559d9b54a1f6ee459db76dc5c62a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 3 Nov 2025 21:52:37 +1000 Subject: [PATCH 25/73] Added ascii strcmp/icmp --- src/urt/string/ascii.d | 44 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/urt/string/ascii.d b/src/urt/string/ascii.d index 9edab53..36f814f 100644 --- a/src/urt/string/ascii.d +++ b/src/urt/string/ascii.d @@ -67,16 +67,20 @@ char to_upper(char c) pure char[] to_lower(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = to_lower(str[i]); - return buffer; + buffer[i] = str[i].to_lower; + return buffer[0..str.length]; } char[] to_upper(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = to_upper(str[i]); - return buffer; + buffer[i] = str[i].to_upper; + return buffer[0..str.length]; } char[] to_lower(char[] str) pure @@ -88,3 +92,35 @@ char[] to_upper(char[] str) pure { 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; From 5c0fb18a01229ffcc261205548d3a093d15bfb15 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 3 Nov 2025 22:00:13 +1000 Subject: [PATCH 26/73] Added toString/fromString for common time types. Improved precision for stringification down to nanoseconds. Added unit tests. --- src/urt/time.d | 377 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 344 insertions(+), 33 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index d1d0e00..b8dd4be 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -107,17 +107,33 @@ pure nothrow @nogc: } else { - long ms = (ticks != 0 ? appTime(this) : Duration()).as!"msecs"; + long ns = (ticks != 0 ? appTime(this) : Duration()).as!"nsecs"; if (!buffer.ptr) - return 2 + timeToString(ms, null); + return 2 + timeToString(ns, null); if (buffer.length < 2) return -1; buffer[0..2] = "T+"; - ptrdiff_t len = timeToString(ms, buffer[2..$]); + 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 { debug @@ -191,7 +207,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 @@ -290,15 +397,6 @@ pure nothrow @nogc: import urt.conv : format_int; size_t offset = 0; - uint y = year; - if (year <= 0) - { - if (buffer.length < 3) - return -1; - y = -year + 1; - buffer[0 .. 3] = "BC "; - offset += 3; - } ptrdiff_t len = year.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; @@ -313,7 +411,7 @@ pure nothrow @nogc: if (len < 0 || len == buffer.length) return -1; offset += len; - buffer[offset++] = ' '; + buffer[offset++] = 'T'; len = hour.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; @@ -325,15 +423,149 @@ pure nothrow @nogc: offset += len; buffer[offset++] = ':'; len = second.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + if (len < 0) return -1; offset += len; - buffer[offset++] = '.'; - len = (ns / 1_000_000).format_int(buffer[offset..$], 10, 3, '0'); - if (len < 0) - return len; + if (ns) + { + if (len == buffer.length) + return -1; + buffer[offset++] = '.'; + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + if (len == buffer.length) + return -1; + int digit = nsecs / digit_multipliers[m]; + buffer[offset++] = cast(char)('0' + digit); + nsecs -= digit * digit_multipliers[m++]; + } + } return offset + len; } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int; + import urt.string.ascii : ieq, is_numeric, is_whitespace, to_lower; + + if (s.length < 14) + return -1; + size_t offset = 0; + + // parse year + if (s[0..2].ieq("bc")) + { + offset = 2; + if (s[2] == ' ') + ++offset; + } + if (s[offset] == '+') + return -1; + 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; + + if (s[offset] != '-' && s[offset] != '/') + return -1; + + // parse month + value = s[++offset..$].parse_int(&len); + if (len == 0) + { + 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; + + if (s[offset] != '-' && s[offset] != '/') + return -1; + + // parse day + value = s[++offset..$].parse_int(&len); + if (len == 0 || value < 1 || value > 31) + return -1; + day = cast(ubyte)value; + offset += len; + + if (offset >= s.length || (s[offset] != 'T' && s[offset] != ' ')) + return -1; + + // parse hour + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 23) + return -1; + hour = cast(ubyte)value; + offset += len; + + if (offset >= s.length || s[offset++] != ':') + return -1; + + // 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 || s[offset++] != ':') + return -1; + + // parse second + value = s[offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + second = cast(ubyte)value; + offset += len; + + ns = 0; + 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 -1; // no number after the dot + } + + if (offset < s.length && s[offset].to_lower == 'z') + { + ++offset; + // TODO: UTC timezone designator... + assert(false, "TODO: we need to know our local timezone..."); + } + + return offset; + } } Duration dur(string base)(long value) pure @@ -403,6 +635,10 @@ SysTime getSysTime() } } +SysTime getSysTime(DateTime time) pure +{ + assert(false, "TODO: convert to SysTime..."); +} DateTime getDateTime() { @@ -459,6 +695,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; @@ -517,22 +756,38 @@ 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 : format_int; - long hr = ms / 3_600_000; + long hr = ns / 3_600_000_000_000; if (!buffer.ptr) - return hr.format_int(null, 10, 2, '0') + 10; + { + size_t tail = 6; + ns %= 1_000_000_000; + if (ns) + { + ++tail; + uint m = 0; + do + { + ++tail; + uint digit = cast(uint)(ns / digit_multipliers[m]); + ns -= digit * digit_multipliers[m++]; + } + while (ns); + } + return hr.format_int(null, 10, 2, '0') + tail; + } ptrdiff_t len = hr.format_int(buffer, 10, 2, '0'); - if (len < 0 || buffer.length < len + 10) + 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; + ubyte min = cast(ubyte)(ns / 60_000_000_000 % 60); + ubyte sec = cast(ubyte)(ns / 1_000_000_000 % 60); + ns %= 1_000_000_000; buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (min / 10)); @@ -540,10 +795,21 @@ 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 (ns) + { + if (buffer.length < len + 2) + return -1; + buffer.ptr[len++] = '.'; + uint m = 0; + while (ns) + { + if (buffer.length < len + 1) + return -1; + uint digit = cast(uint)(ns / digit_multipliers[m]); + buffer.ptr[len++] = cast(char)('0' + digit); + ns -= digit * digit_multipliers[m++]; + } + } return len; } @@ -552,10 +818,55 @@ 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(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.") == -1); + assert(dt.fromString("2025-01-01") == -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); } From d5527350f783423107967fae2d3c6d8df375b07b Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 3 Nov 2025 22:08:21 +1000 Subject: [PATCH 27/73] Added support for `Duration` to `Variant`, which is a synonym for `Quantity!Nanoseconds`. Since both types are functionally equivalent, they can be interchanged. --- src/urt/si/unit.d | 1 + src/urt/variant.d | 48 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1db2668..480ab98 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -66,6 +66,7 @@ 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; diff --git a/src/urt/variant.d b/src/urt/variant.d index 52c004c..a086c33 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -8,7 +8,8 @@ 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: @@ -20,7 +21,8 @@ enum ValidUserType(T) = (is(T == struct) || is(T == class)) && !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); @@ -170,6 +172,13 @@ nothrow @nogc: } } + this(Duration dur) + { + this(dur.as!"nsecs"); + flags |= Flags.IsQuantity; + count = Nanosecond.pack; + } + this(const(char)[] s) // TODO: (S)(S s) // if (is(S : const(char)[])) { @@ -567,6 +576,8 @@ nothrow @nogc: => (flags & Flags.DoubleFlag) != 0; bool isQuantity() const pure => (flags & Flags.IsQuantity) != 0; + bool isDuration() const pure + => isQuantity && (count & 0xFFFFFF) == Second.pack; bool isString() const pure => (flags & Flags.IsString) != 0; bool isArray() const pure @@ -707,6 +718,35 @@ nothrow @nogc: 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(char)[] asString() const pure { if (isNull) @@ -804,6 +844,10 @@ nothrow @nogc: { 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 From 7b57ce8ca3a14a2d5f9dd70d761064b370bffde6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 5 Nov 2025 22:16:42 +1000 Subject: [PATCH 28/73] Fixed format for Quantity and related types. --- src/urt/si/quantity.d | 5 +++-- src/urt/si/unit.d | 14 ++++++++------ src/urt/variant.d | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index c8da35e..880166a 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -291,7 +291,8 @@ nothrow @nogc: return r; } - ptrdiff_t toString(char[] buffer) const + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const { import urt.conv : format_float; @@ -338,7 +339,7 @@ nothrow @nogc: if (u.pack) { - ptrdiff_t l2 = u.toString(buffer[l .. $]); + ptrdiff_t l2 = u.toString(buffer[l .. $], null, null); if (l2 < 0) return l2; l += l2; diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 480ab98..1b98a6a 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -108,8 +108,8 @@ nothrow: // debug/ctfe helper string toString() pure { - char[32] t; - ptrdiff_t l = toString(t); + 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; @@ -244,7 +244,8 @@ nothrow: this = this.opBinary!op(rh); } - ptrdiff_t toString(char[] buffer) const pure + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { assert(false, "TODO"); } @@ -361,8 +362,8 @@ nothrow: // debug/ctfe helper string toString() pure { - char[32] t; - ptrdiff_t l = toString(t); + 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; @@ -706,7 +707,8 @@ nothrow: return len; } - ptrdiff_t toString(char[] buffer) const pure + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { if (!unit.pack) { diff --git a/src/urt/variant.d b/src/urt/variant.d index a086c33..febad39 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -937,7 +937,7 @@ nothrow @nogc: case Variant.Type.Number: if (isQuantity()) - return asQuantity().toString(buffer);//, format, formatArgs); + return asQuantity().toString(buffer, format, formatArgs); if (isDouble()) return asDouble().format_float(buffer); From 42f5ac166a610175778ccc9e1f52c2d1dfbab8db Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 5 Nov 2025 22:21:03 +1000 Subject: [PATCH 29/73] Add a way to register simple log sinks. TODO: more to come on this... --- src/urt/log.d | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/urt/log.d b/src/urt/log.d index 71af7fd..029718c 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("{0}: {@-2}", levelNames[level], things, 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; From 0cf0f6b23f5d6a15f58e2face397f545c17c8eac Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 6 Nov 2025 03:24:50 +1000 Subject: [PATCH 30/73] Fix the DateTime stringify function. It didn't previously report the needed length when supplied a null buffer. Also, the ISO 8601 timestamp format requires padded month-day. Also, fixed a bug! --- src/urt/time.d | 60 +++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index b8dd4be..80accd5 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -394,55 +394,65 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - import urt.conv : format_int; + import urt.conv : format_int, format_uint; + + ptrdiff_t len; + if (!buffer.ptr) + { + 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; + } size_t offset = 0; - ptrdiff_t len = year.format_int(buffer[offset..$]); + len = year.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '-'; - len = month.format_int(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.format_int(buffer[offset..$]); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (day / 10); + buffer[offset++] = '0' + (day % 10); buffer[offset++] = 'T'; - len = hour.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (hour / 10); + buffer[offset++] = '0' + (hour % 10); buffer[offset++] = ':'; - len = minute.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (minute / 10); + buffer[offset++] = '0' + (minute % 10); buffer[offset++] = ':'; - len = second.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0) - return -1; - offset += len; + buffer[offset++] = '0' + (second / 10); + buffer[offset++] = '0' + (second % 10); if (ns) { - if (len == buffer.length) + if (offset == buffer.length) return -1; buffer[offset++] = '.'; uint nsecs = ns; uint m = 0; while (nsecs) { - if (len == buffer.length) + if (offset == buffer.length) return -1; int digit = nsecs / digit_multipliers[m]; buffer[offset++] = cast(char)('0' + digit); nsecs -= digit * digit_multipliers[m++]; } } - return offset + len; + return offset; } ptrdiff_t fromString(const(char)[] s) From 294a273913b0fe8cfb54da3b9d9e5660e2d40209 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 10 Nov 2025 23:22:10 +1000 Subject: [PATCH 31/73] Improve StringLit interaction with immutable. --- src/urt/string/string.d | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index b2f7abf..4fa8cdb 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -57,12 +57,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) @@ -187,7 +186,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; From 9b6bebcdfb6f9be352f0a0515f87e354b943fb12 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 10 Nov 2025 23:33:13 +1000 Subject: [PATCH 32/73] Added EnumInfo for efficient runtime enum lookup. --- src/urt/algorithm.d | 64 ++++++++--- src/urt/meta/package.d | 235 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 278 insertions(+), 21 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 1e603fa..0484777 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -1,6 +1,7 @@ module urt.algorithm; -import urt.traits : lvalue_of; +import urt.meta : AliasSeq; +import urt.traits : is_some_function, lvalue_of, Parameters, ReturnType, Unqual; import urt.util : swap; version = SmallSize; @@ -56,8 +57,36 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } +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*))) +{ + const void* p = arr.ptr; + size_t low = 0; + size_t high = arr.length; + 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 == arr.length) + return arr.length; + if (pred(p + low*stride, value) == 0) + return low; + return arr.length; +} + 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; @@ -82,6 +111,8 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg high = mid; } } + if (low == arr.length) + return arr.length; static if (is(pred == void)) { if (p[low] == cmp_args[0]) @@ -96,22 +127,27 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg } -void qsort(alias pred = void, T)(T[] arr) +void qsort(alias pred = void, T)(T[] arr) pure { 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, lvalue_of!T.opCmp(lvalue_of!T))) - 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).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); }); } @@ -121,7 +157,7 @@ void qsort(alias pred = void, T)(T[] arr) if (arr.length > 1) { size_t pivotIndex = arr.length / 2; - T* pivot = &p[pivotIndex]; + T* pivot = p + pivotIndex; size_t i = 0; size_t j = arr.length - 1; @@ -135,8 +171,8 @@ void qsort(alias pred = void, T)(T[] arr) } else { - while (pred(*cast(T*)&p[i], *cast(T*)pivot) < 0) i++; - while (pred(*cast(T*)&p[j], *cast(T*)pivot) > 0) j--; + while (pred(p[i], *pivot) < 0) i++; + while (pred(p[j], *pivot) > 0) j--; } if (i <= j) { @@ -147,9 +183,9 @@ void qsort(alias pred = void, T)(T[] arr) } if (j > 0) - qsort(p[0 .. j + 1]); + qsort!pred(p[0 .. j + 1]); if (i < arr.length) - qsort(p[i .. arr.length]); + qsort!pred(p[i .. arr.length]); } } } @@ -159,7 +195,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; } @@ -167,7 +203,7 @@ 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]); @@ -189,7 +225,7 @@ 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 element_size, 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 / element_size; diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 599d6c9..b7b1449 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -1,5 +1,7 @@ module urt.meta; +import urt.traits : is_callable, is_enum, EnumType, Unqual; + pure nothrow @nogc: alias Alias(alias a) = a; @@ -7,6 +9,52 @@ alias Alias(T) = T; alias AliasSeq(TList...) = TList; +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) + 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) { return (1UL << bits) - 1; @@ -84,18 +132,191 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) template enum_keys(E) { - static assert(is(E == enum), "enum_keys only works with enums!"); + static assert(is_enum!E, "enum_keys only works with enums!"); __gshared immutable string[enum_strings.length] enum_keys = [ enum_strings ]; private alias enum_strings = __traits(allMembers, E); } -E enum_from_string(E)(const(char)[] key) -if (is(E == enum)) +const(E)* enum_from_key(E)(const(char)[] key) + if (is_enum!E) + => MakeEnumInfo!E.value_for(key); + +const(char)[] enum_key_from_value(E)(EnumType!E value) + if (is_enum!E) + => MakeEnumInfo!E.key_for(value); + +struct VoidEnumInfo { - foreach (i, k; enum_keys!E) - if (key[] == k[]) - return cast(E)i; - return cast(E)-1; + import urt.algorithm : binary_search; + import urt.string; + + // keys and values are sorted for binary search + const String[] keys; + const void[] values; + uint 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, stride, value, pred); + if (i < values.length) + return keys[_v2k_lookup[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, stride, value, pred); + if (i < values.length) + return keys[_v2k_lookup[i]][]; + return null; + } + + const(void)* value_for(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + if (i == keys.length) + return null; + i = _k2v_lookup[i]; + return &values[i*stride]; + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + return i < keys.length; + } + +private: + // these tables map between indices of keys and values + const ubyte[] _k2v_lookup; + const ubyte[] _v2k_lookup; +} + +template EnumInfo(E) +{ + alias UE = Unqual!E; + + static if (is(UE == void)) + alias EnumInfo = VoidEnumInfo; + else + { + struct EnumInfo + { + import urt.algorithm : binary_search; + import urt.string; + + 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 + const String[] keys; + const UE[] values; + uint stride = E.sizeof; + uint type_hash; + + ref inout(VoidEnumInfo) make_void() inout + => *cast(inout VoidEnumInfo*)&this; + + const(char)[] key_for(V value) const pure + { + size_t i = binary_search(values, value); + if (i < values.length) + return keys[_v2k_lookup[i]][]; + return null; + } + + const(UE)* value_for(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + if (i == keys.length) + return null; + return &values[_k2v_lookup[i]]; + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + return i < keys.length; + } + + private: + // these tables map between indices of keys and values + const ubyte[] _k2v_lookup; + const ubyte[] _v2k_lookup; + } + + // sanity check the typed one matches the untyped one + static assert(EnumInfo.sizeof == VoidEnumInfo.sizeof); + static assert(EnumInfo.keys.offsetof == VoidEnumInfo.keys.offsetof); + static assert(EnumInfo.values.offsetof == VoidEnumInfo.values.offsetof); + static assert(EnumInfo.stride.offsetof == VoidEnumInfo.stride.offsetof); + static assert(EnumInfo.type_hash.offsetof == VoidEnumInfo.type_hash.offsetof); + static assert(EnumInfo._k2v_lookup.offsetof == VoidEnumInfo._k2v_lookup.offsetof); + static assert(EnumInfo._v2k_lookup.offsetof == VoidEnumInfo._v2k_lookup.offsetof); + } +} + + +template MakeEnumInfo(E) + if (is(Unqual!E == enum)) +{ + alias UE = Unqual!E; + + __gshared immutable MakeEnumInfo = EnumInfo!UE( + _keys[], + _values[], + E.sizeof, + 0, + _k2v_lookup[], + _v2k_lookup[], + ); + +private: + import urt.algorithm : binary_search, compare, qsort; + import urt.string; + import urt.string.uni : uni_compare; + + enum NumItems = __traits(allMembers, E).length; + static assert(NumItems <= ubyte.max, "Too many enum items!"); + + // keys and values are sorted for binary search + __gshared immutable String[NumItems] _keys = [ STATIC_MAP!(GetKey, iota) ]; + __gshared immutable UE[NumItems] _values = [ STATIC_MAP!(GetValue, iota) ]; + + // these tables map between indices of keys and values + __gshared immutable ubyte[NumItems] _k2v_lookup = [ STATIC_MAP!(GetKeyRedirect, iota) ]; + __gshared immutable ubyte[NumItems] _v2k_lookup = [ STATIC_MAP!(GetValRedirect, 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[NumItems] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); + enum by_value = (){ VI[NumItems] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); + enum inv_key = (){ KI[NumItems] bk = by_key; ubyte[NumItems] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); + enum inv_val = (){ VI[NumItems] bv = by_value; ubyte[NumItems] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + + enum MakeKI(ushort i) = KI(enum_members[i], i); + enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); + enum GetKey(size_t i) = StringLit!(by_key[i].k); + 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]; } From 848e77e0acfe6f4fe45827c86fae369af36ba7e7 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 11 Nov 2025 09:23:58 +1000 Subject: [PATCH 33/73] Remove enum_keys helper in favour of the new enum tools. Also eliminated all the redundant length fields, and just store a single length. --- src/urt/algorithm.d | 11 +++-- src/urt/meta/package.d | 105 +++++++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 49 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 0484777..c070380 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -60,9 +60,12 @@ auto compare(T, U)(auto ref T a, auto ref U b) 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 = arr.length; + size_t high = count; while (low < high) { size_t mid = low + (high - low) / 2; @@ -74,11 +77,11 @@ size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, a else high = mid; } - if (low == arr.length) - return arr.length; + if (low == count) + return count; if (pred(p + low*stride, value) == 0) return low; - return arr.length; + return count; } size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index b7b1449..91ca9e6 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -130,13 +130,6 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) } -template enum_keys(E) -{ - static assert(is_enum!E, "enum_keys only works with enums!"); - __gshared immutable string[enum_strings.length] enum_keys = [ enum_strings ]; - private alias enum_strings = __traits(allMembers, E); -} - const(E)* enum_from_key(E)(const(char)[] key) if (is_enum!E) => MakeEnumInfo!E.value_for(key); @@ -145,52 +138,65 @@ const(char)[] enum_key_from_value(E)(EnumType!E value) if (is_enum!E) => MakeEnumInfo!E.key_for(value); +const(char)[] enum_key_by_decl_index(E)(size_t value) + if (is_enum!E) + => MakeEnumInfo!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 - const String[] keys; - const void[] values; - uint stride; + const String* keys; + const void* values; + ubyte 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, stride, value, pred); - if (i < values.length) + size_t i = binary_search(values[0 .. count*stride], stride, value, pred); + if (i < count) return keys[_v2k_lookup[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, stride, value, pred); - if (i < values.length) + size_t i = binary_search(values[0 .. count*stride], stride, value, pred); + if (i < count) return keys[_v2k_lookup[i]][]; return null; } + const(char)[] key_by_decl_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return keys[_decl_lookup[i]][]; + } + const(void)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys, key); - if (i == keys.length) + size_t i = binary_search(keys[0 .. count], key); + if (i == count) return null; i = _k2v_lookup[i]; - return &values[i*stride]; + return values + i*stride; } bool contains(const(char)[] key) const pure { - size_t i = binary_search(keys, key); - return i < keys.length; + size_t i = binary_search(keys[0 .. count], key); + return i < count; } private: // these tables map between indices of keys and values - const ubyte[] _k2v_lookup; - const ubyte[] _v2k_lookup; + const ubyte* _k2v_lookup; + const ubyte* _v2k_lookup; + const ubyte* _decl_lookup; } template EnumInfo(E) @@ -205,6 +211,7 @@ template EnumInfo(E) { import urt.algorithm : binary_search; import urt.string; + nothrow @nogc: static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); @@ -214,9 +221,10 @@ template EnumInfo(E) static assert(false, E.string ~ " is not an enum type!"); // keys and values are sorted for binary search - const String[] keys; - const UE[] values; - uint stride = E.sizeof; + const String* keys; + const UE* values; + const ubyte count = __traits(allMembers, E).length; + const ushort stride = E.sizeof; uint type_hash; ref inout(VoidEnumInfo) make_void() inout @@ -224,40 +232,43 @@ template EnumInfo(E) const(char)[] key_for(V value) const pure { - size_t i = binary_search(values, value); - if (i < values.length) + size_t i = binary_search(values[0 .. count], value); + if (i < count) return keys[_v2k_lookup[i]][]; return null; } + const(char)[] key_by_decl_index(size_t i) const pure + => make_void().key_by_decl_index(i); + const(UE)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys, key); - if (i == keys.length) + size_t i = binary_search(keys[0 .. count], key); + if (i == count) return null; - return &values[_k2v_lookup[i]]; + return values + _k2v_lookup[i]; } bool contains(const(char)[] key) const pure - { - size_t i = binary_search(keys, key); - return i < keys.length; - } + => make_void().contains(key); private: // these tables map between indices of keys and values - const ubyte[] _k2v_lookup; - const ubyte[] _v2k_lookup; + const ubyte* _k2v_lookup; + const ubyte* _v2k_lookup; + const ubyte* _decl_lookup; } // sanity check the typed one matches the untyped one static assert(EnumInfo.sizeof == VoidEnumInfo.sizeof); static assert(EnumInfo.keys.offsetof == VoidEnumInfo.keys.offsetof); static assert(EnumInfo.values.offsetof == VoidEnumInfo.values.offsetof); + static assert(EnumInfo.count.offsetof == VoidEnumInfo.count.offsetof); static assert(EnumInfo.stride.offsetof == VoidEnumInfo.stride.offsetof); static assert(EnumInfo.type_hash.offsetof == VoidEnumInfo.type_hash.offsetof); static assert(EnumInfo._k2v_lookup.offsetof == VoidEnumInfo._k2v_lookup.offsetof); static assert(EnumInfo._v2k_lookup.offsetof == VoidEnumInfo._v2k_lookup.offsetof); + static assert(EnumInfo._decl_lookup.offsetof == VoidEnumInfo._decl_lookup.offsetof); } } @@ -267,13 +278,18 @@ template MakeEnumInfo(E) { alias UE = Unqual!E; - __gshared immutable MakeEnumInfo = EnumInfo!UE( - _keys[], - _values[], + enum ubyte NumItems = __traits(allMembers, E).length; + static assert(NumItems <= ubyte.max, "Too many enum items!"); + + __gshared immutable MakeEnumInfo = immutable(EnumInfo!UE)( + _keys.ptr, + _values.ptr, + NumItems, E.sizeof, 0, - _k2v_lookup[], - _v2k_lookup[], + _lookup.ptr, + _lookup.ptr + NumItems, + _lookup.ptr + NumItems*2 ); private: @@ -281,16 +297,14 @@ private: import urt.string; import urt.string.uni : uni_compare; - enum NumItems = __traits(allMembers, E).length; - static assert(NumItems <= ubyte.max, "Too many enum items!"); - // keys and values are sorted for binary search __gshared immutable String[NumItems] _keys = [ STATIC_MAP!(GetKey, iota) ]; __gshared immutable UE[NumItems] _values = [ STATIC_MAP!(GetValue, iota) ]; // these tables map between indices of keys and values - __gshared immutable ubyte[NumItems] _k2v_lookup = [ STATIC_MAP!(GetKeyRedirect, iota) ]; - __gshared immutable ubyte[NumItems] _v2k_lookup = [ STATIC_MAP!(GetValRedirect, iota) ]; + __gshared immutable ubyte[NumItems * 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 @@ -317,6 +331,7 @@ private: 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]; } From fe37a7e8e5b28df2761672d769407a71941e17c9 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 17 Nov 2025 01:18:28 +1000 Subject: [PATCH 34/73] Fix several logging bugs (actually mostly string formatting bugs!) --- src/urt/log.d | 2 +- src/urt/string/format.d | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/urt/log.d b/src/urt/log.d index 029718c..9a08f0a 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -53,7 +53,7 @@ void writeLogf(T...)(Level level, const(char)[] format, ref T things) if (level > logLevel) return; - const(char)[] message = tformat("{0}: {@-2}", levelNames[level], things, 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); diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 410432e..074a32d 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -344,9 +344,9 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); + ptrdiff_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); else - size_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + ptrdiff_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); if (to_lower && len > 0) { @@ -473,6 +473,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)) @@ -861,6 +863,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 From d329520278f4876427cc9b0f64a80086154b476d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 1 Dec 2025 08:58:29 +1000 Subject: [PATCH 35/73] Add support for Variant to store binary data. Made variant strings a flag on binary data. --- src/urt/format/json.d | 25 +++++++++------ src/urt/traits.d | 2 +- src/urt/variant.d | 72 +++++++++++++++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index ca2cd26..01e977e 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -100,17 +100,24 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u return -1; return written; - case Variant.Type.String: - const char[] s = val.asString(); - if (buffer.ptr) + case Variant.Type.Buffer: + if (val.isString) { - if (buffer.length < s.length + 2) - return -1; - buffer[0] = '"'; - buffer[1 .. 1 + s.length] = s[]; - buffer[1 + s.length] = '"'; + const char[] s = val.asString(); + if (buffer.ptr) + { + if (buffer.length < s.length + 2) + return -1; + buffer[0] = '"'; + buffer[1 .. 1 + s.length] = s[]; + buffer[1 + s.length] = '"'; + } + return s.length + 2; + } + else + { + assert(false, "TODO: how are binary buffers represented in json?"); } - return s.length + 2; case Variant.Type.Number: import urt.conv; diff --git a/src/urt/traits.d b/src/urt/traits.d index bfa8073..fb124d9 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -10,7 +10,7 @@ enum bool is_boolean(T) = __traits(isUnsigned, T) && is(T : bool); 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(Unqual!T == bool) || is_unsigned_int!T || is_some_char!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); diff --git a/src/urt/variant.d b/src/urt/variant.d index febad39..81af559 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -194,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); @@ -387,7 +403,7 @@ nothrow @nogc: case Type.Null: if (b.type == Type.Null) return 0; - else if ((b.type == Type.String || b.type == Type.Array || b.type == Type.Map) && b.empty()) + 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; @@ -469,13 +485,13 @@ nothrow @nogc: r = -1; // sort numbers before other things... break; - case Type.String: - if (b.type != Type.String) + case Type.Buffer: + if (b.type != Type.Buffer) { r = -1; break; } - r = compare(a.asString(), b.asString()); + r = compare(cast(const(char)[])a.asBuffer(), cast(const(char)[])b.asBuffer()); break; case Type.Array: @@ -578,6 +594,8 @@ nothrow @nogc: => (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 @@ -747,6 +765,16 @@ nothrow @nogc: 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 { if (isNull) @@ -949,17 +977,25 @@ nothrow @nogc: 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: @@ -1165,7 +1201,7 @@ package: True = 1, False = 2, Number = 3, - String = 4, + Buffer = 4, Array = 5, Map = 6, User = 7 @@ -1182,8 +1218,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, From 60ddd5e778c77146d4931e692fb860c6ebed3be4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 1 Dec 2025 09:00:19 +1000 Subject: [PATCH 36/73] Add 2 little helpers: - Bounded strlen_s - SysTime from unit time --- src/urt/string/package.d | 8 ++++++++ src/urt/time.d | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 3210e03..81cbf75 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -15,6 +15,14 @@ public import urt.mem.temp : tstringz, twstringz; nothrow @nogc: +size_t strlen_s(const(char)[] s) pure +{ + size_t len = 0; + while (len < s.length && s[len] != '\0') + ++len; + return len; +} + ptrdiff_t cmp(bool case_insensitive = false, T, U)(const(T)[] a, const(U)[] b) pure { static if (case_insensitive) diff --git a/src/urt/time.d b/src/urt/time.d index 80accd5..7972a1e 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -697,6 +697,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); From 708247852e742132163f933c9ca8b19e0bacb1bc Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 2 Dec 2025 09:55:38 +1000 Subject: [PATCH 37/73] More druntime scrubbing... --- src/urt/internal/lifetime.d | 121 ++++++++++++++++++++++++++++++++++++ src/urt/lifetime.d | 34 +--------- src/urt/math.d | 1 - src/urt/mem/package.d | 20 +++--- 4 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 src/urt/internal/lifetime.d 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 cb6cf40..586d9db 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -1,5 +1,6 @@ module urt.lifetime; +import urt.internal.lifetime : emplaceRef; // TODO: DESTROY THIS! T* emplace(T)(T* chunk) @safe pure { @@ -78,12 +79,6 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) return cast(T*) chunk.ptr; } - -// HACK: we should port this to our lib... -static import core.internal.lifetime; -alias emplaceRef = core.internal.lifetime.emplaceRef; - - /+ 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/math.d b/src/urt/math.d index 3c66be8..e3b06b8 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -1,7 +1,6 @@ module urt.math; import urt.intrinsic; -import core.stdc.stdio; // For writeDebugf // for arch where using FPU for int<->float conversions is preferred //version = PreferFPUIntConv; 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); } From e55eafde5225a2ff3769d978725948bdcf907c82 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 2 Dec 2025 23:05:34 +1000 Subject: [PATCH 38/73] Json string writer must escape strings --- src/urt/format/json.d | 74 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 01e977e..4b65529 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -104,15 +104,77 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u if (val.isString) { const char[] s = val.asString(); - if (buffer.ptr) + + if (!buffer.ptr) { - if (buffer.length < s.length + 2) + 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) + { + 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; } else { From 4a6088087f4bf6dd8c23bf4798950697927e21cc Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Dec 2025 18:14:53 +1000 Subject: [PATCH 39/73] Variant fixes --- src/urt/endian.d | 10 +++++----- src/urt/variant.d | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/urt/endian.d b/src/urt/endian.d index bb806d9..9c0033c 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -96,7 +96,7 @@ 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; @@ -261,7 +261,7 @@ ubyte[T.sizeof] nativeToLittleEndian(T)(auto ref const T data) // load/store from/to memory void storeBigEndian(T)(T* target, const T val) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) *target = val; @@ -269,7 +269,7 @@ void storeBigEndian(T)(T* target, const T val) *target = byte_reverse(val); } void storeLittleEndian(T)(T* target, const T val) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) *target = val; @@ -277,7 +277,7 @@ void storeLittleEndian(T)(T* target, const T val) *target = byte_reverse(val); } T loadBigEndian(T)(const(T)* src) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) return *src; @@ -285,7 +285,7 @@ T loadBigEndian(T)(const(T)* src) return byte_reverse(*src); } T loadLittleEndian(T)(const(T)* src) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) return *src; diff --git a/src/urt/variant.d b/src/urt/variant.d index 81af559..6fbf197 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -344,11 +344,12 @@ nothrow @nogc: return nodeArray.pushBack(); } - bool opEquals(T)(ref const Variant rhs) const pure + 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))) @@ -536,6 +537,7 @@ nothrow @nogc: 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))) @@ -854,7 +856,7 @@ nothrow @nogc: return asUlong(); else { - uint u = asInt(); + uint u = asUint(); static if (!is(T == uint)) assert(u <= T.max, "Value out of range for " ~ T.stringof); return cast(T)u; @@ -918,6 +920,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 // { From c978668e72b8889812de72d95866300d0c518f07 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:01:31 +1000 Subject: [PATCH 40/73] Added a function to build a runtime EnumInfo. Substantially compressed the data by: - aggregating buffers - storing keys as a 16-bit index into a single string buffer - removed a bunch of redundant length fields The runtime EnumInfo is a single allocation. --- src/urt/meta/package.d | 309 +++++++++++++++++++++++++++++++---------- 1 file changed, 232 insertions(+), 77 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 91ca9e6..0a17ea3 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -2,7 +2,7 @@ module urt.meta; import urt.traits : is_callable, is_enum, EnumType, Unqual; -pure nothrow @nogc: +nothrow @nogc: alias Alias(alias a) = a; alias Alias(T) = T; @@ -18,7 +18,7 @@ template Iota(ptrdiff_t start, ptrdiff_t end) } alias Iota(size_t end) = Iota!(0, end); -auto make_delegate(Fun)(Fun fun) +auto make_delegate(Fun)(Fun fun) pure if (is_callable!Fun) { import urt.traits : is_function_pointer, ReturnType, Parameters; @@ -55,7 +55,7 @@ auto make_delegate(Fun)(Fun fun) static assert(false, "Unsupported type for make_delegate"); } -ulong bit_mask(size_t bits) +ulong bit_mask(size_t bits) pure { return (1UL << bits) - 1; } @@ -130,17 +130,17 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) } -const(E)* enum_from_key(E)(const(char)[] key) +const(E)* enum_from_key(E)(const(char)[] key) pure if (is_enum!E) - => MakeEnumInfo!E.value_for(key); + => enum_info!E.value_for(key); -const(char)[] enum_key_from_value(E)(EnumType!E value) +const(char)[] enum_key_from_value(E)(EnumType!E value) pure if (is_enum!E) - => MakeEnumInfo!E.key_for(value); + => enum_info!E.key_for(value); -const(char)[] enum_key_by_decl_index(E)(size_t value) +const(char)[] enum_key_by_decl_index(E)(size_t value) pure if (is_enum!E) - => MakeEnumInfo!E.key_by_decl_index(value); + => enum_info!E.key_by_decl_index(value); struct VoidEnumInfo { @@ -149,54 +149,71 @@ struct VoidEnumInfo nothrow @nogc: // keys and values are sorted for binary search - const String* keys; - const void* values; - ubyte count; + 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); + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); if (i < count) - return keys[_v2k_lookup[i]][]; + 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); + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); if (i < count) - return keys[_v2k_lookup[i]][]; + 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 keys[_decl_lookup[i]][]; + return get_key(_lookup_tables[count*2 + i]); } const(void)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys[0 .. count], key); + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); if (i == count) return null; - i = _k2v_lookup[i]; - return values + i*stride; + i = _lookup_tables[i]; + return _values + i*stride; } bool contains(const(char)[] key) const pure { - size_t i = binary_search(keys[0 .. count], key); + 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* _k2v_lookup; - const ubyte* _v2k_lookup; - const ubyte* _decl_lookup; + 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) @@ -210,7 +227,6 @@ template EnumInfo(E) struct EnumInfo { import urt.algorithm : binary_search; - import urt.string; nothrow @nogc: static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); @@ -221,90 +237,117 @@ template EnumInfo(E) static assert(false, E.string ~ " is not an enum type!"); // keys and values are sorted for binary search - const String* keys; - const UE* values; - const ubyte count = __traits(allMembers, E).length; - const ushort stride = E.sizeof; - uint type_hash; + union { + VoidEnumInfo _base; + const UE* _values; // shadows the _values in _base with a typed version + } + alias _base this; + + inout(VoidEnumInfo*) make_void() inout pure + => &_base; - ref inout(VoidEnumInfo) make_void() inout - => *cast(inout VoidEnumInfo*)&this; + 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 keys[_v2k_lookup[i]][]; + return get_key(_lookup_tables[count + i]); return null; } const(char)[] key_by_decl_index(size_t i) const pure - => make_void().key_by_decl_index(i); + => _base.key_by_decl_index(i); const(UE)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys[0 .. count], key); + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); if (i == count) return null; - return values + _k2v_lookup[i]; + return _values + _lookup_tables[i]; } bool contains(const(char)[] key) const pure - => make_void().contains(key); - - private: - // these tables map between indices of keys and values - const ubyte* _k2v_lookup; - const ubyte* _v2k_lookup; - const ubyte* _decl_lookup; + => _base.contains(key); } - - // sanity check the typed one matches the untyped one - static assert(EnumInfo.sizeof == VoidEnumInfo.sizeof); - static assert(EnumInfo.keys.offsetof == VoidEnumInfo.keys.offsetof); - static assert(EnumInfo.values.offsetof == VoidEnumInfo.values.offsetof); - static assert(EnumInfo.count.offsetof == VoidEnumInfo.count.offsetof); - static assert(EnumInfo.stride.offsetof == VoidEnumInfo.stride.offsetof); - static assert(EnumInfo.type_hash.offsetof == VoidEnumInfo.type_hash.offsetof); - static assert(EnumInfo._k2v_lookup.offsetof == VoidEnumInfo._k2v_lookup.offsetof); - static assert(EnumInfo._v2k_lookup.offsetof == VoidEnumInfo._v2k_lookup.offsetof); - static assert(EnumInfo._decl_lookup.offsetof == VoidEnumInfo._decl_lookup.offsetof); } } - -template MakeEnumInfo(E) +template enum_info(E) if (is(Unqual!E == enum)) { alias UE = Unqual!E; - enum ubyte NumItems = __traits(allMembers, E).length; - static assert(NumItems <= ubyte.max, "Too many enum items!"); + enum ubyte num_items = enum_members.length; + static assert(num_items <= ubyte.max, "Too many enum items!"); - __gshared immutable MakeEnumInfo = immutable(EnumInfo!UE)( - _keys.ptr, + __gshared immutable enum_info = immutable(EnumInfo!UE)( + num_items, + fnv1a(cast(ubyte[])UE.stringof), _values.ptr, - NumItems, - E.sizeof, - 0, - _lookup.ptr, - _lookup.ptr + NumItems, - _lookup.ptr + NumItems*2 + _keys.ptr, + _strings.ptr, + _lookup.ptr ); private: import urt.algorithm : binary_search, compare, qsort; - import urt.string; + import urt.hash : fnv1a; import urt.string.uni : uni_compare; // keys and values are sorted for binary search - __gshared immutable String[NumItems] _keys = [ STATIC_MAP!(GetKey, iota) ]; - __gshared immutable UE[NumItems] _values = [ STATIC_MAP!(GetValue, iota) ]; + __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[NumItems * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), - STATIC_MAP!(GetValRedirect, iota), - STATIC_MAP!(GetKeyOrig, iota) ]; + __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 @@ -320,20 +363,112 @@ private: alias iota = Iota!(enum_members.length); enum enum_members = __traits(allMembers, E); - enum by_key = (){ KI[NumItems] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); - enum by_value = (){ VI[NumItems] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); - enum inv_key = (){ KI[NumItems] bk = by_key; ubyte[NumItems] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); - enum inv_val = (){ VI[NumItems] bv = by_value; ubyte[NumItems] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + 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(enum_members[i], i); enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); - enum GetKey(size_t i) = StringLit!(by_key[i].k); 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: @@ -350,3 +485,23 @@ 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 +{ + import urt.string.uni : uni_compare; + const(char)* s = strings + a; + return uni_compare(s[0 .. s.key_length], b); +} From d3a470dd8905b00d5a341afbbc90422dc0b0db14 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:03:28 +1000 Subject: [PATCH 41/73] Added a helper for building string caches. - make sure it's CTFE-able. --- src/urt/mem/string.d | 5 --- src/urt/string/string.d | 89 +++++++++++++++++++++++++++++++++---- src/urt/string/tailstring.d | 6 +-- 3 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index 1cefa4e..a3adc22 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -11,11 +11,6 @@ shared static this() } -struct StringCache -{ -} - - struct CacheString { nothrow @nogc: diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 4fa8cdb..843c7fd 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -32,6 +32,47 @@ struct StringAllocator void delegate(char* s) nothrow @nogc free; } +struct StringCacheBuilder +{ +nothrow @nogc: + this(char[] buffer) pure + { + assert(buffer.length <= ushort.max, "Buffer too long"); + this._buffer = buffer; + this._offset = 0; + } + + ushort add_string(const(char)[] s) pure + { + assert(s.length <= MaxStringLen, "String too long"); + 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; + } + +private: + char[] _buffer; + ushort _offset; +} //enum String StringLit(string s) = s.makeString; template StringLit(const(char)[] lit, bool zeroTerminate = true) @@ -116,6 +157,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 { @@ -941,14 +1022,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 c9d8751..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); } From 7025ea75bf963b0989484ef7cafe95d9e056437c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:03:58 +1000 Subject: [PATCH 42/73] Fix qsort... apparently I buggered it up! --- src/urt/algorithm.d | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index c070380..9995cfb 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -232,30 +232,32 @@ version (SmallSize) { void* p = arr.ptr; size_t length = arr.length / element_size; - if (length > 1) - { - size_t pivotIndex = length / 2; - void* pivot = p + pivotIndex*element_size; + if (length <= 1) + return; - size_t i = 0; - size_t j = length - 1; + size_t pivot_index = length / 2; + size_t last = length - 1; + swap(p + pivot_index*element_size, p + last*element_size); - while (i <= j) + void* pivot = p + last*element_size; + + 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*element_size, pivot) < 0) i++; - while (compare(p + j*element_size, pivot) > 0) j--; - if (i <= j) - { - swap(p + i*element_size, p + j*element_size); - i++; - j--; - } + if (k != partition) + swap(elem, p + partition*element_size); + ++partition; } - - if (j > 0) - qsort(p[0 .. (j + 1)*element_size], element_size, compare, swap); - if (i < length) - qsort(p[i*element_size .. length*element_size], element_size, 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); } } From 0b3c1109ab6948f25e86a8328e49e689f02d8557 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:54:51 +1000 Subject: [PATCH 43/73] Added a signed parse_int_with_base --- src/urt/conv.d | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index dbbd3f9..8889082 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -132,11 +132,23 @@ done: return value; } +long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure +{ + const(char)* p = str.ptr; + int base = str.parse_base_prefix(); + if (base == 10) + return str.parse_int(bytes_taken); + ulong i = str.parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += str.ptr - p; + return i; +} + ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { const(char)* p = str.ptr; - int base = parse_base_prefix(str); - ulong i = parse_uint(str, bytes_taken, base); + int base = str.parse_base_prefix(); + ulong i = str.parse_uint(bytes_taken, base); if (bytes_taken && *bytes_taken != 0) *bytes_taken += str.ptr - p; return i; From 44060913a7449a4f288985830423255a89413f60 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 13:08:17 +1000 Subject: [PATCH 44/73] Added a template to make a ScaledUnit from a string. --- src/urt/si/unit.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1b98a6a..1ebf7a0 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -32,6 +32,9 @@ nothrow @nogc: // +enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parseUnit(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); From 38f75e3d4380d93cd81d51487ef266df55b6d9e4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:55:10 +1000 Subject: [PATCH 45/73] Unroll array --- src/urt/meta/package.d | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 91ca9e6..a28f17a 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -99,6 +99,18 @@ template STATIC_MAP(alias fun, 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!(); From 74fc4f11e6e85345c42e019803095f8214cc6b93 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 8 Dec 2025 12:57:56 +1000 Subject: [PATCH 46/73] Fixed an assortment of small bugs. --- src/urt/endian.d | 4 ++-- src/urt/si/quantity.d | 2 +- src/urt/time.d | 24 ++++++++++++++++++++---- src/urt/traits.d | 2 ++ src/urt/variant.d | 20 ++++++++++---------- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/urt/endian.d b/src/urt/endian.d index 9c0033c..a48f872 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -100,8 +100,8 @@ T endianToNative(T, bool little)(ref const ubyte[T.sizeof] 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; } } diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 880166a..df9775f 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -72,7 +72,7 @@ nothrow @nogc: value = adjustScale(b); } } - + void opAssign()(T value) pure { static if (Dynamic) diff --git a/src/urt/time.d b/src/urt/time.d index 7972a1e..2b3036d 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -391,6 +391,23 @@ 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 { @@ -416,11 +433,10 @@ pure nothrow @nogc: return len; } - size_t offset = 0; - len = year.format_int(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++] = '-'; buffer[offset++] = '0' + (month / 10); buffer[offset++] = '0' + (month % 10); diff --git a/src/urt/traits.d b/src/urt/traits.d index fb124d9..6f8a761 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -222,6 +222,8 @@ enum is_primitive(T) = is_integral!T || is_some_float!T || (is_enum!T && is_prim 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 is_trivial(T) = __traits(isPOD, 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)))) || diff --git a/src/urt/variant.d b/src/urt/variant.d index 6fbf197..d6aebdd 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1018,9 +1018,9 @@ nothrow @nogc: case Variant.Type.User: if (flags & Flags.Embedded) - return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true); + return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true, format, formatArgs); else - return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true); + return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true, format, formatArgs); } } @@ -1091,7 +1091,7 @@ nothrow @nogc: { 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); + ptrdiff_t taken = td.stringify(td.embedded ? embed.ptr : buffer.ptr, cast(char[])s, false, null, null); if (taken > 0) { flags = Flags.User; @@ -1283,6 +1283,7 @@ unittest private: import urt.hash : fnv1a; +import urt.string.format : formatValue, FormatArg; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); @@ -1342,7 +1343,7 @@ struct TypeDetails bool embedded; void function(void* src, void* dst, bool move) nothrow @nogc copy_emplace; void function(void* val) nothrow @nogc destroy; - ptrdiff_t function(void* val, char[] buffer, bool format) 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; } __gshared TypeDetails[8] g_type_details; @@ -1405,7 +1406,7 @@ public template TypeDetailsFor(T) else enum move_emplace = null; - static if (!is(T == class) && is(typeof(destroy!(false, T)))) + static if (!is_trivial!T && is(typeof(destroy!(false, T)))) { static void destroy_impl(void* val) nothrow @nogc { @@ -1416,13 +1417,12 @@ public template TypeDetailsFor(T) else enum destroy_fun = null; - static ptrdiff_t stringify(void* val, char[] buffer, bool format) nothrow @nogc + static ptrdiff_t stringify(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc { - import urt.string.format : toString; - if (format) + if (do_format) { - static if (is(typeof(toString!T))) - return toString(*cast(const T*)val, buffer); + 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; } From bb72172fc6ed3c1cfed2403fb912aec9a81d18a2 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 10 Dec 2025 15:19:14 +1000 Subject: [PATCH 47/73] Fix handling of negative times. --- src/urt/time.d | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 2b3036d..20857dc 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -796,23 +796,24 @@ ptrdiff_t timeToString(long ns, char[] buffer) pure { import urt.conv : format_int; - long hr = ns / 3_600_000_000_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) { size_t tail = 6; - ns %= 1_000_000_000; - if (ns) + if (remainder) { ++tail; uint m = 0; do { ++tail; - uint digit = cast(uint)(ns / digit_multipliers[m]); - ns -= digit * digit_multipliers[m++]; + uint digit = cast(uint)(remainder / digit_multipliers[m]); + remainder -= digit * digit_multipliers[m++]; } - while (ns); + while (remainder); } return hr.format_int(null, 10, 2, '0') + tail; } @@ -821,9 +822,9 @@ ptrdiff_t timeToString(long ns, char[] buffer) pure if (len < 0 || buffer.length < len + 6) return -1; - ubyte min = cast(ubyte)(ns / 60_000_000_000 % 60); - ubyte sec = cast(ubyte)(ns / 1_000_000_000 % 60); - ns %= 1_000_000_000; + 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)); @@ -831,19 +832,20 @@ ptrdiff_t timeToString(long ns, char[] buffer) pure buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (sec / 10)); buffer.ptr[len++] = cast(char)('0' + (sec % 10)); - if (ns) + if (remainder) { if (buffer.length < len + 2) return -1; buffer.ptr[len++] = '.'; - uint m = 0; - while (ns) + uint i = 0; + while (remainder) { - if (buffer.length < len + 1) + if (buffer.length <= len) return -1; - uint digit = cast(uint)(ns / digit_multipliers[m]); + uint m = digit_multipliers[i++]; + uint digit = cast(uint)(remainder / m); buffer.ptr[len++] = cast(char)('0' + digit); - ns -= digit * digit_multipliers[m++]; + remainder -= digit * m; } } return len; @@ -855,6 +857,7 @@ unittest 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"); + 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+"); From 5b90efcd9d6babfee8f2f772a82d72fc443bcc13 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 12 Dec 2025 16:31:47 +1000 Subject: [PATCH 48/73] Fix EnumInfo typed values array offset. --- src/urt/meta/package.d | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 0d25eea..bb6e8d9 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -251,7 +251,10 @@ template EnumInfo(E) // keys and values are sorted for binary search union { VoidEnumInfo _base; - const UE* _values; // shadows the _values in _base with a typed version + struct { + ubyte[VoidEnumInfo._values.offsetof] _pad; + const UE* _values; // shadows the _values in _base with a typed version + } } alias _base this; From b7ca9d6c9670a04c4a6f08d6ab581a9023c623f9 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 12 Dec 2025 19:03:33 +1000 Subject: [PATCH 49/73] Fixed a bug resuming from a fibre multiple times. --- src/urt/async.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/async.d b/src/urt/async.d index 81e0a19..557f24a 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -79,7 +79,7 @@ void asyncUpdate() if (!t.event.ready()) continue; } - t.resume(); + t.call.fibre.resume(); } } From a7cc818c5b20148dda194ec97d69ddcc27c1ed65 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 14 Dec 2025 14:02:44 +1000 Subject: [PATCH 50/73] Add toString for Arrays and Maps. - Made it a template; so the code is never generated unless it's called (this should probably be standard practise!) --- src/urt/array.d | 14 +++++++++ src/urt/map.d | 63 +++++++++++++++++++++++++++++++++++++++++ src/urt/string/format.d | 34 ++++++++++++++-------- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 702c861..6674151 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -685,6 +685,20 @@ 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: T* ptr; uint _length; diff --git a/src/urt/map.d b/src/urt/map.d index 97a6f99..3a624ac 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -300,6 +300,69 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) 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: alias Node = AVLTreeNode!(K, V); diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 074a32d..7784113 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -120,6 +120,8 @@ struct FormatArg { static if (IsAggregate!T && is(typeof(&value.toString) : StringifyFunc)) toString = &value.toString; + else 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... @@ -151,22 +153,16 @@ struct FormatArg } ptrdiff_t getString(char[] buffer, const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(buffer, format, args); - } + => toString(buffer, format, args); + 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; - } + => toInt != null; + ptrdiff_t getInt() const nothrow @nogc - { - return toInt(); - } + => toInt(); private: // TODO: we could assert that the delegate pointers match, and only store it once... @@ -569,6 +565,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(); @@ -581,6 +580,7 @@ struct DefFormat(T) } catch (Exception) return -1; ++/ } else static if (is(T == const)) { @@ -632,6 +632,16 @@ struct DefFormat(T) } return ++len; } + else static if (is(T == function)) + { + 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); } From 89eba280556af7dbf6624e8b17b249cdf37e276f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 14 Dec 2025 14:03:25 +1000 Subject: [PATCH 51/73] Write a log message before calling abort() when a fibre encounters an unrecoverable error. --- src/urt/fibre.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 2e736d2..38a5579 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -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(); } From f0867935dcbb8e6449b0621dcc9b24c9edfa4cc4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 30 Dec 2025 21:12:39 +1000 Subject: [PATCH 52/73] Fixed a swap-endian bug in a function that had never been called. --- src/urt/endian.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/urt/endian.d b/src/urt/endian.d index a48f872..2ee3da5 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -208,8 +208,7 @@ pragma(inline, true) auto nativeToEndian(bool little, T)(T val) { import urt.meta : IntForWidth; alias U = IntForWidth!(T.sizeof*8); - U r = nativeToEndian!little(*cast(U*)&val); - return *cast(T*)&r; + return nativeToEndian!little(*cast(U*)&val); } ubyte[T.sizeof] nativeToEndian(bool little, T)(auto ref const T data) From aa0d02b34d2adf6bd744739f763cbb76591aa28a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 14 Dec 2025 20:07:39 +1000 Subject: [PATCH 53/73] Explicit format arg. --- src/urt/string/string.d | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 843c7fd..1cf61e2 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -559,9 +559,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() @@ -690,9 +690,9 @@ nothrow @nogc: return this; } - ref MutableString!Embed appendFormat(Things...)(auto ref Things things) + ref MutableString!Embed appendFormat(Things...)(const(char)[] format, auto ref Things args) { - insertFormat(length(), forward!things); + insertFormat(length(), format, forward!args); return this; } @@ -704,11 +704,11 @@ 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); + insertFormat(0, format, forward!args); return this; } @@ -740,7 +740,7 @@ nothrow @nogc: return this; } - ref MutableString!Embed insertFormat(Things...)(size_t offset, auto ref Things things) + ref MutableString!Embed insertFormat(Things...)(size_t offset, const(char)[] format, auto ref Things args) { import urt.string.format : _format = format; import urt.util : max, next_power_of_2; @@ -748,7 +748,7 @@ nothrow @nogc: 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; @@ -757,7 +757,7 @@ nothrow @nogc: size_t oldAlloc = allocated(); 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) From 22e16a0722f5a0bfea591eb53484999b995bb4e8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 31 Dec 2025 20:59:08 +1000 Subject: [PATCH 54/73] Added a function to parse a decimal uint, reporting the exponent where the resulting value is the smallest integral significand. --- src/urt/conv.d | 87 +++++++++++++++++++++++++++++++++++++++++++++++ src/urt/si/unit.d | 37 +++----------------- 2 files changed, 92 insertions(+), 32 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 8889082..8bd0b79 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -81,6 +81,91 @@ ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) p return value; } +ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + import urt.util : ctz, is_power_of_2; + + assert(base > 1 && base <= 36, "Invalid base"); + + const(char)* s = str.ptr; + const(char)* e = s + str.length; + + ulong value = 0; + int exp = 0; + uint digits = 0; + uint zero_seq = 0; + + for (; s < e; ++s) + { + char c = *s; + + if (c == '.') + { + if (s == str.ptr) + goto done; + ++s; + if (digits) + digits += zero_seq; + zero_seq = 0; + goto parse_decimal; + } + else if (c == '0') + { + ++zero_seq; + continue; + } + + uint digit = get_digit(c); + if (digit >= base) + break; + + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + value += digit; + + if (digits) + digits += zero_seq; + digits += 1; + zero_seq = 0; + } + + // number has no decimal point, tail zeroes are positive exp + exp = digits ? zero_seq : 0; + goto done; + +parse_decimal: + for (; s < e; ++s) + { + char c = *s; + + if (c == '0') + { + ++zero_seq; + continue; + } + + uint digit = get_digit(c); + if (digit >= base) + break; + + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + value += digit; + + if (digits) + digits += zero_seq; + digits += 1; + exp -= 1 + zero_seq; + zero_seq = 0; + } + +done: + exponent = exp; + if (bytes_taken) + *bytes_taken = s - str.ptr; + return value; +} + ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -99,6 +184,8 @@ ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, if (c == '.') { + if (s == str.ptr) + goto done; ++s; goto parse_decimal; } diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1ebf7a0..1e2c70e 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -567,6 +567,8 @@ nothrow: ptrdiff_t parseUnit(const(char)[] s, out float preScale) pure { + import urt.conv : parse_uint_with_exponent; + preScale = 1; if (s.length == 0) @@ -599,41 +601,12 @@ nothrow: { size_t offset = 0; - // parse the exponent + // parse the scale factor int e = 0; if (term[0].is_numeric) { - if (term[0] == '0') - { - if (term.length < 2 || term[1] != '.') - return -1; - e = 1; - offset = 2; - while (offset < term.length) - { - if (term[offset] == '1') - break; - if (term[offset] != '0') - return -1; - ++e; - ++offset; - } - ++offset; - e = -e; - } - else if (term[0] == '1') - { - offset = 1; - while (offset < term.length) - { - if (term[offset] != '0') - break; - ++e; - ++offset; - } - } - else - return -1; + ulong sf = term.parse_uint_with_exponent(e, &offset); + preScale *= sf; } if (offset == term.length) From 50d5ba50da76de7f9716a4b978b14863397dffc1 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 31 Dec 2025 23:54:21 +1000 Subject: [PATCH 55/73] Added unittest for parse_uint_with_exponent... fixed a bug. --- src/urt/conv.d | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 8bd0b79..160eb11 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -104,9 +104,7 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte if (s == str.ptr) goto done; ++s; - if (digits) - digits += zero_seq; - zero_seq = 0; + exp = zero_seq; goto parse_decimal; } else if (c == '0') @@ -119,12 +117,13 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte if (digit >= base) break; - for (uint i = 0; i <= zero_seq; ++i) - value = value * base; - value += digit; - if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; digits += zero_seq; + } + value += digit; digits += 1; zero_seq = 0; } @@ -148,16 +147,19 @@ parse_decimal: if (digit >= base) break; - for (uint i = 0; i <= zero_seq; ++i) - value = value * base; - value += digit; - if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; digits += zero_seq; + } + value += digit; digits += 1; exp -= 1 + zero_seq; zero_seq = 0; } + if (!digits) + exp = 0; // didn't parse any digits; reset exp to 0 done: exponent = exp; @@ -166,6 +168,19 @@ done: return value; } +unittest +{ + int e; + size_t taken; + 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); +} + ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); From 50f7c18692a72a92e2ff1d3fcd38ae05775ca568 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 7 Jan 2026 15:11:52 +1000 Subject: [PATCH 56/73] Fixed some string formatting bugs and TODOs --- src/urt/encoding.d | 20 ++-- src/urt/format/json.d | 147 ++++++++++++++++------------ src/urt/inet.d | 30 +++--- src/urt/si/unit.d | 69 ++++++++++---- src/urt/string/format.d | 205 +++++++++++++++++++++++++--------------- 5 files changed, 285 insertions(+), 186 deletions(-) diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 43fa85e..9f76aa0 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -8,10 +8,10 @@ enum HexDecode(string str) = () { ubyte[hex_decode_length(str.length)] 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 source_length) pure +size_t base64_encode_length(size_t source_length) pure => (source_length + 2) / 3 * 4; -ptrdiff_t base64_encode_length(const void[] data) pure +size_t base64_encode_length(const void[] data) pure => base64_encode_length(data.length); ptrdiff_t base64_encode(const void[] data, char[] result) pure @@ -58,10 +58,10 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure return out_len; } -ptrdiff_t base64_decode_length(size_t source_length) pure +size_t base64_decode_length(size_t source_length) pure => source_length / 4 * 3; -ptrdiff_t base64_decode_length(const char[] data) pure +size_t base64_decode_length(const char[] data) pure => base64_decode_length(data.length); ptrdiff_t base64_decode(const char[] data, void[] result) pure @@ -143,10 +143,10 @@ unittest assert(data[0..10] == decoded[0..10]); } -ptrdiff_t hex_encode_length(size_t sourceLength) pure +size_t hex_encode_length(size_t sourceLength) pure => sourceLength * 2; -ptrdiff_t hex_encode_length(const void[] data) pure +size_t hex_encode_length(const void[] data) pure => data.length * 2; ptrdiff_t hex_encode(const void[] data, char[] result) pure @@ -157,10 +157,10 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure return toHexString(data, result).length; } -ptrdiff_t hex_decode_length(size_t sourceLength) pure +size_t hex_decode_length(size_t sourceLength) pure => sourceLength / 2; -ptrdiff_t hex_decode_length(const char[] data) pure +size_t hex_decode_length(const char[] data) pure => data.length / 2; ptrdiff_t hex_decode(const char[] data, void[] result) pure @@ -212,7 +212,7 @@ unittest } -ptrdiff_t url_encode_length(const char[] data) pure +size_t url_encode_length(const char[] data) pure { import urt.string.ascii : is_url; @@ -255,7 +255,7 @@ ptrdiff_t url_encode(const char[] data, char[] result) pure 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;) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 4b65529..9a4e7dd 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -101,85 +101,98 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u return written; case Variant.Type.Buffer: - if (val.isString) + if (!val.isString) { - const char[] s = val.asString(); + import urt.encoding; - if (!buffer.ptr) + // emit raw buffer as base64 + const data = val.asBuffer(); + size_t enc_len = base64_encode_length(data.length); + 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 < 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; + } - if (buffer.length < s.length + 2) - return -1; + const char[] s = val.asString(); - buffer[0] = '"'; - // escape strings - size_t offset = 1; + if (!buffer.ptr) + { + size_t len = 0; foreach (c; s) { - 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'; + if (c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') + len += 2; 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; - } + len += 6; } else if (c == '"' || c == '\\') - sub = 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) + { + 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 + 2) + if (buffer.length < offset + 7) return -1; - buffer[offset++] = c; + buffer[offset .. offset + 4] = "\\u00"; + offset += 4; + buffer[offset++] = hex_digits[c >> 4]; + buffer[offset++] = hex_digits[c & 0xF]; continue; } - - // write escape sequence - if (buffer.length < offset + 3) + } + else if (c == '"' || c == '\\') + sub = c; + else + { + if (buffer.length < offset + 2) return -1; - buffer[offset++] = '\\'; - buffer[offset++] = sub; + buffer[offset++] = c; + continue; } - buffer[offset++] = '"'; - return offset; - } - else - { - assert(false, "TODO: how are binary buffers represented in json?"); + + // write escape sequence + if (buffer.length < offset + 3) + return -1; + buffer[offset++] = '\\'; + buffer[offset++] = sub; } + buffer[offset++] = '"'; + return offset; case Variant.Type.Number: import urt.conv; @@ -198,11 +211,19 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u 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; } } diff --git a/src/urt/inet.d b/src/urt/inet.d index b2d37bb..be1bddc 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -114,7 +114,7 @@ nothrow @nogc: return fnv1a(b[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { char[15] stack_buffer = void; char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; @@ -123,7 +123,7 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = '.'; - offset += b[i].format_int(tmp[offset..$]); + offset += b[i].format_uint(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stack_buffer.ptr) @@ -139,22 +139,22 @@ nothrow @nogc: { ubyte[4] t; size_t offset = 0, len; - ulong i = s[offset..$].parse_int(&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..$].parse_int(&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..$].parse_int(&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..$].parse_int(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255) return -1; @@ -297,7 +297,7 @@ nothrow @nogc: tmp[offset++] = ':'; continue; } - offset += s[i].format_int(tmp[offset..$], 16); + offset += s[i].format_uint(tmp[offset..$], 16); ++i; } @@ -338,7 +338,7 @@ nothrow @nogc: } if (str[offset] == ':') return -1; - ulong i = str[offset..$].parse_int(&len, 16); + ulong i = str[offset..$].parse_uint(&len, 16); if (len == 0) break; if (i > ushort.max || count[0] + count[1] == 8) @@ -418,7 +418,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefix_len.format_int(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { @@ -436,7 +436,7 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parse_int(&t); + ulong plen = s[taken..$].parse_uint(&t); if (t == 0 || plen > 32) return -1; addr = a; @@ -515,7 +515,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefix_len.format_int(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { @@ -533,7 +533,7 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parse_int(&t); + ulong plen = s[taken..$].parse_uint(&t); if (t == 0 || plen > 128) return -1; addr = a; @@ -665,7 +665,7 @@ nothrow @nogc: { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; - offset += _a.ipv4.port.format_int(tmp[offset..$]); + offset += _a.ipv4.port.format_uint(tmp[offset..$]); } else { @@ -673,7 +673,7 @@ nothrow @nogc: offset = 1 + _a.ipv6.addr.toString(tmp[1 .. $], null, null); tmp[offset++] = ']'; tmp[offset++] = ':'; - offset += _a.ipv6.port.format_int(tmp[offset..$]); + offset += _a.ipv6.port.format_uint(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stack_buffer.ptr) @@ -720,7 +720,7 @@ nothrow @nogc: if (s.length > taken && s[taken] == ':') { size_t t; - ulong p = s[++taken..$].parse_int(&t); + ulong p = s[++taken..$].parse_uint(&t); if (t == 0 || p > ushort.max) return -1; taken += t; diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1e2c70e..c0c4ae5 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -690,13 +690,27 @@ nothrow: { if (siScale && exp == -2) { - if (buffer.length == 0) - return -1; - buffer[0] = '%'; + 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!"); + assert(false, "TODO!"); // how (or should?) we encode a scale as a unit type? } size_t len = 0; @@ -711,18 +725,24 @@ nothrow: { if (y == 1) { - if (buffer.length < 2) - return -1; + if (buffer.ptr) + { + if (buffer.length < 2) + return -1; + buffer[0..2] = "10"; + } --x; - buffer[0..2] = "10"; len += 2; } else { - if (buffer.length < 3) - return -1; + if (buffer.ptr) + { + if (buffer.length < 3) + return -1; + buffer[0..3] = "100"; + } x -= 2; - buffer[0..3] = "100"; len += 3; } } @@ -730,17 +750,24 @@ nothrow: if (x != 0) { - if (buffer.length <= len) - return -1; - buffer[len++] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + 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.length < len + name.length) - return -1; - buffer[len .. len + name.length] = *name; + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } len += name.length; } else @@ -753,9 +780,12 @@ nothrow: { if (const string* name = this in scaledUnitNames) { - if (buffer.length < len + name.length) - return -1; - buffer[len .. len + name.length] = *name; + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } len += name.length; } else @@ -776,7 +806,6 @@ nothrow: return r; } - size_t toHash() const pure => pack; diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 7784113..613a819 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -7,29 +7,27 @@ 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,86 +101,146 @@ 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 && 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 - => toString(buffer, format, args); + StringifyFunc getString; ptrdiff_t getLength(const(char)[] format, const(FormatArg)[] args) const nothrow @nogc => getString(null, format, args); - bool canInt() const nothrow @nogc - => toInt != null; + bool canInt() const pure nothrow @nogc + => _to_int_fun !is null; - ptrdiff_t getInt() const nothrow @nogc - => toInt(); + ptrdiff_t getInt() const pure nothrow @nogc + { + 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) +{ + enum overload_index = to_string_overload_index!T; + + static if (overload_index >= 0) + { + static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFunc)) + return &__traits(getOverloads, value, "toString", true)[overload_index]; + + // TODO: if toString is a template, we need to instantiate it and then captuire the function pointer... + + static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced))// || +// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced)) + { + StringifyFunc d; + d.ptr = &value; + d.funcptr = &ToStringShim.shim1!T; + return d; + } + + static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced2))// || +// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced2)) + { + StringifyFunc d; + d.ptr = &value; + 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) { - return (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + 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; @@ -588,6 +645,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) { @@ -645,24 +704,14 @@ struct DefFormat(T) else static assert(false, "Not implemented for type: ", T.stringof); } - - static if (is_some_int!T || is(T == bool)) - { - ptrdiff_t toInt() const pure nothrow @nogc - { - static if (T.max > ptrdiff_t.max) - debug assert(value <= ptrdiff_t.max); - return cast(ptrdiff_t)value; - } - } } -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; From 73cd497905665b60816c162002d33ee3ed01eb68 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 8 Jan 2026 12:48:48 +1000 Subject: [PATCH 57/73] Properly support template toString functions, and also improved/simplified the implementation. --- src/urt/string/format.d | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 613a819..ae564c6 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -181,26 +181,27 @@ StringifyFunc get_to_string_func(T)(ref T value) static if (overload_index >= 0) { - static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFunc)) - return &__traits(getOverloads, value, "toString", true)[overload_index]; + alias overload = __traits(getOverloads, T, "toString", true)[overload_index]; + static if (__traits(isTemplate, overload)) + alias to_string = overload!(); + else + alias to_string = overload; - // TODO: if toString is a template, we need to instantiate it and then captuire the function pointer... + // TODO: we can't alias the __traits(child) expression, so we need to repeat it everywhere! - static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced))// || -// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced)) - { - StringifyFunc d; - d.ptr = &value; - d.funcptr = &ToStringShim.shim1!T; - return d; - } + alias method_type = typeof(&__traits(child, value, to_string)); - static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced2))// || -// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced2)) + 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; - d.funcptr = &ToStringShim.shim2!T; + static if (is(method_type : StringifyFuncReduced)) + d.funcptr = &ToStringShim.shim1!T; + else + d.funcptr = &ToStringShim.shim2!T; return d; } From a672ee1ff40628a1b1dabd7ff389380ca1ce8357 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 8 Jan 2026 17:08:40 +1000 Subject: [PATCH 58/73] Fleshed out the wildcard match, and Claude added a shitload of tests! --- src/urt/string/package.d | 165 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 8 deletions(-) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 81cbf75..ad11e2c 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -459,19 +459,66 @@ unittest } -bool wildcardMatch(const(char)[] wildcard, const(char)[] value) pure +bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure { - // TODO: write this function... + const(char)* a = wildcard.ptr, ae = a + wildcard.length, b = value.ptr, be = b + value.length; + const(char)* star_a = null, star_b = null; - // HACK: we just use this for tail wildcards right now... - for (size_t i = 0; i < wildcard.length; ++i) + while (a < ae && b < be) { - if (wildcard[i] == '*') - return true; - if (wildcard[i] != value[i]) + char ca_orig = *a, cb_orig = *b; + char ca = ca_orig, cb = cb_orig; + + // handle escape + if (ca == '\\' && a + 1 < ae) + ca = *++a; + if (value_wildcard && cb == '\\' && b + 1 < be) + cb = *++b; + + // handle wildcards + if (ca_orig == '*') + { + star_a = ++a; + star_b = b; + continue; + } + if (value_wildcard && cb_orig == '*') + { + star_b = ++b; + star_a = a; + continue; + } + + // compare next char + if (ca_orig == '?' || (value_wildcard && cb_orig == '?') || ca == cb) + { + ++a; + ++b; + continue; + } + + // backtrack: expand previous * match + if (!star_a) return false; + a = star_a; + b = ++star_b; } - return wildcard.length == value.length; + + // skip past tail wildcards + while (a < ae && *a == '*') + ++a; + if (value_wildcard) + { + while (b < be && *b == '*') + ++b; + } + + // check for match + if (a == ae && (b == be || star_a !is null)) + return true; + if (value_wildcard && b == be && star_b !is null) + return true; + return false; } @@ -500,4 +547,106 @@ unittest 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")); + + // 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("\\*", "a")); + 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)); } From 3c483a258b1c06d04c479186a445ca8e8e5ae862 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 8 Jan 2026 17:47:49 +1000 Subject: [PATCH 59/73] Added case insensitive option, numeric match, and optional match. --- src/urt/string/package.d | 354 +++++++++++++++++++++++++++++++++++---- 1 file changed, 318 insertions(+), 36 deletions(-) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index ad11e2c..0e39201 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -459,68 +459,186 @@ unittest } -bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure +// 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 { const(char)* a = wildcard.ptr, ae = a + wildcard.length, b = value.ptr, be = b + value.length; - const(char)* star_a = null, star_b = null; - while (a < ae && b < be) + static inout(char)* skip_wilds(inout(char)* p, const(char)* pe) + { + 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 { - char ca_orig = *a, cb_orig = *b; - char ca = ca_orig, cb = cb_orig; + const(char)* a, b; + } + BacktrackState[64] backtrack_stack = void; + size_t backtrack_depth = 0; - // handle escape - if (ca == '\\' && a + 1 < ae) - ca = *++a; - if (value_wildcard && cb == '\\' && b + 1 < be) - cb = *++b; + 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 == '*') { - star_a = ++a; - star_b = b; - continue; + 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; } - if (value_wildcard && cb_orig == '*') + + // handle optionals + if (ca_orig == '~') { - star_b = ++b; - star_a = a; + 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 == '?' || (value_wildcard && cb_orig == '?') || ca == cb) + if (ca_orig == '#') { - ++a; - ++b; - continue; + 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; - // backtrack: expand previous * match - if (!star_a) + try_backtrack: + if (backtrack_depth == 0) return false; - a = star_a; - b = ++star_b; + + --backtrack_depth; + a = backtrack_stack[backtrack_depth].a; + b = backtrack_stack[backtrack_depth].b; + continue; + + advance: + ++a, ++b; } - // skip past tail wildcards - while (a < ae && *a == '*') - ++a; - if (value_wildcard) + // check the strings are equal... + if (a == ae) { - while (b < be && *b == '*') - ++b; + if (value_wildcard) + b = skip_wilds(b, be); + if (b == be) + return true; } - - // check for match - if (a == ae && (b == be || star_a !is null)) - return true; - if (value_wildcard && b == be && star_b !is null) + else if (b == be && skip_wilds(a, ae) == ae) return true; - return false; + + 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 { @@ -560,6 +678,149 @@ unittest 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")); @@ -594,8 +855,11 @@ unittest // 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("\\*\\?\\\\", "*?\\")); @@ -649,4 +913,22 @@ unittest 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")); } From 2233eecf97396f04fb31bed1962f2377d64724cb Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 21 Jan 2026 10:08:24 +1000 Subject: [PATCH 60/73] Fixed unit parsing. - Moved the scaling factor outside of the SI unit parsing, since all units can be scaled. - Fixed an issue where some units could incorrectly have SI prefixes added to them. --- src/urt/si/unit.d | 163 ++++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index c0c4ae5..8994aef 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -595,85 +595,85 @@ nothrow: if (p == 0) return -1; // invalid exponent - if (const ScaledUnit* su = term in noScaleUnitMap) + size_t offset = 0; + + // parse the scale factor + int e = 0; + if (term[0].is_numeric) + { + ulong sf = term.parse_uint_with_exponent(e, &offset); + preScale *= sf; + } + + if (offset == term.length) + r *= ScaledUnit(Unit(), e); + else if (const ScaledUnit* su = term[offset .. $] in noScaleUnitMap) + { r *= (*su) ^^ (invert ? -p : p); + preScale *= 10.0^^e; + } else { - size_t offset = 0; - - // parse the scale factor - int e = 0; - if (term[0].is_numeric) + // try and parse SI prefix... + switch (term[offset]) { - ulong sf = term.parse_uint_with_exponent(e, &offset); - preScale *= sf; + 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) - r *= ScaledUnit(Unit(), e); - else + return -1; + + term = term[offset .. $]; + if (const Unit* u = term in unitMap) { - // try and parse SI prefix... - switch (term[offset]) + if (term == "kg") { - 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) + // we alrady parsed the 'k', so this string must have been "kkg", which is nonsense return -1; - - term = term[offset .. $]; - if (const Unit* u = term in unitMap) - { - if (term == "kg") - { - // we alrady parsed the 'k' - 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 noScaleUnitMap) - { - r *= (*su) ^^ (invert ? -p : p); - preScale *= 10^^e; } - else - return -1; // string was not taken? + 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); + preScale *= 10.0^^e; } + else + return -1; // string was not taken? } if (sep == '/') @@ -986,7 +986,7 @@ immutable Unit[string] unitMap = [ "kg" : Kilogram, "s" : Second, "A" : Ampere, - "°K" : Kelvin, + "K" : Kelvin, "cd" : Candela, "rad" : Radian, @@ -1030,24 +1030,31 @@ immutable ScaledUnit[string] noScaleUnitMap = [ "deg" : Degree, "°C" : Celsius, "°F" : Fahrenheit, - "Ah" : AmpereHour, - "Wh" : WattHour, "cy" : Cycle, - "Hz" : Hertz, "psi" : PSI, - - // questionable... :/ - "VAh" : WattHour, - "varh" : WattHour, + "%" : Percent, + "‰" : Permille, + "‱" : ScaledUnit(Unit(), -4), + "ppm" : ScaledUnit(Unit(), -6), ]; +// these can have SI prefixes immutable ScaledUnit[string] scaledUnitMap = [ - "%" : Percent, - "‰" : Permille, "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 takePower(ref const(char)[] s) pure { size_t e = s.findFirst('^'); From 569e27bd85db0b5ea78729a2c192e4dc1aae1935 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 24 Jan 2026 16:21:41 +1000 Subject: [PATCH 61/73] Trim underscore from enum keys, which often exist to disambiguate from keywords. - Tweak `trim` to accept a predicate (default to `is_whitespace`) - Tweak `uni_seq_len` to remove some redundant logic. - Fix `uni_compare` and simplify the implementation a bit. --- src/urt/meta/package.d | 5 +++- src/urt/string/package.d | 10 +++---- src/urt/string/uni.d | 65 +++++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index bb6e8d9..c703fc5 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -391,7 +391,7 @@ private: return total; }(); - enum MakeKI(ushort i) = KI(enum_members[i], i); + 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]; @@ -487,6 +487,9 @@ VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] va private: +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 diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 0e39201..08025ea 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -223,25 +223,25 @@ bool endsWith(const(char)[] s, const(char)[] suffix) pure return cmp(s[$ - suffix.length .. $], suffix) == 0; } -inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure +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 && is_whitespace(s.ptr[first])) + while (first < s.length && pred(s.ptr[first])) ++first; } static if (Back) { - while (last > first && is_whitespace(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) pure { diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index ce7f7d1..8447be9 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -6,36 +6,38 @@ import urt.traits : is_some_char; pure nothrow @nogc: -size_t uni_seq_len(const(char)[] s) +size_t uni_seq_len(const(char)[] str) { - if (s.length == 0) - return 0; + 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 (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; else if ((s[0] & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx - return (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : - (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + 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 (s.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : - (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : - (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + 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)[] s) +size_t uni_seq_len(const(wchar)[] str) { - if (s.length == 0) - return 0; - if (s[0] >= 0xD800 && s[0] < 0xDC00 && s.length >= 2 && s[1] >= 0xDC00 && s[1] < 0xE000) + 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) - => s.length > 0; + => 1; size_t uni_strlen(C)(const(C)[] s) if (is_some_char!C) @@ -552,38 +554,41 @@ int uni_compare(T, U)(const(T)[] s1, const(U)[] s2) // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) - while (p1 < p1end && p2 < p2end) + while (true) { - dchar a = *p1; + // 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) { - dchar b = *p2; if (a != b) - { - if (b >= 0x80) - { - size_t _; - b = next_dchar(p2[0 .. p2end - p2], _); - } - return cast(int)a - cast(int)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); - dchar b = next_dchar(p2[0 .. p2end - p2], bl); + b = next_dchar(p2[0 .. p2end - p2], bl); if (a != b) return cast(int)a - cast(int)b; p1 += al; - p2 += 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; } int uni_compare_i(T, U)(const(T)[] s1, const(U)[] s2) From f7de5c1c528a2a141e9a214515498d71a79e7bd4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 24 Jan 2026 20:58:23 +1000 Subject: [PATCH 62/73] Add a function to adjust a Quantity scale. Minimise error accumulation from floating point precision loss from unnecessary arithmetic. --- src/urt/si/quantity.d | 90 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index df9775f..0d5a6b2 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -69,7 +69,7 @@ nothrow @nogc: assert(isCompatible(b), "Incompatible units!"); else static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - value = adjustScale(b); + value = adjust_scale(b); } } @@ -97,7 +97,7 @@ nothrow @nogc: assert(isCompatible(b), "Incompatible units!"); else static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - value = adjustScale(b); + value = adjust_scale(b); } } @@ -132,7 +132,7 @@ nothrow @nogc: static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); Quantity!(TypeForOp!(op, T, U), _unit) r; - r.value = mixin("value " ~ op ~ " adjustScale(b)"); + r.value = mixin("value " ~ op ~ " adjust_scale(b)"); static if (Dynamic) r.unit = unit; return r; @@ -211,7 +211,7 @@ nothrow @nogc: assert(isCompatible(r), "Incompatible units!"); else static assert(IsCompatible!_U, "Incompatible units: ", r.unit.toString, " and ", unit.toString); - r.value = cast(U)r.adjustScale(this); + r.value = cast(U)r.adjust_scale(this); static if (T.Dynamic) r.unit = unit; return r; @@ -258,7 +258,7 @@ nothrow @nogc: rhs = rhs*rScale + rTrans; }} else - rhs = adjustScale(rh); + rhs = adjust_scale(rh); compare: static if (epsilon == 0) @@ -287,7 +287,19 @@ nothrow @nogc: } else Quantity!(T, ScaledUnit(unit.unit)) r; - r.value = r.adjustScale(this); + 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; } @@ -354,40 +366,48 @@ nothrow @nogc: } private: - T adjustScale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure + T adjust_scale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure { - static if (Dynamic) - { - auto lScale = unit.scale!true(); - auto lTrans = unit.offset!true(); - } + static if (!Dynamic && !b.Dynamic && unit == b.unit) + return cast(T)b.value; 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(); - } + if (unit == b.unit) + return cast(T)b.value; - static if (Dynamic || b.Dynamic) - { - auto scale = lScale*rScale; - auto trans = lTrans + lScale*rTrans; - } - else - { - enum scale = lScale*rScale; - enum trans = lTrans + lScale*rTrans; + 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); } } From 5d854a89cc3d7916a48b7efba527f21c0b98f7fd Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 25 Jan 2026 19:27:04 +1000 Subject: [PATCH 63/73] Add some helpers to StringCacheBuilder --- src/urt/string/string.d | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 1cf61e2..e3349c1 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -45,6 +45,7 @@ nothrow @nogc: ushort add_string(const(char)[] s) pure { 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) @@ -69,6 +70,15 @@ nothrow @nogc: 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; From 0efbdde6382783953854b7486f2387a7b580aa8e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 28 Jan 2026 15:22:17 +1000 Subject: [PATCH 64/73] Array!char.concat/append was extended to support the same set of arguments as string concatenation. --- src/urt/array.d | 136 ++++++++++++++++++++++++++++++++++------ src/urt/string/string.d | 22 +++---- 2 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 6674151..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; +import urt.traits : is_some_char, is_primitive, is_trivial; nothrow @nogc: @@ -314,17 +314,120 @@ nothrow @nogc: } // manipulation - ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + static if (!is_some_char!T) { - reserve(_length + things.length); - static foreach (i; 0 .. things.length) + alias concat = append; // TODO: REMOVE THIS ALIAS, we phase out the old name... + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) { - static if (is(T == class) || is(T == interface)) - ptr[_length++] = things[i]; - else - emplace!T(&ptr[_length++], forward!(things[i])); + 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; } - 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 @@ -592,18 +695,9 @@ nothrow @nogc: return [x, y]; } - void opOpAssign(string op : "~", U)(auto ref U el) - if (is(U : T)) - { - pushBack(forward!el); - } - - 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) @@ -700,6 +794,8 @@ nothrow @nogc: } private: + enum copy_elements = is(T == class) || is(T == interface) || is_primitive!T || is_trivial!T; + T* ptr; uint _length; diff --git a/src/urt/string/string.d b/src/urt/string/string.d index e3349c1..be23b03 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -700,9 +700,9 @@ nothrow @nogc: return this; } - ref MutableString!Embed appendFormat(Things...)(const(char)[] format, auto ref Things args) + ref MutableString!Embed append_format(Things...)(const(char)[] format, auto ref Things args) { - insertFormat(length(), format, forward!args); + insert_format(length(), format, forward!args); return this; } @@ -718,7 +718,7 @@ nothrow @nogc: { if (ptr) writeLength(0); - insertFormat(0, format, forward!args); + insert_format(0, format, forward!args); return this; } @@ -750,7 +750,7 @@ nothrow @nogc: return this; } - ref MutableString!Embed insertFormat(Things...)(size_t offset, const(char)[] format, auto ref Things args) + 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, next_power_of_2; @@ -923,11 +923,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 @@ -948,13 +948,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 From 361a2839f77b8936e8ceebcc45e5186ea06ca55f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 30 Jan 2026 09:37:07 +1000 Subject: [PATCH 65/73] Fix variant opCmp --- src/urt/variant.d | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/urt/variant.d b/src/urt/variant.d index d6aebdd..8b2a944 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -426,21 +426,19 @@ nothrow @nogc: static double asDoubleWithBool(ref const Variant v) => v.isBool() ? double(v.asBool()) : v.asDouble(); - if (a.isQuantity || b.isQuantity) + uint aunit = a.isQuantity ? a.count : 0; + uint bunit = b.isQuantity ? b.count : 0; + if (aunit || bunit) { // we can't compare different units - uint aunit = a.isQuantity ? (a.count & 0xFFFFFF) : 0; - uint bunit = b.isQuantity ? (b.count & 0xFFFFFF) : 0; - if (aunit != bunit) + if ((aunit & 0xFFFFFF) != (bunit & 0xFFFFFF)) { - r = aunit - bunit; + r = (aunit & 0xFFFFFF) - (bunit & 0xFFFFFF); break; } // matching units, but we'll only do quantity comparison if there is some scaling - ubyte ascale = a.isQuantity ? (a.count >> 24) : 0; - ubyte bscale = b.isQuantity ? (b.count >> 24) : 0; - if (ascale || bscale) + 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)); @@ -449,7 +447,7 @@ nothrow @nogc: } } - if (a.flags & Flags.FloatFlag || b.flags & Flags.FloatFlag) + 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? From 2be4ae3169134740b97d5403fc35be830140a19f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Feb 2026 14:02:41 +1000 Subject: [PATCH 66/73] Improve the DateTime parsing, and supported DateTime -> SysTime conversion. --- src/urt/time.d | 224 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 173 insertions(+), 51 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 20857dc..7cca87f 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -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 @@ -468,27 +474,31 @@ pure nothrow @nogc: nsecs -= digit * digit_multipliers[m++]; } } + // TODO: timezone suffix? return offset; } ptrdiff_t fromString(const(char)[] s) { - import urt.conv : parse_int; + import urt.conv : parse_int, parse_uint; import urt.string.ascii : ieq, is_numeric, is_whitespace, to_lower; - if (s.length < 14) - return -1; + month = Month.Unspecified; + day = 0; + hour = 0; + minute = 0; + second = 0; + ns = 0; + size_t offset = 0; // parse year - if (s[0..2].ieq("bc")) + if (s.length >= 2 && s[0..2].ieq("bc")) { offset = 2; - if (s[2] == ' ') + if (s.length >= 3 && s[2] == ' ') ++offset; } - if (s[offset] == '+') - return -1; size_t len; long value = s[offset..$].parse_int(&len); if (len == 0) @@ -504,13 +514,15 @@ pure nothrow @nogc: year = cast(short)value; offset += len; - if (s[offset] != '-' && s[offset] != '/') - return -1; + 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])) @@ -528,8 +540,8 @@ pure nothrow @nogc: month = cast(Month)value; offset += len; - if (s[offset] != '-' && s[offset] != '/') - return -1; + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; // parse day value = s[++offset..$].parse_int(&len); @@ -538,8 +550,8 @@ pure nothrow @nogc: day = cast(ubyte)value; offset += len; - if (offset >= s.length || (s[offset] != 'T' && s[offset] != ' ')) - return -1; + if (offset == s.length || (s[offset] != 'T' && s[offset] != ' ')) + return offset; // parse hour value = s[++offset..$].parse_int(&len); @@ -548,49 +560,116 @@ pure nothrow @nogc: hour = cast(ubyte)value; offset += len; - if (offset >= s.length || s[offset++] != ':') - return -1; + if (offset == s.length) + return 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 (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 || s[offset++] != ':') - return -1; + if (offset == s.length) + return 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 (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; - ns = 0; - if (offset < s.length && s[offset] == '.') - { - // parse fractions - ++offset; - uint multiplier = 100_000_000; - while (offset < s.length && multiplier > 0 && s[offset].is_numeric) + 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] == '.') { - ns += (s[offset++] - '0') * multiplier; - multiplier /= 10; + // fraction of minute + assert(false, "TODO"); } - if (multiplier == 100_000_000) - return -1; // no number after the dot + } + else if (s[offset] == '.') + { + // fraction of hour + assert(false, "TODO"); } - if (offset < s.length && s[offset].to_lower == 'z') + if (offset == s.length) + return offset; + + if (s[offset].to_lower == 'z') { - ++offset; // TODO: UTC timezone designator... - assert(false, "TODO: we need to know our local timezone..."); +// assert(false, "TODO: we need to know our local timezone..."); + + return offset + 1; } - return offset; + 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; } } @@ -663,7 +742,14 @@ SysTime getSysTime() SysTime getSysTime(DateTime time) pure { - assert(false, "TODO: convert to SysTime..."); + version (Windows) + return dateTimeToFileTime(time); + else version (Posix) + { + assert(false, "TODO"); + } + else + static assert(false, "TODO"); } DateTime getDateTime() @@ -896,8 +982,18 @@ unittest 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.") == -1); - assert(dt.fromString("2025-01-01") == -1); + 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); @@ -934,6 +1030,32 @@ 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) { From 114e944c7a75dffd04cba0cce32413038565cd0d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Feb 2026 14:03:26 +1000 Subject: [PATCH 67/73] Move function out of urt. --- src/urt/string/package.d | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 08025ea..4fd0e24 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -243,19 +243,6 @@ alias trimFront(alias pred = is_whitespace) = trim!(pred, true, false); alias trimBack(alias pred = is_whitespace) = trim!(pred, false, true); -inout(char)[] trimComment(char Delimiter)(inout(char)[] s) pure -{ - 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 { for (size_t i = 0; i < s.length; ++i) From fe4b7002043120d391c7f04ecf1383f93fc5a0a6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Feb 2026 14:00:54 +1000 Subject: [PATCH 68/73] Parse e-notation, delete the `with_decimal` ones, normalise, and reorganise these implementations. --- src/urt/conv.d | 280 +++++++++++++++++++++--------------------- src/urt/format/json.d | 33 +++-- src/urt/math.d | 1 + src/urt/variant.d | 8 +- 4 files changed, 158 insertions(+), 164 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 160eb11..dfdc9f0 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -8,47 +8,52 @@ nothrow @nogc: // on error or not-a-number cases, bytes_taken will contain 0 -long parse_int(const(char)[] str, size_t* bytes_taken = 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++; - } - - ulong value = str.ptr[i .. str.length].parse_uint(bytes_taken, base); +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 += i; - return neg ? -cast(long)value : cast(long)value; + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -long parse_int_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = 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].parse_uint_with_decimal(fixed_point_divisor, bytes_taken, base); +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 += i; - return neg ? -cast(long)value : cast(long)value; + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -ulong parse_uint(const(char)[] str, size_t* bytes_taken = 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; @@ -81,11 +86,19 @@ ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) p return value; } -ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - import urt.util : ctz, is_power_of_2; + 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; +} - assert(base > 1 && base <= 36, "Invalid base"); +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; @@ -129,8 +142,11 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte } // number has no decimal point, tail zeroes are positive exp - exp = digits ? zero_seq : 0; - goto done; + if (!digits) + goto nothing; + + exp = zero_seq; + goto check_exp; parse_decimal: for (; s < e; ++s) @@ -159,120 +175,83 @@ parse_decimal: zero_seq = 0; } if (!digits) - exp = 0; // didn't parse any digits; reset exp to 0 + goto nothing; -done: - exponent = exp; - if (bytes_taken) - *bytes_taken = s - str.ptr; - return value; -} - -unittest -{ - int e; - size_t taken; - 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); -} - -ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure -{ - assert(base > 1 && base <= 36, "Invalid base"); - - ulong value = 0; - ulong divisor = 1; - - const(char)* s = str.ptr; - const(char)* e = s + str.length; - - // TODO: we could optimise the common base <= 10 case... - - for (; s < e; ++s) +check_exp: + // check for exponent part + if (s < e && (*s == 'e' || *s == 'E')) { - char c = *s; - - if (c == '.') + ++s; + bool exp_neg = false; + if (s < e) { - if (s == str.ptr) - goto done; - ++s; - goto parse_decimal; + char c = *s; + exp_neg = c == '-'; + if (exp_neg || c == '+') + ++s; } - uint digit = get_digit(c); - if (digit >= base) - break; - value = value*base + digit; - } - goto done; - -parse_decimal: - for (; s < e; ++s) - { - uint digit = get_digit(*s); - if (digit >= base) + int exp_value = 0; + const(char)* t = s; + for (; t < e; ++t) { - // 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; - break; + uint digit = *t - '0'; + if (digit > 9) + break; + exp_value = exp_value * 10 + digit; + } + if (t > s) + { + exp += exp_neg ? -exp_value : exp_value; + s = t; } - value = value*base + digit; - divisor *= base; } done: - fixed_point_divisor = divisor; + exponent = exp; if (bytes_taken) *bytes_taken = s - str.ptr; return value; -} -long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure -{ - const(char)* p = str.ptr; - int base = str.parse_base_prefix(); - if (base == 10) - return str.parse_int(bytes_taken); - ulong i = str.parse_uint(bytes_taken, base); - if (bytes_taken && *bytes_taken != 0) - *bytes_taken += str.ptr - p; - return i; +nothing: + exp = 0; + goto done; } -ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = 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 = str.parse_base_prefix(); - ulong i = str.parse_uint(bytes_taken, base); - if (bytes_taken && *bytes_taken != 0) - *bytes_taken += 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(parse_uint("123") == 123); assert(parse_int("+123.456") == 123); assert(parse_int("-123.456", null, 10) == -123); - assert(parse_uint_with_decimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); - assert(parse_int_with_decimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); assert(parse_int("11001", null, 2) == 25); - assert(parse_int_with_decimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); 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("10e+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 5); + assert("0.01E+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 0 && taken == 7); } int parse_int_fast(ref const(char)[] text, out bool success) pure @@ -337,17 +316,25 @@ unittest // 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, int base = 10) pure +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.parse_int_with_decimal(div, &taken, base); + 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 @@ -362,6 +349,7 @@ unittest 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); } @@ -626,7 +614,7 @@ template to(T) { long to(const(char)[] str) { - int base = parse_base_prefix(str); + uint base = parse_base_prefix(str); size_t taken; long r = parse_int(str, &taken, base); assert(taken == str.length, "String is not numeric"); @@ -637,7 +625,7 @@ template to(T) { double to(const(char)[] str) { - int base = parse_base_prefix(str); + uint base = parse_base_prefix(str); size_t taken; double r = parse_float(str, &taken, base); assert(taken == str.length, "String is not numeric"); @@ -681,35 +669,43 @@ template to(T) private: +// valid result is 0 .. 35; result is garbage outside that bound uint get_digit(char c) pure { uint zero_base = c - '0'; if (zero_base < 10) return zero_base; - uint A_base = c - 'A'; - if (A_base < 26) - return A_base + 10; - uint a_base = c - 'a'; - if (a_base < 26) - return a_base + 10; - return -1; + uint a_base = (c | 0x20) - 'a'; + return 10 + a_base; } -int parse_base_prefix(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; +} + /+ size_t format_struct(T)(ref T value, char[] buffer) nothrow @nogc diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 9a4e7dd..5866a3c 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -399,31 +399,28 @@ Variant parse_node(ref const(char)[] text) } 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 .. $].parse_uint_with_decimal(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) + // let's work out if value*10^^e is an integer? + bool is_integer = e >= 0; + for (; e > 0; --e) { - double d = cast(double)value; - if (neg) - d = -d; - d /= div; - return Variant(d); - } - else - { - 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/math.d b/src/urt/math.d index e3b06b8..ff63a20 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -63,6 +63,7 @@ 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) diff --git a/src/urt/variant.d b/src/urt/variant.d index 8b2a944..033f072 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1060,8 +1060,8 @@ nothrow @nogc: { size_t taken; ScaledUnit unit; - ulong div; - long i = s.parse_int_with_decimal(div, &taken, 10); + int e; + long i = s.parse_int_with_exponent(e, &taken, 10); if (taken < s.length) { size_t t2 = unit.fromString(s[taken .. $]); @@ -1070,8 +1070,8 @@ nothrow @nogc: } if (taken == s.length) { - if (div != 1) - this = double(i) / div; + if (e != 0) + this = i * 10.0^^e; else this = i; if (unit.pack) From a913c441e30bb1aa4bb8bfb2a98cbc0511dcf9e6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 2 Feb 2026 17:12:04 +1000 Subject: [PATCH 69/73] Fix CTFE implementation of qsort! --- src/urt/algorithm.d | 64 +++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 9995cfb..d44fec3 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -132,6 +132,9 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg void qsort(alias pred = void, T)(T[] arr) pure { + if (arr.length <= 1) + return; + version (SmallSize) enum use_small_size_impl = true; else @@ -157,39 +160,44 @@ void qsort(alias pred = void, T)(T[] arr) pure 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(p[i], *pivot) < 0) i++; - while (pred(p[j], *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; + + swap(p[i], p[j]); + ++i; + --j; } - - if (j > 0) - qsort!pred(p[0 .. j + 1]); - if (i < arr.length) - qsort!pred(p[i .. arr.length]); } + + if (j >= 0) + qsort!pred(p[0 .. j + 1]); + if (i < n) + qsort!pred(p[i .. n]); } } From 73eb47134c6a817a302c5477742985d5ebec191d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 2 Feb 2026 17:11:47 +1000 Subject: [PATCH 70/73] Quantity fix. --- src/urt/si/quantity.d | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 0d5a6b2..bf9ae9c 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -146,11 +146,12 @@ nothrow @nogc: return mixin("this.value " ~ op ~ " value"); else { - Quantity!(TypeForOp!(op, T, U), unit) 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); } } From 6925ddd0f3ab5e15f7d20a1c92e5f416750ad538 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 7 Feb 2026 02:10:40 +1000 Subject: [PATCH 71/73] Fixed integer division. --- src/urt/si/unit.d | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 8994aef..b9be825 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -423,12 +423,12 @@ nothrow: 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]; @@ -436,9 +436,9 @@ nothrow: } 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; @@ -809,6 +809,17 @@ nothrow: 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 { @@ -881,9 +892,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 From 08df31921c796d6072da3aba7e2db30b6acf7583 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 5 Feb 2026 00:19:47 +1000 Subject: [PATCH 72/73] Reserve null in the string cache. --- src/urt/string/string.d | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index be23b03..ccb06f9 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -37,13 +37,17 @@ struct StringCacheBuilder nothrow @nogc: this(char[] buffer) pure { - assert(buffer.length <= ushort.max, "Buffer too long"); + assert(buffer.length >= 2 && buffer.length <= ushort.max, "Invalid buffer length"); + buffer[0..2] = 0; this._buffer = buffer; - this._offset = 0; + 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) From c4ede51db2ef41565946b7b8d39c3d87f0d0ca15 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 8 Feb 2026 18:37:14 +1000 Subject: [PATCH 73/73] Added parse_quantity which is like parse_float, but with a unit suffix. --- src/urt/conv.d | 92 +++++++++++++++++++++++++++++++--------- src/urt/si/quantity.d | 2 +- src/urt/si/unit.d | 29 +++++++------ src/urt/string/package.d | 62 ++++++++++++--------------- 4 files changed, 116 insertions(+), 69 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index dfdc9f0..f0ab5c1 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -1,6 +1,7 @@ module urt.conv; import urt.meta; +import urt.si.quantity; import urt.string; public import urt.string.format : toString; @@ -107,10 +108,11 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte int exp = 0; uint digits = 0; uint zero_seq = 0; + char c = void; for (; s < e; ++s) { - char c = *s; + c = *s; if (c == '.') { @@ -151,7 +153,7 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte parse_decimal: for (; s < e; ++s) { - char c = *s; + c = *s; if (c == '0') { @@ -179,32 +181,32 @@ parse_decimal: check_exp: // check for exponent part - if (s < e && (*s == 'e' || *s == 'E')) + if (s + 1 < e && ((*s | 0x20) == 'e')) { - ++s; - bool exp_neg = false; - if (s < e) + c = s[1]; + bool exp_neg = c == '-'; + if (exp_neg || c == '+') { - char c = *s; - exp_neg = c == '-'; - if (exp_neg || c == '+') - ++s; + if (s + 2 >= e || !s[2].is_numeric) + goto done; + s += 2; + } + else + { + if (!c.is_numeric) + goto done; + ++s; } int exp_value = 0; - const(char)* t = s; - for (; t < e; ++t) + for (; s < e; ++s) { - uint digit = *t - '0'; + uint digit = *s - '0'; if (digit > 9) break; exp_value = exp_value * 10 + digit; } - if (t > s) - { - exp += exp_neg ? -exp_value : exp_value; - s = t; - } + exp += exp_neg ? -exp_value : exp_value; } done: @@ -250,8 +252,12 @@ unittest 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("10e+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 5); + 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 parse_int_fast(ref const(char)[] text, out bool success) pure @@ -354,6 +360,50 @@ unittest 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; + + 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) { @@ -676,7 +726,7 @@ uint get_digit(char c) pure if (zero_base < 10) return zero_base; uint a_base = (c | 0x20) - 'a'; - return 10 + a_base; + return 10 + (a_base & 0xFF); } uint parse_base_prefix(ref const(char)* str, const(char)* end) pure @@ -703,7 +753,7 @@ uint parse_sign(ref const(char)* str, const(char)* end) pure if (neg > 2 || neg == 1) return 0; ++str; - return neg; + return neg; // neg is 0 (+) or 2 (-) } diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index bf9ae9c..e293ebb 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -7,7 +7,7 @@ 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)); diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index b9be825..7bc059b 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -32,7 +32,7 @@ nothrow @nogc: // -enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parseUnit(desc, f); assert(e > 0, "Invalid unit"); assert(f == 1, "Unit requires pre-scale"); return r; }(); +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 @@ -267,7 +267,7 @@ nothrow: char sep; while (const(char)[] unit = s.split!('/', '*')(sep)) { - int p = unit.takePower(); + int p = unit.take_power(); if (p == 0) return -1; // invalid power @@ -565,11 +565,12 @@ nothrow: bool opEquals(Unit rh) const pure => (pack & 0xFF000000) ? false : unit == rh; - ptrdiff_t parseUnit(const(char)[] s, out float preScale) pure + 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; - preScale = 1; + pre_scale = 1; if (s.length == 0) { @@ -582,18 +583,20 @@ nothrow: { if (s.length == 1) return -1; - preScale = -1; + pre_scale = -1; s = s[1 .. $]; } ScaledUnit r; bool invert; char sep; - while (const(char)[] term = s.split!('/', '*')(sep)) + while (const(char)[] term = s.split!(['/', '*'], false, false)(&sep)) { - int p = term.takePower(); + int p = term.take_power(); if (p == 0) return -1; // invalid exponent + if (term.length == 0) + return -1; size_t offset = 0; @@ -601,8 +604,10 @@ nothrow: 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); - preScale *= sf; + pre_scale *= sf; } if (offset == term.length) @@ -610,7 +615,7 @@ nothrow: else if (const ScaledUnit* su = term[offset .. $] in noScaleUnitMap) { r *= (*su) ^^ (invert ? -p : p); - preScale *= 10.0^^e; + pre_scale *= 10.0^^e; } else { @@ -670,7 +675,7 @@ nothrow: else if (const ScaledUnit* su = term in noScaleUnitMapSI) { r *= (*su) ^^ (invert ? -p : p); - preScale *= 10.0^^e; + pre_scale *= 10.0^^e; } else return -1; // string was not taken? @@ -800,7 +805,7 @@ nothrow: ptrdiff_t fromString(const(char)[] s) pure { float scale; - ptrdiff_t r = parseUnit(s, scale); + ptrdiff_t r = parse_unit(s, scale); if (scale != 1) return -1; return r; @@ -1066,7 +1071,7 @@ immutable ScaledUnit[string] noScaleUnitMapSI = [ "varh" : WattHour, ]; -int takePower(ref const(char)[] s) pure +int take_power(ref const(char)[] s) pure { size_t e = s.findFirst('^'); if (e < s.length) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 4fd0e24..ca5ee74 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -265,20 +265,26 @@ inout(char)[] takeLine(ref inout(char)[] s) pure return t; } -inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] s) pure +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; @@ -288,39 +294,25 @@ 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) pure -{ - 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; } +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?