From d55fd895c9b22b9d6304d5778efb0aecc9057cf3 Mon Sep 17 00:00:00 2001
From: koniifer <koniifer@proton.me>
Date: Tue, 17 Dec 2024 13:50:04 +0000
Subject: [PATCH] formatter update

---
 sysdata/libraries/stn/src/formatters.hb       | 139 ++++++++++++------
 sysdata/libraries/stn/src/lib.hb              |   6 +-
 .../programs/test/src/tests/stn/formatters.hb |  10 +-
 3 files changed, 104 insertions(+), 51 deletions(-)

diff --git a/sysdata/libraries/stn/src/formatters.hb b/sysdata/libraries/stn/src/formatters.hb
index c60d2a7..8c90fa1 100644
--- a/sysdata/libraries/stn/src/formatters.hb
+++ b/sysdata/libraries/stn/src/formatters.hb
@@ -1,30 +1,49 @@
-.{string, primitive, unsigned_int, signed_int, float, integer, memory, panic} := @use("stn")
+.{string, pointer, primitive, unsigned_int, signed_int, float, integer, memory, panic} := @use("stn")
 
-format_int := fn($T: type, v: T, str: ^u8): uint {
+format_int := fn($T: type, v: T, str: ^u8, radix: T): uint {
 	if integer(T) {
 		is_negative := signed_int(T) & v < 0
-		i := 0
 		if is_negative v = -v
 
-		if v == 0 {
-			*str = '0'
-			return 1
+		prefix_len := 0
+		if radix == 16 {
+			*str = '0';
+			*(str + 1) = 'x'
+			prefix_len = 2
+		} else if radix == 2 {
+			*str = '0';
+			*(str + 1) = 'b'
+			prefix_len = 2
+		} else if radix == 8 {
+			*str = '0';
+			*(str + 1) = 'o'
+			prefix_len = 2
 		}
 
-		loop if v == 0 break else {
-			remainder := v % 10
-			v /= 10;
-			*(str + i) = @intcast(remainder + '0')
+		if v == 0 {
+			*(str + prefix_len) = '0'
+			return prefix_len + 1
+		}
+
+		i := 0
+		loop if v <= 0 break else {
+			remainder := v % radix
+			v /= radix
+			if remainder > 9 {
+				*(str + prefix_len + i) = @intcast(remainder - 10 + 'A')
+			} else {
+				*(str + prefix_len + i) = @intcast(remainder + '0')
+			}
 			i += 1
 		}
 
 		if is_negative {
-			*(str + i) = '-'
+			*(str + prefix_len + i) = '-'
 			i += 1
 		}
 
-		@inline(string.reverse, str)
-		return i
+		@inline(string.reverse, str + prefix_len)
+		return prefix_len + i
 	} else {
 		panic("Type is not an integer\0")
 	}
@@ -32,85 +51,110 @@ format_int := fn($T: type, v: T, str: ^u8): uint {
 
 format_bool := fn(v: bool, str: ^u8): uint {
 	if v {
-		memory.copy(u8, "true\0", str, 4)
+		*@as(^[u8; 4], @bitcast(str)) = *@bitcast("true\0")
 		return 4
 	} else {
-		memory.copy(u8, "false\0", str, 5)
+		*@as(^[u8; 5], @bitcast(str)) = *@bitcast("false\0")
 		return 5
 	}
 }
 
-format_float := fn($T: type, v: T, str: ^u8, precision: uint): uint {
+format_float := fn($T: type, v: T, str: ^u8, precision: uint, radix: int): uint {
 	if float(T) {
 		is_negative := v < 0
 		i := 0
 
+		prefix_len := 0
+		if radix == 16 {
+			*str = '0';
+			*(str + 1) = 'x'
+			prefix_len = 2
+		} else if radix == 2 {
+			*str = '0';
+			*(str + 1) = 'b'
+			prefix_len = 2
+		} else if radix == 8 {
+			*str = '0';
+			*(str + 1) = 'o'
+			prefix_len = 2
+		}
+
 		if is_negative v = -v
 
 		integer_part := @fti(v)
 		fractional_part := v - @itf(integer_part)
 
 		loop if integer_part == 0 & i > 0 break else {
-			remainder := integer_part % 10
-			integer_part /= 10;
-			*(str + i) = @intcast(remainder + '0')
+			remainder := integer_part % radix
+			integer_part /= radix
+			if remainder > 9 {
+				*(str + prefix_len + i) = @intcast(remainder - 10 + 'A')
+			} else {
+				*(str + prefix_len + i) = @intcast(remainder + '0')
+			}
 			i += 1
 		}
 
 		if is_negative {
-			*(str + i) = '-'
+			*(str + prefix_len + i) = '-'
 			i += 1
 		}
 
-		@inline(string.reverse, str);
-		*(str + i) = '.'
+		@inline(string.reverse, str + prefix_len);
+		*(str + prefix_len + i) = '.'
 		i += 1
 
-		tolerance := @as(T, 0.000000001)
 		p := precision
+		tolerance := @as(T, 0.00000001)
 		loop if p <= 0 | fractional_part < tolerance break else {
-			fractional_part *= 10.0
-			digit := @fti(fractional_part);
-			*(str + i) = @intcast(digit + '0')
+			fractional_part *= @itf(radix)
+			digit := @fti(fractional_part)
+			if digit > 9 {
+				*(str + prefix_len + i) = @intcast(digit - 10 + 'A')
+			} else {
+				*(str + prefix_len + i) = @intcast(digit + '0')
+			}
 			i += 1
 			fractional_part -= @itf(digit)
 			p -= 1
 		}
 
-		return i
+		return prefix_len + i
 	} else {
 		panic("Type is not a floating point\0")
 	}
 }
 
 format_inner := fn($T: type, v: T, str: ^u8, opts: FormatOptions): uint {
-	if integer(T) {
-		return @inline(format_int, T, v, str)
+	if integer(T) | pointer(T) {
+		if integer(T) {
+			return @inline(format_int, T, v, str, @intcast(opts.radix))
+		} else {
+			return @inline(format_int, uint, @bitcast(v), str, 16)
+		}
 	} else if T == bool {
 		return @inline(format_bool, v, str)
 	} else if float(T) {
-		return @inline(format_float, T, v, str, opts.decimal_digits)
+		return @inline(format_float, T, v, str, opts.decimal_digits, @intcast(opts.radix))
 	} else if !primitive(T) {
 		i := 0
 		// name := @nameof(T)
-		// len := string.length(name)
-		// memory.copy(u8, name, str, len)
-		len := 0
-		memory.copy(u8, ".(\0", str + len, 2)
+		// len := @inline(string.length, name)
+		len := 0;
+		*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(".(\0")
 		len += 2
-		$loop if i == @len(T) break else {
+		$loop {
 			v_sub := v[i]
 			TSub := @TypeOf(v_sub)
 			len += @inline(format_inner, TSub, v_sub, str + len, opts)
-			if i != @len(T) - 1 {
-				memory.copy(u8, ", \0", str + len, 2)
-				len += 2
-			} else {
-				memory.copy(u8, ")\0", str + len, 1)
-				len += 1
-			}
 			i += 1
-		}
+			if i == @len(T) break else {
+				*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(", \0")
+				len += 2
+			}
+		};
+		*@as(^[u8; 1], @bitcast(str + len)) = *@bitcast(")\0")
+		len += 1
 		return len
 	} else {
 		panic("Unsupported formatter type\0")
@@ -121,11 +165,8 @@ format_inner := fn($T: type, v: T, str: ^u8, opts: FormatOptions): uint {
 * Custom formatters using struct methods (T._fmt(self, str): uint),
 * Format struct names "Name.(x, y, z)"
 * Format struct fields "Name.{a: x, b: y, c: z}"
-* Optimise (so many instructions...)
-*	-> Consider switching `memory.copy` to loop for comptime stuff
 * Optionally tabulate
 * Add more FormatOption fields
-* Support radices for integers
 * Support scientific notation for floating point
 * Support format string (impossible right now)
 * Support nullables (impossible right now)
@@ -134,10 +175,14 @@ format_inner := fn($T: type, v: T, str: ^u8, opts: FormatOptions): uint {
 
 FormatOptions := struct {
 	decimal_digits: uint,
+	radix: uint,
 }
 
-$DEFAULT_OPTS := FormatOptions.(2)
+$DEFAULT_OPTS := FormatOptions.(2, 10)
 
+/* SAFETY:
+ * Assumes the buffer is wide enough for the formatted text and a null char
+*/
 format := fn($T: type, v: T, str: ^u8): ^u8 return @inline(format_args, T, v, str, DEFAULT_OPTS)
 
 format_args := fn($T: type, v: T, str: ^u8, opts: FormatOptions): ^u8 {
diff --git a/sysdata/libraries/stn/src/lib.hb b/sysdata/libraries/stn/src/lib.hb
index cd00b85..a99515d 100644
--- a/sysdata/libraries/stn/src/lib.hb
+++ b/sysdata/libraries/stn/src/lib.hb
@@ -24,6 +24,10 @@ panic := fn(message: ?^u8): never {
 	die
 }
 
+$pointer := fn($T: type): bool {
+	return T != f64 & T != uint & T != int & @sizeof(T) == @sizeof(uint) & @alignof(T) == @alignof(uint)
+}
+
 $unsigned_int := fn($T: type): bool {
 	return T == uint | T == u8 | T == u16 | T == u32
 }
@@ -41,7 +45,7 @@ $float := fn($T: type): bool {
 }
 
 $primitive := fn($T: type): bool {
-	return integer(T) | float(T) | T == bool
+	return integer(T) | float(T) | pointer(T) | T == bool
 }
 
 $float_bytes := fn($T: type): type {
diff --git a/sysdata/programs/test/src/tests/stn/formatters.hb b/sysdata/programs/test/src/tests/stn/formatters.hb
index 77b5409..4378450 100644
--- a/sysdata/programs/test/src/tests/stn/formatters.hb
+++ b/sysdata/programs/test/src/tests/stn/formatters.hb
@@ -1,4 +1,4 @@
-.{formatters: .{format, format_args}, log, memory, math} := @use("stn");
+.{formatters: .{format, format_args, DEFAULT_OPTS}, log, memory, math} := @use("stn");
 .{Color} := @use("lib:render")
 
 Thingy := struct {
@@ -16,10 +16,14 @@ test := fn(): uint {
 	buffer := memory.request_page(1)
 	log.info(format(Thingy, .(-100, -100, .(-math.PI, true)), buffer))
 	log.info(format(SubThingy, .(-math.E, false), buffer))
-	log.info(format(Color, .{r: 1, g: 2, b: 3, a: 4}, buffer))
+	log.info(format_args(Color, .{r: 255, g: 254, b: 253, a: 252}, buffer, .{
+		decimal_digits: DEFAULT_OPTS.decimal_digits,
+		radix: 16,
+	}))
 	// default value: .{decimal_digits: 2}
-	log.info(format_args(f64, math.LN_2, buffer, .{decimal_digits: 1 << 32}))
+	log.info(format_args(f64, math.LN_2, buffer, .{decimal_digits: 1 << 32, radix: 16}))
 	log.info(format([u8; 3], .(1, 2, 3), buffer))
+	log.info(format(^SubThingy, &.(0.0, true), buffer))
 
 	return 0
 }
\ No newline at end of file