ableos/sysdata/libraries/stn/src/formatters.hb

192 lines
4.3 KiB
Plaintext
Raw Normal View History

2024-12-17 07:50:04 -06:00
.{string, pointer, primitive, unsigned_int, signed_int, float, integer, memory, panic} := @use("stn")
2024-12-17 07:50:04 -06:00
format_int := fn($T: type, v: T, str: ^u8, radix: T): uint {
if integer(T) {
is_negative := signed_int(T) & v < 0
if is_negative v = -v
2024-12-17 07:50:04 -06:00
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 v == 0 {
2024-12-17 07:50:04 -06:00
*(str + prefix_len) = '0'
return prefix_len + 1
}
2024-12-17 07:50:04 -06:00
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 {
2024-12-17 07:50:04 -06:00
*(str + prefix_len + i) = '-'
i += 1
}
2024-12-17 07:50:04 -06:00
@inline(string.reverse, str + prefix_len)
return prefix_len + i
} else {
panic("Type is not an integer\0")
}
}
format_bool := fn(v: bool, str: ^u8): uint {
if v {
2024-12-17 07:50:04 -06:00
*@as(^[u8; 4], @bitcast(str)) = *@bitcast("true\0")
return 4
} else {
2024-12-17 07:50:04 -06:00
*@as(^[u8; 5], @bitcast(str)) = *@bitcast("false\0")
return 5
}
}
2024-12-17 07:50:04 -06:00
format_float := fn($T: type, v: T, str: ^u8, precision: uint, radix: int): uint {
if float(T) {
is_negative := v < 0
i := 0
2024-12-17 07:50:04 -06:00
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 {
2024-12-17 07:50:04 -06:00
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 {
2024-12-17 07:50:04 -06:00
*(str + prefix_len + i) = '-'
i += 1
}
2024-12-17 07:50:04 -06:00
@inline(string.reverse, str + prefix_len);
*(str + prefix_len + i) = '.'
i += 1
p := precision
2024-12-17 07:50:04 -06:00
tolerance := @as(T, 0.00000001)
loop if p <= 0 | fractional_part < tolerance break else {
2024-12-17 07:50:04 -06:00
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
}
2024-12-17 07:50:04 -06:00
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 {
2024-12-17 07:50:04 -06:00
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) {
2024-12-17 07:50:04 -06:00
return @inline(format_float, T, v, str, opts.decimal_digits, @intcast(opts.radix))
2024-12-16 20:10:30 -06:00
} else if !primitive(T) {
i := 0
// name := @nameof(T)
2024-12-17 07:50:04 -06:00
// len := @inline(string.length, name)
len := 0;
*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(".(\0")
len += 2
2024-12-17 07:50:04 -06:00
$loop {
v_sub := v[i]
TSub := @TypeOf(v_sub)
len += @inline(format_inner, TSub, v_sub, str + len, opts)
2024-12-17 07:50:04 -06:00
i += 1
if i == @len(T) break else {
*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(", \0")
len += 2
}
2024-12-17 07:50:04 -06:00
};
*@as(^[u8; 1], @bitcast(str + len)) = *@bitcast(")\0")
len += 1
return len
2024-12-16 20:10:30 -06:00
} else {
panic("Unsupported formatter type\0")
}
}
/* TODO:
* 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}"
* Optionally tabulate
* Add more FormatOption fields
* Support scientific notation for floating point
* Support format string (impossible right now)
* Support nullables (impossible right now)
* Support pointers
*/
FormatOptions := struct {
decimal_digits: uint,
2024-12-17 07:50:04 -06:00
radix: uint,
}
2024-12-17 07:50:04 -06:00
$DEFAULT_OPTS := FormatOptions.(2, 10)
2024-12-17 07:50:04 -06:00
/* 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 {
@inline(string.clear, str)
_ = @inline(format_inner, T, v, str, opts)
return str
}