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

273 lines
6.6 KiB
Plaintext
Raw Normal View History

.{Kind, usize, string, signed_int, panic, float, integer, memory, log: .{LogLevel}} := @use("stn")
2024-12-17 16:30:22 -06:00
2024-12-22 13:48:12 -06:00
fmt_int := fn(v: @Any(), str: []u8, radix: @TypeOf(v)): uint {
2024-12-17 16:30:22 -06:00
is_negative := signed_int(@TypeOf(v)) & v < 0
prefix_len := 0
2024-12-22 13:48:12 -06:00
if is_negative {
v = -v
str[0] = '-'
prefix_len += 1
}
2024-12-17 16:30:22 -06:00
if radix == 16 {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0x".ptr)
prefix_len += 2
2024-12-17 16:30:22 -06:00
} else if radix == 2 {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0b".ptr)
prefix_len += 2
2024-12-17 16:30:22 -06:00
} else if radix == 8 {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0o".ptr)
prefix_len += 2
2024-12-17 16:30:22 -06:00
}
2024-12-17 16:30:22 -06:00
if v == 0 {
2024-12-22 13:48:12 -06:00
str[prefix_len] = '0'
2024-12-17 16:30:22 -06:00
return prefix_len + 1
}
2024-12-22 13:48:12 -06:00
i := prefix_len
2024-12-17 16:30:22 -06:00
loop if v <= 0 break else {
remainder := v % radix
v /= radix
if remainder > 9 {
2024-12-22 13:48:12 -06:00
str[i] = @intcast(remainder - 10 + 'A')
2024-12-17 16:30:22 -06:00
} else {
2024-12-22 13:48:12 -06:00
str[i] = @intcast(remainder + '0')
}
2024-12-17 16:30:22 -06:00
i += 1
}
2024-12-22 13:48:12 -06:00
string.reverse(str[prefix_len..i])
return i
}
2024-12-22 13:48:12 -06:00
fmt_bool := fn(v: bool, str: []u8): uint {
if v {
2024-12-22 13:48:12 -06:00
*@as(^[4]u8, @bitcast(str.ptr)) = *@bitcast("true".ptr)
return 4
} else {
2024-12-22 13:48:12 -06:00
*@as(^[5]u8, @bitcast(str.ptr)) = *@bitcast("false".ptr)
return 5
}
}
2024-12-22 13:48:12 -06:00
$FP_TOLERANCE := 0.00000001
fmt_float := fn(v: @Any(), str: []u8, precision: uint, radix: int): uint {
2024-12-17 16:30:22 -06:00
is_negative := v < 0
prefix_len := 0
2024-12-22 13:48:12 -06:00
if is_negative {
v = -v
str[0] = '-'
prefix_len += 1
}
2024-12-17 16:30:22 -06:00
if radix == 16 {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0x".ptr)
prefix_len += 2
2024-12-17 16:30:22 -06:00
} else if radix == 2 {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0b".ptr)
prefix_len += 2
2024-12-17 16:30:22 -06:00
} else if radix == 8 {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0o".ptr)
prefix_len += 2
2024-12-17 16:30:22 -06:00
}
2024-12-17 07:50:04 -06:00
2024-12-17 16:30:22 -06:00
integer_part := @fti(v)
fractional_part := v - @itf(integer_part)
2024-12-22 13:48:12 -06:00
i := prefix_len
loop if integer_part == 0 & i > prefix_len break else {
2024-12-17 16:30:22 -06:00
remainder := integer_part % radix
integer_part /= radix
if remainder > 9 {
2024-12-22 13:48:12 -06:00
str[i] = @intcast(remainder - 10 + 'A')
2024-12-17 16:30:22 -06:00
} else {
2024-12-22 13:48:12 -06:00
str[i] = @intcast(remainder + '0')
}
2024-12-17 16:30:22 -06:00
i += 1
}
2024-12-22 13:48:12 -06:00
string.reverse(str[prefix_len..i])
str[i] = '.'
2024-12-17 16:30:22 -06:00
i += 1
p := precision
2024-12-22 13:48:12 -06:00
loop if p <= 0 | fractional_part < FP_TOLERANCE break else {
2024-12-17 16:30:22 -06:00
fractional_part *= @itf(radix)
digit := @fti(fractional_part)
if digit > 9 {
2024-12-22 13:48:12 -06:00
str[i] = @intcast(digit - 10 + 'A')
2024-12-17 16:30:22 -06:00
} else {
2024-12-22 13:48:12 -06:00
str[i] = @intcast(digit + '0')
2024-12-17 16:30:22 -06:00
}
i += 1
2024-12-17 16:30:22 -06:00
fractional_part -= @itf(digit)
p -= 1
}
2024-12-22 13:48:12 -06:00
return i
2024-12-17 16:30:22 -06:00
}
2024-12-22 13:48:12 -06:00
fmt_container := fn(v: @Any(), str: []u8, opts: FormatOptions): uint {
2024-12-17 16:30:22 -06:00
T2 := @TypeOf(v)
2024-12-20 05:21:03 -06:00
kind := Kind.of(T2)
i := 0
len := 0
if kind == .Struct {
2024-12-22 13:48:12 -06:00
*@as(^[@intcast(@nameof(T2).len)]u8, @bitcast(str.ptr + len)) = *@bitcast(@nameof(T2).ptr)
len += @nameof(T2).len;
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(".(".ptr)
2024-12-20 05:21:03 -06:00
len += 2
} else if kind == .Slice {
2024-12-22 13:48:12 -06:00
*@as(^[@intcast(@nameof(@ChildOf(T2)).len)]u8, @bitcast(str.ptr + len)) = *@bitcast(@nameof(@ChildOf(T2)).ptr)
len += @nameof(@ChildOf(T2)).len;
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(".[".ptr)
2024-12-20 05:21:03 -06:00
len += 2
} else if kind == .Tuple {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(".(".ptr)
2024-12-20 05:21:03 -06:00
len += 2
}
2024-12-17 16:30:22 -06:00
$loop {
v_sub := v[i]
2024-12-22 13:48:12 -06:00
len += @inline(format, v_sub, str[len..], opts)
2024-12-17 16:30:22 -06:00
i += 1
if i == @lenof(T2) break else {
2024-12-22 13:48:12 -06:00
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(", ".ptr)
2024-12-17 16:30:22 -06:00
len += 2
}
2024-12-20 05:21:03 -06:00
}
if kind == .Struct | kind == .Tuple {
2024-12-22 13:48:12 -06:00
*@as(^[1]u8, @bitcast(str.ptr + len)) = *@bitcast(")".ptr)
2024-12-20 05:21:03 -06:00
len += 1
} else if kind == .Slice {
2024-12-22 13:48:12 -06:00
*@as(^[1]u8, @bitcast(str.ptr + len)) = *@bitcast("]".ptr)
2024-12-20 05:21:03 -06:00
len += 1
}
2024-12-17 16:30:22 -06:00
return len
}
2024-12-22 13:48:12 -06:00
fmt_nullable := fn(v: @Any(), str: []u8, opts: FormatOptions): uint {
2024-12-17 16:30:22 -06:00
if v == null {
2024-12-22 13:48:12 -06:00
*@as(^[4]u8, @bitcast(str.ptr)) = *@bitcast("null".ptr)
2024-12-17 16:30:22 -06:00
return 4
} else {
2024-12-19 11:19:57 -06:00
return @inline(format, @as(@ChildOf(@TypeOf(v)), v), str, opts)
}
}
2024-12-22 13:48:12 -06:00
fmt_enum := fn(v: @Any(), str: []u8, opts: FormatOptions): uint {
2024-12-19 11:19:57 -06:00
T := @TypeOf(v)
2024-12-22 13:48:12 -06:00
len := @nameof(T).len;
*@as(^[@intcast(@nameof(T).len)]u8, @bitcast(str.ptr)) = *@bitcast(@nameof(T).ptr);
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(".(".ptr)
2024-12-19 11:29:39 -06:00
len += 2
2024-12-22 13:48:12 -06:00
len += fmt_int(@as(usize(T), @bitcast(v)), str[len..], 10);
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(")".ptr)
return len + 1
2024-12-19 11:19:57 -06:00
}
2024-12-21 06:01:48 -06:00
/* TODO:
* Custom formatters using struct methods (T._fmt(self, str): uint),
* 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
*/
FormatOptions := struct {
precision: uint = 2,
radix: uint = 10,
// temporarily here, will change later maybe
log: LogLevel = .Info,
}
2024-12-19 11:19:57 -06:00
/* SAFETY:
* Assumes the buffer is wide enough for the formatted text and a null char
* Does not clear the buffer for you
*/
2024-12-22 13:48:12 -06:00
format := fn(v: @Any(), str: []u8, opts: FormatOptions): uint {
2024-12-17 16:30:22 -06:00
T := @TypeOf(v)
match Kind.of(T) {
2024-12-22 13:48:12 -06:00
.Pointer => return @inline(fmt_int, @as(uint, @bitcast(v)), str, 16),
2024-12-17 11:43:00 -06:00
.Builtin => {
if integer(T) {
2024-12-17 16:30:22 -06:00
return @inline(fmt_int, v, str, @intcast(opts.radix))
2024-12-17 11:43:00 -06:00
} else if T == bool {
2024-12-17 16:30:22 -06:00
return @inline(fmt_bool, v, str)
2024-12-17 11:43:00 -06:00
} else if float(T) {
2024-12-21 06:01:48 -06:00
return @inline(fmt_float, v, str, opts.precision, @intcast(opts.radix))
}
2024-12-17 11:43:00 -06:00
},
2024-12-17 16:30:22 -06:00
.Opt => return @inline(fmt_nullable, v, str, opts),
2024-12-19 11:19:57 -06:00
.Enum => return @inline(fmt_enum, v, str, opts),
2024-12-17 16:30:22 -06:00
.Struct => return @inline(fmt_container, v, str, opts),
2024-12-20 04:47:22 -06:00
.Tuple => return @inline(fmt_container, v, str, opts),
2024-12-22 13:48:12 -06:00
.Slice => if T != []u8 return @inline(fmt_container, v, str, opts) else {
i := 0
loop if i == v.len break else {
str[i] = v[i]
i += 1
}
return v.len
},
_ => @error("Type: \"", T, "\" is not supported."),
}
}
2024-12-22 13:48:12 -06:00
format_with_str := fn(v: @Any(), read: []u8, write: []u8, opts: FormatOptions): uint {
T := @TypeOf(v)
n := string.count(read, '{')
2024-12-22 13:48:12 -06:00
if n != string.count(read, '}') panic("Missing closing '}' in format string.")
if Kind.of(T) == .Tuple {
2024-12-22 13:48:12 -06:00
if @lenof(T) != n panic("Format string has different number of '{}' than args given.")
m := 0
i := 0
j := 0
$loop if m > @lenof(T) break else {
if m == @lenof(T) {
2024-12-22 13:48:12 -06:00
loop if i == read.len break else {
write[j] = read[i]
i += 1
j += 1
}
m += 1
} else {
v2 := v[m]
2024-12-22 13:48:12 -06:00
loop if i == read.len break else {
if read[i] == '{' & read[i + 1] == '}' {
j += format(v2, write[j..], opts)
i += 2
break
} else {
2024-12-22 13:48:12 -06:00
write[j] = read[i]
i += 1
j += 1
}
}
m += 1
}
}
return j
} else if n > 1 {
2024-12-22 13:48:12 -06:00
panic("Format string has multiple '{}' but value provided is not a tuple.")
} else {
i := 0
j := 0
2024-12-22 13:48:12 -06:00
loop if i == read.len break else {
if read[i] == '{' & read[i + 1] == '}' {
j += format(v, write[j..], opts)
i += 2
} else {
2024-12-22 13:48:12 -06:00
write[j] = read[i]
i += 1
j += 1
}
}
return j
}
2024-12-20 04:47:22 -06:00
}