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