Explorar o código

Added string builder ToNumber methods. (#390)

* Added string builder ToNumber methods.

Implemented more efficient strto number functions.

* Formatting and replaced SIZE_T_MAX with SIZE_MAX
Brucey hai 2 semanas
pai
achega
b7867fd7a6

+ 1 - 0
blitz.mod/blitz.bmx

@@ -123,6 +123,7 @@ Import "blitz_gc.c"
 Import "blitz_unicode.c"
 Import "blitz_unicode.c"
 Import "blitz_enum.c"
 Import "blitz_enum.c"
 Import "blitz_coverage.c"
 Import "blitz_coverage.c"
+Import "blitz_strto.c"
 Import "blitz_string_ex.cpp"
 Import "blitz_string_ex.cpp"
 
 
 ?coverage
 ?coverage

+ 1 - 0
blitz.mod/blitz.h

@@ -40,6 +40,7 @@
 #include "blitz_app.h" 
 #include "blitz_app.h" 
 #include "blitz_enum.h"
 #include "blitz_enum.h"
 #include "blitz_coverage.h"
 #include "blitz_coverage.h"
+#include "blitz_strto.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C"{
 extern "C"{

+ 3 - 0
blitz.mod/blitz_string.h

@@ -182,6 +182,9 @@ int	bbStringToSizeTEx( BBString *str, BBSIZET *val, int start, int end, BBULONG
 int	bbStringToLongIntEx( BBString *str, BBLONGINT *val, int start, int end, BBULONG format, int base );
 int	bbStringToLongIntEx( BBString *str, BBLONGINT *val, int start, int end, BBULONG format, int base );
 int	bbStringToULongIntEx( BBString *str, BBULONGINT *val, int start, int end, BBULONG format, int base );
 int	bbStringToULongIntEx( BBString *str, BBULONGINT *val, int start, int end, BBULONG format, int base );
 
 
+int bbStrToDoubleEx( BBChar *buf, int length, double * val, int startPos, int endPos, BBULONG format, BBString* sep );
+int bbStrToFloatEx( BBChar *buf, int length, float * val, int startPos, int endPos, BBULONG format, BBString* sep );
+
 BBUINT* bbStringToUTF32String( BBString *str );
 BBUINT* bbStringToUTF32String( BBString *str );
 BBString* bbStringFromUTF32String( const BBUINT *p );
 BBString* bbStringFromUTF32String( const BBUINT *p );
 BBString* bbStringFromUTF32Bytes( const BBUINT *p, int n );
 BBString* bbStringFromUTF32Bytes( const BBUINT *p, int n );

+ 16 - 8
blitz.mod/blitz_string_ex.cpp

@@ -30,14 +30,18 @@ typedef LONG_PTR LPARAM;
 // endPos of -1 means the end of the string
 // endPos of -1 means the end of the string
 // returns 0 if the string is not a valid double, or the position of the first character after the double otherwise
 // returns 0 if the string is not a valid double, or the position of the first character after the double otherwise
 int bbStringToDoubleEx( BBString *str, double * val, int startPos, int endPos, BBULONG format, BBString* sep ) {
 int bbStringToDoubleEx( BBString *str, double * val, int startPos, int endPos, BBULONG format, BBString* sep ) {
-    if ( startPos < 0 || startPos >= str->length || endPos < -1 || endPos > str->length ) {
+    return bbStrToDoubleEx( str->buf, str->length, val, startPos, endPos, format, sep );
+}
+
+int bbStrToDoubleEx( BBChar *buf, int length, double * val, int startPos, int endPos, BBULONG format, BBString* sep ) {
+    if ( startPos < 0 || startPos >= length || endPos < -1 || endPos > length ) {
         return 0;
         return 0;
     }
     }
     if (endPos == -1) {
     if (endPos == -1) {
-        endPos = str->length;
+        endPos = length;
     }
     }
-    const char16_t * start = (char16_t*)str->buf;
-    const char16_t * end = start + str->length;
+    const char16_t * start = (char16_t*)buf;
+    const char16_t * end = start + length;
     const char16_t * p = start + startPos;
     const char16_t * p = start + startPos;
     const char16_t * e = start + endPos;
     const char16_t * e = start + endPos;
     const char16_t sepChar = sep->length > 0 ? sep->buf[0] : '.';
     const char16_t sepChar = sep->length > 0 ? sep->buf[0] : '.';
@@ -65,14 +69,18 @@ int bbStringToDoubleEx( BBString *str, double * val, int startPos, int endPos, B
 // endPos of -1 means the end of the string
 // endPos of -1 means the end of the string
 // returns 0 if the string is not a valid float, or the position of the first character after the float otherwise
 // returns 0 if the string is not a valid float, or the position of the first character after the float otherwise
 int bbStringToFloatEx( BBString *str, float * val, int startPos, int endPos, BBULONG format, BBString* sep ) {
 int bbStringToFloatEx( BBString *str, float * val, int startPos, int endPos, BBULONG format, BBString* sep ) {
-    if ( startPos < 0 || startPos >= str->length || endPos < -1 || endPos > str->length ) {
+    return bbStrToFloatEx( str->buf, str->length, val, startPos, endPos, format, sep );
+}
+
+int bbStrToFloatEx( BBChar *buf, int length, float * val, int startPos, int endPos, BBULONG format, BBString* sep ) {
+    if ( startPos < 0 || startPos >= length || endPos < -1 || endPos > length ) {
         return 0;
         return 0;
     }
     }
     if (endPos == -1) {
     if (endPos == -1) {
-        endPos = str->length;
+        endPos = length;
     }
     }
-    const char16_t * start = (char16_t*)str->buf;
-    const char16_t * end = start + str->length;
+    const char16_t * start = (char16_t*)buf;
+    const char16_t * end = start + length;
     const char16_t * p = start + startPos;
     const char16_t * p = start + startPos;
     const char16_t * e = start + endPos;
     const char16_t * e = start + endPos;
     const char16_t sepChar = sep->length > 0 ? sep->buf[0] : '.';
     const char16_t sepChar = sep->length > 0 ? sep->buf[0] : '.';

+ 368 - 0
blitz.mod/blitz_strto.c

@@ -0,0 +1,368 @@
+
+#include "blitz.h"
+
+#include <limits.h>
+#include <errno.h>
+#include <stdint.h>
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+#  define BB_INLINE inline
+#else
+#  define BB_INLINE /* nothing */
+#endif
+
+static BB_INLINE int bbIsspace(BBChar c) {
+    return c == 0x20 || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
+}
+static BB_INLINE int bbIsdigit(BBChar c) {
+    return c >= '0' && c <= '9';
+}
+
+/* Convert a BBChar to a digit value for the given base (2,10,16). 
+   Returns 0..base-1 on success, or -1 if not a valid digit. */
+static BB_INLINE int bbGetdigit(BBChar ch, int base) {
+    int v;
+    if (ch >= '0' && ch <= '9') {
+        v = (int)(ch - '0');
+    } else if (ch >= 'A' && ch <= 'F') {
+        v = 10 + (int)(ch - 'A');
+    } else if (ch >= 'a' && ch <= 'f') {
+        v = 10 + (int)(ch - 'a');
+    } else {
+        return -1;
+    }
+    return (v < base) ? v : -1;  /* reject e.g. '2' in base 2, 'A' in base 10 */
+}
+
+BBLONGINT bbStrToLongInt(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBLONG v = bbStrToLong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0; // no digits
+    }
+    if (errno == ERANGE) {
+        return 0; // overflow/underflow
+    }
+    if (v < LONG_MIN || v > LONG_MAX) {
+        errno = ERANGE;
+        return 0; // out of int range
+    }
+    return (BBLONGINT)v;
+}
+
+BBINT bbStrToInt(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBLONG v = bbStrToLong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0; // no digits
+    }
+    if (errno == ERANGE) {
+        return 0; // overflow/underflow
+    }
+    if (v < INT_MIN) {
+        errno = ERANGE;
+        return INT_MIN; // out of int range
+    } else if (v > INT_MAX) {
+        errno = ERANGE;
+        return INT_MAX; // out of int range
+    }
+    return (int)v;
+}
+
+BBULONGINT bbStrToULongInt(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBULONG v = bbStrToULong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0;  // no digits
+    }
+    if (errno == ERANGE) {
+        return 0; // overflow/underflow
+    }
+    if (v > ULONG_MAX) {
+        errno = ERANGE;
+        return 0; // out of int range
+    }
+    return (BBULONGINT)v;
+}
+
+BBBYTE bbStrToByte(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBULONG v = bbStrToULong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0; // no digits
+    }
+    if (errno == ERANGE) {
+        return 0; // overflow/underflow
+    }
+    if (v > UCHAR_MAX) {
+        errno = ERANGE;
+        return 0; // out of int range
+    }
+    return (BBBYTE)v;
+}
+
+BBSHORT bbStrToShort(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBULONG v = bbStrToULong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0; // no digits
+    }
+    if (errno == ERANGE) {
+        return 0; // overflow/underflow
+    }
+    if (v > USHRT_MAX) {
+        errno = ERANGE;
+        return 0; // out of int range
+    }
+    return (BBSHORT)v;
+}
+
+BBUINT bbStrToUInt(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBULONG v = bbStrToULong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0; // no digits
+    }
+    if (errno == ERANGE) {
+        return 0; // overflow/underflow
+    }
+    if (v > UINT_MAX) {
+        errno = ERANGE;
+        return 0; // out of int range
+    }
+    return (BBUINT)v;
+}
+
+BBLONG bbStrToLong(const BBChar *s, int length, int *end_index) {
+    BBLONG acc, cutoff;
+    int i, any, neg, base, overflowed, digits_start, cutlim, d;
+
+    if (end_index) {
+        *end_index = 0;
+    }
+    if (!s || length <= 0) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    i = 0;
+    while (i < length && bbIsspace(s[i])) ++i;
+    if (i >= length) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    neg = 0;
+    if (s[i] == '+' || s[i] == '-') {
+        neg = (s[i] == '-');
+        ++i;
+    }
+
+    base = 10;
+    if (i < length) {
+        if (s[i] == '%') {
+            base = 2;
+            ++i;
+        } else if (s[i] == '$') {
+            base = 16;
+            ++i;
+        }
+    }
+
+    acc = 0;
+    any = 0;
+    overflowed = 0;
+
+    /* negative-accumulation guards LLONG_MIN properly */
+    cutoff = neg ? LLONG_MIN : -LLONG_MAX;     /* negative */
+    cutlim = (int)(-(cutoff % (BBLONG)base));  /* 0..base-1 */
+    cutoff /= (BBLONG)base;
+
+    digits_start = i;
+
+    for (; i < length; ++i) {
+        d = bbGetdigit(s[i], base);
+
+        if (d < 0) {
+            break;
+        }
+
+        if (!overflowed) {
+            if (acc < cutoff || (acc == cutoff && d > cutlim)) {
+                overflowed = 1;   /* keep scanning to set end_index correctly */
+            } else {
+                acc = acc * (BBLONG)base - (BBLONG)d; /* stay negative */
+            }
+        }
+        any = 1;
+    }
+
+    if (!any || i == digits_start) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    if (end_index) {
+        *end_index = i;  /* first unparsed char (one-past-last) */
+    }
+
+    if (overflowed) {
+        errno = ERANGE;
+        return neg ? LLONG_MIN : LLONG_MAX;
+    }
+
+    errno = 0;
+    return neg ? acc : -acc;
+}
+
+BBULONG bbStrToULong(const BBChar *s, int length, int *end_index) {
+    int i, neg, base, any, overflowed, digits_start, d;
+    BBULONG acc, cutoff, cutlim;
+
+    if (end_index) {
+        *end_index = 0;
+    }
+    if (!s || length <= 0) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    i = 0;
+    while (i < length && bbIsspace(s[i])) ++i;
+    if (i >= length) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    /* optional sign */
+    neg = 0;
+    if (s[i] == '+' || s[i] == '-') {
+        neg = (s[i] == '-');
+        ++i;
+    }
+
+    /* optional BlitzMax prefix after sign: % (bin) or $ (hex); default base 10 */
+    base = 10;
+    if (i < length) {
+        if (s[i] == '%') {
+            base = 2;
+            ++i;
+        } else if (s[i] == '$') {
+            base = 16;
+            ++i;
+        }
+    }
+
+    acc = 0;
+    any = 0;
+    overflowed = 0;
+
+    /* cutoff/cutlim for unsigned accumulation: acc*base + d must not exceed ULONG_MAX */
+    cutoff = ULLONG_MAX / (BBULONG)base;
+    cutlim = ULLONG_MAX % (BBULONG)base;
+
+    digits_start = i;
+
+    for (; i < length; ++i) {
+        d = bbGetdigit(s[i], base);
+        if (d < 0) {
+            break;
+        }
+
+        if (!overflowed) {
+            if (acc > cutoff || (acc == cutoff && (BBULONG)d > cutlim)) {
+                overflowed = 1;  /* keep scanning digits to place end_index correctly */
+            } else {
+                acc = acc * (BBULONG)base + (BBULONG)d;
+            }
+        }
+        any = 1;
+    }
+
+    if (!any || i == digits_start) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    if (end_index) {
+        *end_index = i;   /* first unparsed character (one-past-last) */
+    }
+
+    if (overflowed) {
+        errno = ERANGE;
+        return ULLONG_MAX;
+    }
+
+    errno = 0;
+    /* POSIX strtoul semantics: accept '-' and convert via unsigned wrap */
+    return neg ? (BBULONG)(0 - acc) : acc;
+}
+
+BBSIZET bbStrToSizet(const BBChar *s, int length, int *end_index) {
+    errno = 0;
+    int last;
+    BBULONG v = bbStrToULong(s, length, &last);
+    if (end_index) {
+        *end_index = last;
+    }
+
+    if (last <= 0) {
+        return 0;           // no digits
+    }
+    if (errno == ERANGE) {
+        return 0;           // overflow/underflow
+    }
+    if (v > SIZE_MAX) {
+        errno = ERANGE;
+        return 0;               // out of int range
+    }
+    return (BBSIZET)v;
+}
+
+BBDOUBLE bbStrToDouble(const BBChar *s, int length, int *end_index) {
+    double value = 0.0;
+    int res = bbStrToDoubleEx(s, length, &value, 0, length, 0, &bbEmptyString);
+    if (end_index) {
+        *end_index = res;
+    }
+    return value;
+}
+
+BBFLOAT bbStrToFloat(const BBChar *s, int length, int *end_index) {
+    float value = 0.0f;
+    int res = bbStrToFloatEx(s, length, &value, 0, length, 0, &bbEmptyString);
+    if (end_index) {
+        *end_index = res;
+    }
+    return value;
+}

+ 27 - 0
blitz.mod/blitz_strto.h

@@ -0,0 +1,27 @@
+
+#ifndef BLITZ_STRTO_H
+#define BLITZ_STRTO_H
+
+#include "blitz_types.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+BBINT bbStrToInt(const BBChar *s, int length, int *end_index);
+BBSHORT bbStrToShort(const BBChar *s, int length, int *end_index);
+BBBYTE bbStrToByte(const BBChar *s, int length, int *end_index);
+BBLONG bbStrToLong(const BBChar *s, int length, int *end_index);
+BBUINT bbStrToUInt(const BBChar *s, int length, int *end_index);
+BBULONG bbStrToULong(const BBChar *s, int length, int *end_index);
+BBLONGINT bbStrToLongInt(const BBChar *s, int length, int *end_index);
+BBULONGINT bbStrToULongInt(const BBChar *s, int length, int *end_index);
+BBSIZET bbStrToSizet(const BBChar *s, int length, int *end_index);
+BBFLOAT bbStrToFloat(const BBChar *s, int length, int *end_index);
+BBDOUBLE bbStrToDouble(const BBChar *s, int length, int *end_index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 584 - 0
blitz.mod/tests/test.bmx

@@ -478,3 +478,587 @@ Type TStringFromUTF8BytesTest Extends TTest
     End Method
     End Method
 	
 	
 End Type
 End Type
+
+Extern
+	Function bbStrToInt:Int(s:Short Ptr, length:Int, end_index:Int Ptr)="int bbStrToInt(const BBChar *,int,int*)"
+	Function bbStrToLong:Long(s:Short Ptr, length:Int, end_index:Int Ptr)="BBLONG bbStrToLong(const BBChar *,int,int*)"
+	Function bbStrToUInt:UInt(s:Short Ptr, length:Int, end_index:Int Ptr)="BBUINT bbStrToUInt(const BBChar *,int,int*)"
+	Function bbStrToShort:Short(s:Short Ptr, length:Int, end_index:Int Ptr)="BBSHORT bbStrToShort(const BBChar *,int,int*)"
+	Function bbStrToByte:Byte(s:Short Ptr, length:Int, end_index:Int Ptr)="BBBYTE bbStrToByte(const BBChar *,int,int*)"
+	Function bbStrToULong:ULong(s:Short Ptr, length:Int, end_index:Int Ptr)="BBULONG bbStrToULong(const BBChar *,int,int*)"
+End Extern
+
+Const INT_MAX:Int = 2147483647
+Const INT_MIN:Int = -2147483648
+
+' Set this to False if your C parser stops updating end_index on overflow (i.e., leaves it on the last valid digit).
+Const OVERFLOW_CONSUMES_ALL_DIGITS:Int = True
+
+Type TIntCase
+	Field s:String
+	Field expected:Int
+	Field consumed:Int
+	Field isOverflow:Int  ' 1 if this row expects overflow clamping
+
+	Method New(s:String, expected:Int, consumed:Int, isOverflow:Int = False)
+		self.s = s
+		self.expected = expected
+		self.consumed = consumed
+		self.isOverflow = isOverflow
+	End Method
+
+	' Adjust consumed for implementations that don't advance end_index across overflow digits.
+	Method AdjustConsumed:Int()
+		If isOverflow And Not OVERFLOW_CONSUMES_ALL_DIGITS Then Return consumed - 1
+		Return consumed
+	End Method
+
+	Method ParseWithBBInt:Int(consumed:Int Var)
+		' Prepare UTF-16 buffer
+		Local n:Size_t = s.Length + 1
+		Local buf:Short[] = New Short[n]
+		s.ToWStringBuffer(buf, n)
+		' Call the native parser
+		Local end_index:Int = -1
+		Local value:Int = bbStrToInt(buf, Int(n), VarPtr end_index)
+
+		consumed = end_index
+		Return value
+	End Method
+End Type
+
+Const LONG_MAX:Long = (Long(1) Shl 63) - 1
+Const LONG_MIN:Long = - (Long(1) Shl 63)
+
+Type TLongCase
+	Field s:String
+	Field expected:Long
+	Field consumed:Int
+	Field isOverflow:Int  ' 1 if this row expects overflow clamping
+
+	Method New(s:String, expected:Long, consumed:Int, isOverflow:Int = False)
+		Self.s = s
+		Self.expected = expected
+		Self.consumed = consumed
+		Self.isOverflow = isOverflow
+	End Method
+
+	' Adjust consumed for implementations that don't advance end_index across overflow digits.
+	Method AdjustConsumed:Int()
+		If isOverflow And Not OVERFLOW_CONSUMES_ALL_DIGITS Then Return consumed - 1
+		Return consumed
+	End Method
+
+	Method ParseWithBBLong:Long(consumed:Int Var)
+		' Prepare UTF-16 buffer (+1 for NUL)
+		Local n:Size_t = s.Length + 1
+		Local buf:Short[] = New Short[n]
+		s.ToWStringBuffer(buf, n)
+
+		' Call the native parser
+		Local end_index:Int = -1
+		Local value:Long = bbStrToLong(buf, Int(n), VarPtr end_index)
+
+		consumed = end_index
+		Return value
+	End Method
+End Type
+
+Type TUIntCase
+	Field s:String
+	Field expected:UInt
+	Field consumed:Int
+	Field isOverflow:Int  ' treat as overflow/out-of-range (end_index rules may differ)
+
+	Method New(s:String, expected:UInt, consumed:Int, isOverflow:Int = False)
+		Self.s = s
+		Self.expected = expected
+		Self.consumed = consumed
+		Self.isOverflow = isOverflow
+	End Method
+
+	' Adjust consumed if your C impl stops at last valid digit instead of scanning all digits.
+	Method AdjustConsumed:Int()
+		If isOverflow And Not OVERFLOW_CONSUMES_ALL_DIGITS Then Return consumed - 1
+		Return consumed
+	End Method
+
+	Method ParseWithBBUInt:UInt(consumed:Int Var)
+		' Prepare UTF-16 buffer (+1 for NUL)
+		Local n:Size_t = s.Length + 1
+		Local buf:Short[] = New Short[n]
+		s.ToWStringBuffer(buf, n)
+
+		' Call the native parser
+		Local end_index:Int = -1
+		Local value:UInt = bbStrToUInt(buf, Int(n), VarPtr end_index)
+
+		consumed = end_index
+		Return value
+	End Method
+End Type
+
+Type TShortCase
+	Field s:String
+	Field expected:Short
+	Field consumed:Int
+	Field isOverflow:Int
+
+	Method New(s:String, expected:Short, consumed:Int, isOverflow:Int = False)
+		Self.s = s
+		Self.expected = expected
+		Self.consumed = consumed
+		Self.isOverflow = isOverflow
+	End Method
+
+	Method AdjustConsumed:Int()
+		If isOverflow And Not OVERFLOW_CONSUMES_ALL_DIGITS Then Return consumed - 1
+		Return consumed
+	End Method
+
+	Method ParseWithBBShort:Short(consumed:Int Var)
+		Local n:Size_t = s.Length + 1
+		Local buf:Short[] = New Short[n]
+		s.ToWStringBuffer(buf, n)
+		Local end_index:Int = -1
+		Local value:Short = bbStrToShort(buf, Int(n), VarPtr end_index)
+		consumed = end_index
+		Return value
+	End Method
+End Type
+
+Type TByteCase
+	Field s:String
+	Field expected:Byte
+	Field consumed:Int
+	Field isOverflow:Int
+
+	Method New(s:String, expected:Byte, consumed:Int, isOverflow:Int = False)
+		Self.s = s
+		Self.expected = expected
+		Self.consumed = consumed
+		Self.isOverflow = isOverflow
+	End Method
+
+	Method AdjustConsumed:Int()
+		If isOverflow And Not OVERFLOW_CONSUMES_ALL_DIGITS Then Return consumed - 1
+		Return consumed
+	End Method
+
+	Method ParseWithBBByte:Byte(consumed:Int Var)
+		Local n:Size_t = s.Length + 1
+		Local buf:Short[] = New Short[n]
+		s.ToWStringBuffer(buf, n)
+		Local end_index:Int = -1
+		Local value:Byte = bbStrToByte(buf, Int(n), VarPtr end_index)
+		consumed = end_index
+		Return value
+	End Method
+End Type
+
+Const UL64_MAX:ULong = ULong(-1)  ' 0xFFFFFFFFFFFFFFFF
+
+Type TULongCase
+	Field s:String
+	Field expected:ULong
+	Field consumed:Int
+	Field isOverflow:Int
+
+	Method New(s:String, expected:ULong, consumed:Int, isOverflow:Int = False)
+		Self.s = s
+		Self.expected = expected
+		Self.consumed = consumed
+		Self.isOverflow = isOverflow
+	End Method
+
+	Method AdjustConsumed:Int()
+		If isOverflow And Not OVERFLOW_CONSUMES_ALL_DIGITS Then Return consumed - 1
+		Return consumed
+	End Method
+
+	Method ParseWithBBULong:ULong(consumed:Int Var)
+		Local n:Size_t = s.Length + 1
+		Local buf:Short[] = New Short[n]
+		s.ToWStringBuffer(buf, n)
+		Local end_index:Int = -1
+		Local value:ULong = bbStrToULong(buf, Int(n), VarPtr end_index)
+		consumed = end_index
+		Return value
+	End Method
+End Type
+
+Function RepeatChar:String(ch:String, count:Int)
+	Local s:String = ""
+	For Local i:Int = 0 Until count
+		s :+ ch
+	Next
+	Return s
+End Function
+
+Type TStringToNumStrToNumTest Extends TTest
+
+		' Safer equality for ULong without relying on string/overloads.
+	Function AssertULongEquals(expected:ULong, actual:ULong, msg:String)
+		assertEquals(Int(expected Shr 32), Int(actual Shr 32), msg + " (hi)")
+		assertEquals(Int(expected & $FFFFFFFF), Int(actual & $FFFFFFFF), msg + " (lo)")
+	End Function
+
+	Method test_ToInt_All() { test }
+		Local cases:TIntCase[] = [ ..
+			..' --- Decimal (base 10)
+			New TIntCase("0",                      0,                  1),
+			New TIntCase("123",                    123,                3),
+			New TIntCase("-123",                   -123,               4),
+			New TIntCase("   +42",                 42,                 6),
+			New TIntCase("   -0",                  0,                  5),
+			New TIntCase("00123",                  123,                5),
+			New TIntCase("123abc",                 123,                3),
+			New TIntCase("123   ",                 123,                3),
+
+			' Exact limits
+			New TIntCase("2147483647",             INT_MAX,            10),
+			New TIntCase("-2147483648",            INT_MIN,            11),
+
+			' Overflows (clamp; mark with isOverflow=1)
+			New TIntCase("2147483648",             INT_MAX,            10, True),
+			New TIntCase("-2147483649",            INT_MIN,            11, True),
+
+			' Errors (no digits)
+			New TIntCase("",                       0,                  0),
+			New TIntCase("   ",                    0,                  0),
+			New TIntCase("+",                      0,                  0),
+			New TIntCase("-",                      0,                  0),
+			New TIntCase("abc",                    0,                  0),
+
+			' --- Hex with $ (base 16)
+			New TIntCase("$7FFFFFFF",              INT_MAX,            9),
+			New TIntCase("-$80000000",             INT_MIN,            10),
+			New TIntCase("$80000000",              INT_MAX,            9,  True),     ' +2^31 overflows
+			New TIntCase("-$80000001",             INT_MIN,            10, True),     ' -(2^31+1) overflows
+			New TIntCase("$7fffffff",              INT_MAX,            9),            ' lowercase
+			New TIntCase("   $1a",                 26,                 6),
+			New TIntCase("$",                      0,                  0),            ' no digits after prefix
+			New TIntCase("$G1",                    0,                  0),            ' first is invalid -> no digits
+			New TIntCase("$FFxyz",                 255,                3),            ' stops at first invalid
+
+			' --- Binary with % (base 2)
+			New TIntCase("%0",                     0,                  2),
+			New TIntCase("%101010",                42,                 7),
+			New TIntCase("-%10000000000000000000000000000000", INT_MIN, 34),          ' -2^31
+			New TIntCase("%10000000000000000000000000000000",  INT_MAX, 33, True),    ' +2^31 overflows
+			New TIntCase("%1002",                  4,                  4),            ' stops at '2'
+			New TIntCase("%",                      0,                  0),            ' no digits after prefix
+			New TIntCase("   %101",                5,                  7),
+
+			' --- With +sign and prefixes
+			New TIntCase("+$7fffffff",             INT_MAX,            10),
+			New TIntCase("+%101",                  5,                  5),
+
+			' --- Trailing junk after signed number
+			New TIntCase("-42xyz",                 -42,                3)..
+		]
+
+		For Local t:TIntCase = EachIn cases
+			Local gotConsumed:Int
+			Local gotValue:Int = t.ParseWithBBInt(gotConsumed)
+			Local expectConsumed:Int = t.AdjustConsumed()
+
+			assertEquals(expectConsumed, gotConsumed, "consumed mismatch for '"+t.s+"'")
+			assertEquals(t.expected, gotValue, "value mismatch for '"+t.s+"'")
+		Next
+	End Method
+
+	Method test_ToLong_All() { test }
+		' Build the 64-bit binary limit strings:
+		' -%1 + 63 zeros  (=-2^63) and  %1 + 63 zeros (=+2^63, overflow)
+		Local z63:String = "0000000000000000000000000000000" + "00000000000000000000000000000000" ' 31 + 32 = 63 zeros
+		Local BIN64_MIN:String = "-%1" + z63
+		Local BIN64_POS_OVF:String = "%1" + z63
+
+		Local cases:TLongCase[] = [..
+			..' --- Decimal (base 10)
+			New TLongCase("0",                        0,                    1),
+			New TLongCase("123",                      123,                  3),
+			New TLongCase("-123",                     -123,                 4),
+			New TLongCase("   +42",                   42,                   6),
+			New TLongCase("   -0",                    0,                    5),
+			New TLongCase("00123",                    123,                  5),
+			New TLongCase("123abc",                   123,                  3),
+			New TLongCase("123   ",                   123,                  3),
+
+			' Exact 64-bit limits
+			New TLongCase("9223372036854775807",      LONG_MAX,             19),
+			New TLongCase("-9223372036854775808",     LONG_MIN,             20),
+
+			' Overflows (clamp; mark with isOverflow=1)
+			New TLongCase("9223372036854775808",      LONG_MAX,             19, True),
+			New TLongCase("-9223372036854775809",     LONG_MIN,             20, True),
+
+			' Errors (no digits)
+			New TLongCase("",                         0,                    0),
+			New TLongCase("   ",                      0,                    0),
+			New TLongCase("+",                        0,                    0),
+			New TLongCase("-",                        0,                    0),
+			New TLongCase("abc",                      0,                    0),
+
+			' --- Hex with $ (base 16)
+			New TLongCase("$7FFFFFFFFFFFFFFF",        LONG_MAX,             17),
+			New TLongCase("-$8000000000000000",       LONG_MIN,             18),
+			New TLongCase("$8000000000000000",        LONG_MAX,             17, True), ' +2^63 overflows
+			New TLongCase("-$8000000000000001",       LONG_MIN,             18, True), ' -(2^63+1) overflows
+			New TLongCase("$7fffffffffffffff",        LONG_MAX,             17),       ' lowercase
+			New TLongCase("   $1a",                   26,                   6),
+			New TLongCase("$",                        0,                    0),        ' no digits after prefix
+			New TLongCase("$G1",                      0,                    0),        ' first is invalid -> no digits
+			New TLongCase("$FFxyz",                   255,                  3),        ' stops at first invalid
+
+			' --- Binary with % (base 2)
+			New TLongCase("%0",                       0,                    2),
+			New TLongCase("%101010",                  42,                   7),
+			New TLongCase(BIN64_MIN,                  LONG_MIN,             66),       ' -2^63
+			New TLongCase(BIN64_POS_OVF,              LONG_MAX,             65, True), ' +2^63 overflows
+			New TLongCase("%1002",                    4,                    4),        ' stops at '2'
+			New TLongCase("%",                        0,                    0),        ' no digits after prefix
+			New TLongCase("   %101",                  5,                    7),
+
+			' --- With +sign and prefixes
+			New TLongCase("+$7fffffffffffffff",       LONG_MAX,             18),
+			New TLongCase("+%101",                    5,                    5),
+
+			' --- Trailing junk after signed number
+			New TLongCase("-42xyz",                   -42,                  3)..
+		]
+
+		For Local t:TLongCase = EachIn cases
+			Local gotConsumed:Int
+			Local gotValue:Long = t.ParseWithBBLong(gotConsumed)
+			Local expectConsumed:Int = t.AdjustConsumed()
+
+			assertEquals(expectConsumed, gotConsumed, "consumed mismatch for '"+t.s+"'")
+			assertEquals(t.expected, gotValue, "value mismatch for '"+t.s+"'")
+		Next
+	End Method
+
+	Method test_ToUInt_All() { test }
+		' Prebuilt strings for 32-bit edges
+		Local BIN32_MAX:String = "%11111111111111111111111111111111" ' 32 ones
+		Local zeros32:String = "00000000000000000000000000000000"  ' 32 zeros
+		Local BIN32_POS_OVF:String = "%1" + zeros32                   ' 2^32 -> overflow
+
+		Local cases:TUIntCase[] = [..
+			..' --- Decimal (base 10)
+			New TUIntCase("0",                       UInt(0),                 1),
+			New TUIntCase("123",                     UInt(123),               3),
+			New TUIntCase("+123",                    UInt(123),               4),
+			New TUIntCase("   +42",                  UInt(42),                6),
+			New TUIntCase("   -0",                   UInt(0),                 5),     ' -0 is still 0
+			New TUIntCase("00123",                   UInt(123),               5),
+			New TUIntCase("123abc",                  UInt(123),               3),
+			New TUIntCase("123   ",                  UInt(123),               3),
+
+			' Range edges
+			New TUIntCase("2147483647",              UInt($7FFFFFFF),         10),    ' INT_MAX fits in UInt
+			New TUIntCase("2147483648",              UInt($80000000),         10),    ' 2^31 fits
+			New TUIntCase("4294967295",              UInt($FFFFFFFF),         10),    ' UINT_MAX
+			New TUIntCase("4294967296",              UInt(0),                 10, True), ' overflow -> 0
+
+			' Negatives are out-of-range (wrapper returns 0) but digits are consumed
+			New TUIntCase("-1",                      UInt(0),                 2,  True),
+			New TUIntCase("-",                       UInt(0),                 0),
+			New TUIntCase("+",                       UInt(0),                 0),
+
+			' Errors (no digits)
+			New TUIntCase("",                        UInt(0),                 0),
+			New TUIntCase("   ",                     UInt(0),                 0),
+			New TUIntCase("abc",                     UInt(0),                 0),
+
+			' --- Hex with $ (base 16)
+			New TUIntCase("$0",                      UInt(0),                 2),
+			New TUIntCase("$FFFFFFFF",               UInt($FFFFFFFF),         9),
+			New TUIntCase("$7FFFFFFF",               UInt($7FFFFFFF),         9),
+			New TUIntCase("$80000000",               UInt($80000000),         9),
+			New TUIntCase("$100000000",              UInt(0),                 10, True),   ' 0x1_0000_0000 overflow
+			New TUIntCase("-$1",                     UInt(0),                 3,  True),   ' negative -> out-of-range
+			New TUIntCase("$7fffffff",               UInt($7FFFFFFF),         9),         ' lowercase
+			New TUIntCase("   $1a",                  UInt(26),                6),
+			New TUIntCase("$",                       UInt(0),                 0),         ' no digits after prefix
+			New TUIntCase("$G1",                     UInt(0),                 0),         ' first is invalid -> no digits
+			New TUIntCase("$FFxyz",                  UInt(255),               3),         ' stops at first invalid
+			New TUIntCase("+$FFFFFFFF",              UInt($FFFFFFFF),         10),
+
+			' --- Binary with % (base 2)
+			New TUIntCase("%0",                      UInt(0),                 2),
+			New TUIntCase("%101010",                 UInt(42),                7),
+			New TUIntCase(BIN32_MAX,                 UInt($FFFFFFFF),         33),       ' 32 ones
+			New TUIntCase("%10000000000000000000000000000000", UInt($80000000), 33),     ' 2^31
+			New TUIntCase(BIN32_POS_OVF,             UInt(0),                 34, True), ' 2^32 -> overflow
+			New TUIntCase("%1002",                   UInt(4),                 4),        ' stops at '2'
+			New TUIntCase("%",                       UInt(0),                 0),        ' no digits after prefix
+			New TUIntCase("   %101",                 UInt(5),                 7),
+			New TUIntCase("+%101",                   UInt(5),                 5),
+			New TUIntCase("-%1",                     UInt(0),                 3,  True)..  ' negative -> out-of-range
+		]
+
+		For Local t:TUIntCase = EachIn cases
+			Local gotConsumed:Int
+			Local gotValue:UInt = t.ParseWithBBUInt(gotConsumed)
+			Local expectConsumed:Int = t.AdjustConsumed()
+
+			assertEquals(expectConsumed, gotConsumed, "consumed mismatch for '"+t.s+"'")
+			' Cast to Long in assertion to avoid any UInt overload ambiguity
+			assertEquals(Long(t.expected), Long(gotValue), "value mismatch for '"+t.s+"'")
+		Next
+	End Method
+
+	Method test_ToShort_All() { test }
+		Local BIN16_MAX:String = "%" + RepeatChar("1", 16)
+		Local BIN16_POS_OVF:String = "%1" + RepeatChar("0", 16)  ' 2^16
+
+		Local cases:TShortCase[] = [..
+			..' Decimal
+			New TShortCase("0",           Short(0),       1),
+			New TShortCase("65535",       Short(65535),   5),
+			New TShortCase("65536",       Short(0),       5, True),
+			New TShortCase("123abc",      Short(123),     3),
+			New TShortCase("   +42",      Short(42),      6),
+			New TShortCase("-1",          Short(0),       2, True),
+
+			' Errors
+			New TShortCase("",            Short(0),       0),
+			New TShortCase("   ",         Short(0),       0),
+			New TShortCase("+",           Short(0),       0),
+			New TShortCase("-",           Short(0),       0),
+			New TShortCase("abc",         Short(0),       0),
+
+			' Hex ($)
+			New TShortCase("$0",          Short(0),       2),
+			New TShortCase("$FFFF",       Short(65535),   5),
+			New TShortCase("$10000",      Short(0),       6, True),
+			New TShortCase("$fffe",       Short(65534),   5),
+			New TShortCase("+$00FF",      Short(255),     6),
+			New TShortCase("-$1",         Short(0),       3, True),
+			New TShortCase("$",           Short(0),       0),
+			New TShortCase("$G1",         Short(0),       0),
+			New TShortCase("$FFxyz",      Short(255),     3),
+
+			' Binary (%)
+			New TShortCase("%0",          Short(0),       2),
+			New TShortCase("%1111111111111111", Short(65535), 17),
+			New TShortCase(BIN16_POS_OVF, Short(0),       18, True),
+			New TShortCase("%1002",       Short(4),       4),
+			New TShortCase("%",           Short(0),       0),
+			New TShortCase("   %101",     Short(5),       7)..
+		]
+
+		For Local t:TShortCase = EachIn cases
+			Local gotConsumed:Int
+			Local gotValue:Short = t.ParseWithBBShort(gotConsumed)
+			assertEquals(t.AdjustConsumed(), gotConsumed, "consumed mismatch for '"+t.s+"'")
+			assertEquals(Int(t.expected), Int(gotValue), "value mismatch for '"+t.s+"'")
+		Next
+	End Method
+
+	Method test_ToByte_All() { test }
+		Local BIN8_MAX:String = "%" + RepeatChar("1", 8)
+		Local BIN8_POS_OVF:String = "%1" + RepeatChar("0", 8)   ' 2^8
+
+		Local cases:TByteCase[] = [..
+			..' Decimal
+			New TByteCase("0",        Byte(0),     1),
+			New TByteCase("255",      Byte(255),   3),
+			New TByteCase("256",      Byte(0),     3, True),
+			New TByteCase("123abc",   Byte(123),   3),
+			New TByteCase("   +42",   Byte(42),    6),
+			New TByteCase("-1",       Byte(0),     2, True),
+
+			' Errors
+			New TByteCase("",         Byte(0),     0),
+			New TByteCase("   ",      Byte(0),     0),
+			New TByteCase("+",        Byte(0),     0),
+			New TByteCase("-",        Byte(0),     0),
+			New TByteCase("abc",      Byte(0),     0),
+
+			' Hex ($)
+			New TByteCase("$0",       Byte(0),     2),
+			New TByteCase("$FF",      Byte(255),   3),
+			New TByteCase("$100",     Byte(0),     4, True),
+			New TByteCase("$7f",      Byte(127),   3),
+			New TByteCase("+$0A",     Byte(10),    4),
+			New TByteCase("-$1",      Byte(0),     3, True),
+			New TByteCase("$",        Byte(0),     0),
+			New TByteCase("$G1",      Byte(0),     0),
+			New TByteCase("$FFxyz",   Byte(255),   3),
+
+			' Binary (%)
+			New TByteCase("%0",       Byte(0),     2),
+			New TByteCase(BIN8_MAX,   Byte(255),   9),
+			New TByteCase(BIN8_POS_OVF, Byte(0),   10, True),
+			New TByteCase("%1002",    Byte(4),     4),
+			New TByteCase("%",        Byte(0),     0),
+			New TByteCase("   %1010", Byte(10),    8),
+			New TByteCase("+%1010",   Byte(10),    6)..
+		]
+
+		For Local t:TByteCase = EachIn cases
+			Local gotConsumed:Int
+			Local gotValue:Byte = t.ParseWithBBByte(gotConsumed)
+			assertEquals(t.AdjustConsumed(), gotConsumed, "consumed mismatch for '"+t.s+"'")
+			assertEquals(Int(t.expected), Int(gotValue), "value mismatch for '"+t.s+"'")
+		Next
+	End Method
+
+		Method test_ToULong_All() { test }
+		Local decMax:String = "18446744073709551615"      ' 2^64 - 1
+		Local decOvf:String = "18446744073709551616"      ' 2^64
+
+		Local BIN64_MAX:String = "%" + RepeatChar("1", 64)
+		Local BIN64_POS_OVF:String = "%1" + RepeatChar("0", 64)
+
+		Local cases:TULongCase[] = [..
+			..' Decimal
+			New TULongCase("0",                 ULong(0),         1),
+			New TULongCase("123",               ULong(123),       3),
+			New TULongCase("   +42",            ULong(42),        6),
+			New TULongCase(decMax,              UL64_MAX,        20),
+			New TULongCase(decOvf,              UL64_MAX,        20, True),  ' clamp on overflow
+
+			' Accept leading '-' with wrap (POSIX strtoul style)
+			New TULongCase("-1",                UL64_MAX,         2),
+			New TULongCase("-0",                ULong(0),         2),
+
+			' Errors
+			New TULongCase("",                  ULong(0),         0),
+			New TULongCase("   ",               ULong(0),         0),
+			New TULongCase("+",                 ULong(0),         0),
+			New TULongCase("-",                 ULong(0),         0),
+			New TULongCase("abc",               ULong(0),         0),
+
+			' Hex ($)
+			New TULongCase("$0",                ULong(0),         2),
+			New TULongCase("$FFFFFFFFFFFFFFFF", UL64_MAX,        17),
+			New TULongCase("$10000000000000000", UL64_MAX,       18, True),  ' overflow
+			New TULongCase("-$1",               UL64_MAX,         3),        ' wrap
+			New TULongCase("$FFxyz",            ULong(255),       3),
+			New TULongCase("$",                 ULong(0),         0),
+			New TULongCase("$G1",               ULong(0),         0),
+			New TULongCase("+$FFFFFFFFFFFFFFFF", UL64_MAX,       18),
+
+			' Binary (%)
+			New TULongCase("%0",                ULong(0),         2),
+			New TULongCase(BIN64_MAX,           UL64_MAX,        65),
+			New TULongCase(BIN64_POS_OVF,       UL64_MAX,        66, True),
+			New TULongCase("-%1",               UL64_MAX,         3),        ' wrap
+			New TULongCase("%1002",             ULong(4),         4),
+			New TULongCase("%",                 ULong(0),         0),
+			New TULongCase("   %101",           ULong(5),         7)..
+		]
+
+		For Local t:TULongCase = EachIn cases
+			Local gotConsumed:Int
+			Local gotValue:ULong = t.ParseWithBBULong(gotConsumed)
+			assertEquals(t.AdjustConsumed(), gotConsumed, "consumed mismatch for '"+t.s+"'")
+			AssertULongEquals(t.expected, gotValue, "value mismatch for '"+t.s+"'")
+		Next
+	End Method
+
+End Type

+ 12 - 1
stringbuilder.mod/common.bmx

@@ -1,4 +1,4 @@
-' Copyright (c) 2018-2024 Bruce A Henderson
+' Copyright (c) 2018-2025 Bruce A Henderson
 ' 
 ' 
 ' This software is provided 'as-is', without any express or implied
 ' This software is provided 'as-is', without any express or implied
 ' warranty. In no event will the authors be held liable for any damages
 ' warranty. In no event will the authors be held liable for any damages
@@ -84,6 +84,17 @@ Extern
 	Function bmx_stringbuilder_hash:ULong(buffer:Byte Ptr)
 	Function bmx_stringbuilder_hash:ULong(buffer:Byte Ptr)
 	Function bmx_stringbuilder_append_utf32string(buffer:Byte Ptr, chars:UInt Ptr)
 	Function bmx_stringbuilder_append_utf32string(buffer:Byte Ptr, chars:UInt Ptr)
 	Function bmx_stringbuilder_append_utf32bytes(buffer:Byte Ptr, chars:UInt Ptr, length:Int)
 	Function bmx_stringbuilder_append_utf32bytes(buffer:Byte Ptr, chars:UInt Ptr, length:Int)
+	Function bmx_stringbuilder_toint:Int(buffer:Byte Ptr)
+	Function bmx_stringbuilder_tolong:Long(buffer:Byte Ptr)
+	Function bmx_stringbuilder_touint:UInt(buffer:Byte Ptr)
+	Function bmx_stringbuilder_toshort:Short(buffer:Byte Ptr)
+	Function bmx_stringbuilder_tobyte:Byte(buffer:Byte Ptr)
+	Function bmx_stringbuilder_toulong:ULong(buffer:Byte Ptr)
+	Function bmx_stringbuilder_tolongint:LongInt(buffer:Byte Ptr)
+	Function bmx_stringbuilder_toulongint:ULongInt(buffer:Byte Ptr)
+	Function bmx_stringbuilder_tosizet:Size_T(buffer:Byte Ptr)
+	Function bmx_stringbuilder_tofloat:Float(buffer:Byte Ptr)
+	Function bmx_stringbuilder_todouble:Double(buffer:Byte Ptr)
 
 
 	Function bmx_stringbuilder_splitbuffer_length:Int(splitPtr:Byte Ptr)
 	Function bmx_stringbuilder_splitbuffer_length:Int(splitPtr:Byte Ptr)
 	Function bmx_stringbuilder_splitbuffer_text:String(splitPtr:Byte Ptr, index:Int)
 	Function bmx_stringbuilder_splitbuffer_text:String(splitPtr:Byte Ptr, index:Int)

+ 98 - 114
stringbuilder.mod/glue.c

@@ -1,5 +1,5 @@
 /*
 /*
-  Copyright (c) 2018-2024 Bruce A Henderson
+  Copyright (c) 2018-2025 Bruce A Henderson
   
   
   This software is provided 'as-is', without any express or implied
   This software is provided 'as-is', without any express or implied
   warranty. In no event will the authors be held liable for any damages
   warranty. In no event will the authors be held liable for any damages
@@ -990,6 +990,94 @@ void bmx_stringbuilder_append_utf32bytes(struct MaxStringBuilder * buf, BBUINT *
 	buf->hash = 0;
 	buf->hash = 0;
 }
 }
 
 
+int bmx_stringbuilder_toint(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToInt(buf->buffer, buf->count, NULL);
+}
+
+BBLONG bmx_stringbuilder_tolong(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToLong(buf->buffer, buf->count, NULL);
+}
+
+BBUINT bmx_stringbuilder_touint(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToUInt(buf->buffer, buf->count, NULL);
+}
+
+BBSHORT bmx_stringbuilder_toshort(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToShort(buf->buffer, buf->count, NULL);
+}
+
+BBBYTE bmx_stringbuilder_tobyte(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToByte(buf->buffer, buf->count, NULL);
+}
+
+BBULONG bmx_stringbuilder_toulong(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToULong(buf->buffer, buf->count, NULL);
+}
+
+BBLONGINT bmx_stringbuilder_tolongint(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToLongInt(buf->buffer, buf->count, NULL);
+}
+
+BBULONGINT bmx_stringbuilder_toulongint(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToULongInt(buf->buffer, buf->count, NULL);
+}
+
+BBSIZET bmx_stringbuilder_tosizet(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToSizet(buf->buffer, buf->count, NULL);
+}
+
+BBFLOAT bmx_stringbuilder_tofloat(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToFloat(buf->buffer, buf->count, NULL);
+}
+
+BBDOUBLE bmx_stringbuilder_todouble(struct MaxStringBuilder * buf) {
+	if (buf->count == 0) {
+		return 0;
+	}
+
+	return bbStrToDouble(buf->buffer, buf->count, NULL);
+}
+
 /* ----------------------------------------------------- */
 /* ----------------------------------------------------- */
 
 
 int bmx_stringbuilder_splitbuffer_length(struct MaxSplitBuffer * buf) {
 int bmx_stringbuilder_splitbuffer_length(struct MaxSplitBuffer * buf) {
@@ -1101,23 +1189,7 @@ int bmx_stringbuilder_splitbuffer_toint(struct MaxSplitBuffer * splitBuffer, int
 
 
     BBChar *segment = (BBChar *)&(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *)&(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    long result = strtol(numbuf, &endPtr, 10);
-
-    // Make sure that endPtr does not exceed the bounds of the segment
-    if (endPtr > numbuf + length) {
-        return 0;
-    }
-
-    if (errno == ERANGE || result > INT_MAX || result < INT_MIN || endPtr == numbuf) {
-        return 0;
-    }
-
-    return (int)result;
+	return bbStrToInt(segment, length, NULL);
 }
 }
 
 
 unsigned int bmx_stringbuilder_splitbuffer_touint(struct MaxSplitBuffer * splitBuffer, int index) {
 unsigned int bmx_stringbuilder_splitbuffer_touint(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1135,18 +1207,7 @@ unsigned int bmx_stringbuilder_splitbuffer_touint(struct MaxSplitBuffer * splitB
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    unsigned long result = strtoul(numbuf, &endPtr, 10);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf || result > UINT_MAX) {
-        return 0;
-    }
-
-    return (unsigned int)result;
+	return bbStrToUInt(segment, length, NULL);
 }
 }
 
 
 float bmx_stringbuilder_splitbuffer_tofloat(struct MaxSplitBuffer * splitBuffer, int index) {
 float bmx_stringbuilder_splitbuffer_tofloat(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1164,18 +1225,7 @@ float bmx_stringbuilder_splitbuffer_tofloat(struct MaxSplitBuffer * splitBuffer,
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    float result = strtof(numbuf, &endPtr);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf) {
-        return 0.0f;
-    }
-
-    return result;
+	return bbStrToFloat(segment, length, NULL);
 }
 }
 
 
 double bmx_stringbuilder_splitbuffer_todouble(struct MaxSplitBuffer * splitBuffer, int index) {
 double bmx_stringbuilder_splitbuffer_todouble(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1192,19 +1242,8 @@ double bmx_stringbuilder_splitbuffer_todouble(struct MaxSplitBuffer * splitBuffe
     }
     }
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
-	
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    double result = strtod(numbuf, &endPtr);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf) {
-        return 0.0;
-    }
 
 
-    return result;
+	return bbStrToDouble(segment, length, NULL);
 }
 }
 
 
 BBInt64 bmx_stringbuilder_splitbuffer_tolong(struct MaxSplitBuffer * splitBuffer, int index) {
 BBInt64 bmx_stringbuilder_splitbuffer_tolong(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1222,18 +1261,7 @@ BBInt64 bmx_stringbuilder_splitbuffer_tolong(struct MaxSplitBuffer * splitBuffer
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    BBInt64 result = strtoll(numbuf, &endPtr, 10);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf) {
-        return 0;
-    }
-
-    return result;
+	return bbStrToLong(segment, length, NULL);
 }
 }
 
 
 BBUInt64 bmx_stringbuilder_splitbuffer_toulong(struct MaxSplitBuffer * splitBuffer, int index) {
 BBUInt64 bmx_stringbuilder_splitbuffer_toulong(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1251,18 +1279,7 @@ BBUInt64 bmx_stringbuilder_splitbuffer_toulong(struct MaxSplitBuffer * splitBuff
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    BBUInt64 result = strtoull(numbuf, &endPtr, 10);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf) {
-        return 0;
-    }
-
-    return result;
+	return bbStrToULong(segment, length, NULL);
 }
 }
 
 
 size_t bmx_stringbuilder_splitbuffer_tosizet(struct MaxSplitBuffer * splitBuffer, int index) {
 size_t bmx_stringbuilder_splitbuffer_tosizet(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1280,18 +1297,7 @@ size_t bmx_stringbuilder_splitbuffer_tosizet(struct MaxSplitBuffer * splitBuffer
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    unsigned long long result = strtoull(numbuf, &endPtr, 10);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf) {
-        return 0;
-    }
-
-    return (size_t)result;
+	return bbStrToSizet(segment, length, NULL);
 }
 }
 
 
 BBSHORT bmx_stringbuilder_splitbuffer_toshort(struct MaxSplitBuffer * splitBuffer, int index) {
 BBSHORT bmx_stringbuilder_splitbuffer_toshort(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1309,18 +1315,7 @@ BBSHORT bmx_stringbuilder_splitbuffer_toshort(struct MaxSplitBuffer * splitBuffe
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    long result = strtol(numbuf, &endPtr, 10);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf || result < 0 || result > USHRT_MAX) {
-        return 0;
-    }
-
-    return (BBSHORT)result;
+	return bbStrToShort(segment, length, NULL);
 }
 }
 
 
 BBBYTE bmx_stringbuilder_splitbuffer_tobyte(struct MaxSplitBuffer * splitBuffer, int index) {
 BBBYTE bmx_stringbuilder_splitbuffer_tobyte(struct MaxSplitBuffer * splitBuffer, int index) {
@@ -1338,16 +1333,5 @@ BBBYTE bmx_stringbuilder_splitbuffer_tobyte(struct MaxSplitBuffer * splitBuffer,
 
 
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
     BBChar *segment = (BBChar *) &(splitBuffer->buffer->buffer[start]);
 
 
-	char numbuf[256];
-	bmx_stringbuilder_toutf8_sbuffer(segment, length, numbuf, sizeof(numbuf));
-
-    char *endPtr;
-    errno = 0;
-    long result = strtol(numbuf, &endPtr, 10);
-
-    if (endPtr > numbuf + length || errno == ERANGE || endPtr == numbuf || result < 0 || result > UCHAR_MAX) {
-        return 0;
-    }
-
-    return (BBBYTE)result;
+	return bbStrToByte(segment, length, NULL);
 }
 }

+ 12 - 1
stringbuilder.mod/glue.h

@@ -1,5 +1,5 @@
 /*
 /*
-  Copyright (c) 2018-2024 Bruce A Henderson
+  Copyright (c) 2018-2025 Bruce A Henderson
   
   
   This software is provided 'as-is', without any express or implied
   This software is provided 'as-is', without any express or implied
   warranty. In no event will the authors be held liable for any damages
   warranty. In no event will the authors be held liable for any damages
@@ -103,6 +103,17 @@ void bmx_stringbuilder_format_double(struct MaxStringBuilder * buf, BBString * f
 BBULONG bmx_stringbuilder_hash(struct MaxStringBuilder * buf);
 BBULONG bmx_stringbuilder_hash(struct MaxStringBuilder * buf);
 void bmx_stringbuilder_append_utf32string(struct MaxStringBuilder * buf, BBUINT * chars);
 void bmx_stringbuilder_append_utf32string(struct MaxStringBuilder * buf, BBUINT * chars);
 void bmx_stringbuilder_append_utf32bytes(struct MaxStringBuilder * buf, BBUINT * chars, int length);
 void bmx_stringbuilder_append_utf32bytes(struct MaxStringBuilder * buf, BBUINT * chars, int length);
+int bmx_stringbuilder_toint(struct MaxStringBuilder * buf);
+BBLONG bmx_stringbuilder_tolong(struct MaxStringBuilder * buf);
+BBUINT bmx_stringbuilder_touint(struct MaxStringBuilder * buf);
+BBSHORT bmx_stringbuilder_toshort(struct MaxStringBuilder * buf);
+BBBYTE bmx_stringbuilder_tobyte(struct MaxStringBuilder * buf);
+BBULONG bmx_stringbuilder_toulong(struct MaxStringBuilder * buf);
+BBLONGINT bmx_stringbuilder_tolongint(struct MaxStringBuilder * buf);
+BBULONGINT bmx_stringbuilder_toulongint(struct MaxStringBuilder * buf);
+BBSIZET bmx_stringbuilder_tosizet(struct MaxStringBuilder * buf);
+BBFLOAT bmx_stringbuilder_tofloat(struct MaxStringBuilder * buf);
+BBDOUBLE bmx_stringbuilder_todouble(struct MaxStringBuilder * buf);
 
 
 /* ----------------------------------------------------- */
 /* ----------------------------------------------------- */
 
 

+ 102 - 3
stringbuilder.mod/stringbuilder.bmx

@@ -1,4 +1,4 @@
-' Copyright (c) 2018-2024 Bruce A Henderson
+' Copyright (c) 2018-2025 Bruce A Henderson
 ' 
 ' 
 ' This software is provided 'as-is', without any express or implied
 ' This software is provided 'as-is', without any express or implied
 ' warranty. In no event will the authors be held liable for any damages
 ' warranty. In no event will the authors be held liable for any damages
@@ -23,10 +23,12 @@ bbdoc: A string builder.
 End Rem	
 End Rem	
 Module BRL.StringBuilder
 Module BRL.StringBuilder
 
 
-ModuleInfo "Version: 1.19"
+ModuleInfo "Version: 1.20"
 ModuleInfo "License: zlib/libpng"
 ModuleInfo "License: zlib/libpng"
-ModuleInfo "Copyright: 2018-2024 Bruce A Henderson"
+ModuleInfo "Copyright: 2018-2025 Bruce A Henderson"
 
 
+ModuleInfo "History: 1.20"
+ModuleInfo "History: Added ToNumber methods."
 ModuleInfo "History: 1.19"
 ModuleInfo "History: 1.19"
 ModuleInfo "History: Added TSplitBuffer Split() method."
 ModuleInfo "History: Added TSplitBuffer Split() method."
 ModuleInfo "History: Added TSplitBuffer to number methods."
 ModuleInfo "History: Added TSplitBuffer to number methods."
@@ -935,6 +937,103 @@ Public
 		Return Not bmx_stringbuilder_equals(buffer, sb.buffer)
 		Return Not bmx_stringbuilder_equals(buffer, sb.buffer)
 	End Method
 	End Method
 
 
+	Rem
+	bbdoc: Returns the string builder as an #Int.
+	about: If the string builder is not a valid #Int, 0 is returned.
+	If the calculated value is out of range for an #Int, either int max or int min is returned.
+	End Rem
+	Method ToInt:Int()
+		Return bmx_stringbuilder_toint(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #Long.
+	about: If the string builder is not a valid #Long, 0 is returned.
+	If the calculated value is out of range for a #Long, either long max or long min is returned.
+	End Rem
+	Method ToLong:Long()
+		Return bmx_stringbuilder_tolong(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #Short.
+	about: If the string builder is not a valid #Short, 0 is returned.
+	If the calculated value is out of range for a #Short, short max is returned.
+	End Rem
+	Method ToShort:Short()
+		Return bmx_stringbuilder_toshort(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #Byte.
+	about: If the string builder is not a valid #Byte, 0 is returned.
+	If the calculated value is out of range for a #Byte, byte max is returned.
+	End Rem
+	Method ToByte:Byte()
+		Return bmx_stringbuilder_tobyte(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #UInt.
+	about: If the string builder is not a valid #UInt, 0 is returned.
+	If the calculated value is out of range for a #UInt, uint max is returned.
+	End Rem
+	Method ToUInt:UInt()
+		Return bmx_stringbuilder_touint(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #ULong.
+	about: If the string builder is not a valid #ULong, 0 is returned.
+	If the calculated value is out of range for a #ULong, ulong max is returned.
+	End Rem
+	Method ToULong:ULong()
+		Return bmx_stringbuilder_toulong(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #Size_T.
+	about: If the string builder is not a valid #Size_T, 0 is returned.
+	If the calculated value is out of range for a #Size_T, Size_T max is returned.
+	End Rem
+	Method ToSizeT:Size_T()
+		Return bmx_stringbuilder_tosizet(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #LongInt.
+	about: If the string builder is not a valid #LongInt, 0 is returned.
+	If the calculated value is out of range for a #LongInt, either long int max or long int min is returned.
+	End Rem
+	Method ToLongInt:LongInt()
+		Return bmx_stringbuilder_tolongint(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #ULongInt.
+	about: If the string builder is not a valid #ULongInt, 0 is returned.
+	If the calculated value is out of range for a #ULongInt, either ulong int max or ulong int min is returned.
+	End Rem
+	Method ToULongInt:ULongInt()
+		Return bmx_stringbuilder_toulongint(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #Float.
+	about: If the string builder is not a valid #Float, 0.0 is returned.
+	End Rem
+	Method ToFloat:Float()
+		Return bmx_stringbuilder_tofloat(buffer)
+	End Method
+
+	Rem
+	bbdoc: Returns the string builder as a #Double.
+	about: If the string builder is not a valid #Double, 0.0 is returned.
+	End Rem
+	Method ToDouble:Double()
+		Return bmx_stringbuilder_todouble(buffer)
+	End Method
+
 	Method Delete()
 	Method Delete()
 		If buffer Then
 		If buffer Then
 			bmx_stringbuilder_free(buffer)
 			bmx_stringbuilder_free(buffer)