akern-gkgoat-fork/sysdata/libraries/stn/src/fmt.hb

279 lines
6.5 KiB
Plaintext

.{Kind, usize, string, signed_int, panic, float, integer, memory, log: .{LogLevel}} := @use("stn")
fmt_int := fn(v: @Any(), str: ^u8, radix: @TypeOf(v)): uint {
is_negative := signed_int(@TypeOf(v)) & v < 0
if is_negative v = -v
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 {
*(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 + prefix_len + i) = '-'
i += 1
}
@inline(string.reverse, str + prefix_len)
return prefix_len + i
}
fmt_bool := fn(v: bool, str: ^u8): uint {
if v {
*@as(^[4]u8, @bitcast(str)) = *@bitcast("true\0")
return 4
} else {
*@as(^[5]u8, @bitcast(str)) = *@bitcast("false\0")
return 5
}
}
fmt_float := fn(v: @Any(), str: ^u8, precision: uint, radix: int): uint {
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 % 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 + prefix_len + i) = '-'
i += 1
}
@inline(string.reverse, str + prefix_len);
*(str + prefix_len + i) = '.'
i += 1
p := precision
tolerance := @as(@TypeOf(v), 0.00000001)
loop if p <= 0 | fractional_part < tolerance break else {
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 prefix_len + i
}
fmt_container := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
T2 := @TypeOf(v)
kind := Kind.of(T2)
i := 0
len := 0
if kind == .Struct {
*@as(^[@lenof(@nameof(T2))]u8, @bitcast(str + len)) = *@bitcast(@nameof(T2))
len += @lenof(@nameof(T2));
*@as(^[2]u8, @bitcast(str + len)) = *@bitcast(".(\0")
len += 2
} else if kind == .Slice {
*@as(^[@lenof(@nameof(@ChildOf(T2)))]u8, @bitcast(str + len)) = *@bitcast(@nameof(@ChildOf(T2)))
len += @lenof(@nameof(@ChildOf(T2)));
*@as(^[2]u8, @bitcast(str + len)) = *@bitcast(".[\0")
len += 2
} else if kind == .Tuple {
*@as(^[2]u8, @bitcast(str + len)) = *@bitcast(".(\0")
len += 2
}
$loop {
v_sub := v[i]
len += @inline(format, v_sub, str + len, opts)
i += 1
if i == @lenof(T2) break else {
*@as(^[2]u8, @bitcast(str + len)) = *@bitcast(", \0")
len += 2
}
}
if kind == .Struct | kind == .Tuple {
*@as(^[1]u8, @bitcast(str + len)) = *@bitcast(")\0")
len += 1
} else if kind == .Slice {
*@as(^[1]u8, @bitcast(str + len)) = *@bitcast("]\0")
len += 1
}
return len
}
fmt_nullable := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
if v == null {
*@as(^[4]u8, @bitcast(str)) = *@bitcast("null\0")
return 4
} else {
return @inline(format, @as(@ChildOf(@TypeOf(v)), v), str, opts)
}
}
fmt_enum := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
T := @TypeOf(v)
len := @lenof(@nameof(T));
*@as(^[@lenof(@nameof(T))]u8, @bitcast(str)) = *@bitcast(@nameof(T));
*@as(^[2]u8, @bitcast(str + len)) = *@bitcast(".(\0")
len += 2
len += @inline(fmt_int, @as(usize(T), @bitcast(v)), str + len, 10);
*@as(^[2]u8, @bitcast(str + len)) = *@bitcast(")\0")
return len + 2
}
/* 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,
}
/* SAFETY:
* Assumes the buffer is wide enough for the formatted text and a null char
* Does not clear the buffer for you
*/
format := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
T := @TypeOf(v)
match Kind.of(T) {
.Pointer => if T != ^u8 return @inline(fmt_int, @as(uint, @bitcast(v)), str, 16) else {
i := 0
loop if *(v + i) == '\0' break else {
*(str + i) = *(v + i)
i += 1
}
return i
},
.Builtin => {
if integer(T) {
return @inline(fmt_int, v, str, @intcast(opts.radix))
} else if T == bool {
return @inline(fmt_bool, v, str)
} else if float(T) {
return @inline(fmt_float, v, str, opts.precision, @intcast(opts.radix))
}
},
.Opt => return @inline(fmt_nullable, v, str, opts),
.Enum => return @inline(fmt_enum, v, str, opts),
.Struct => return @inline(fmt_container, v, str, opts),
.Tuple => return @inline(fmt_container, v, str, opts),
.Slice => return @inline(fmt_container, v, str, opts),
_ => @error("Type: \"\0", T, "\" is not supported.\0"),
}
}
format_with_str := fn(v: @Any(), read: ^u8, write: ^u8, opts: FormatOptions): uint {
T := @TypeOf(v)
n := string.count(read, '{')
if n != string.count(read, '}') panic("Missing closing '}' in format string.\0")
if Kind.of(T) == .Tuple {
if @lenof(T) != n panic("Format string has different number of '{}' than args given.\0")
m := 0
i := 0
j := 0
$loop if m > @lenof(T) break else {
if m == @lenof(T) {
loop if *(read + i) == '\0' break else {
*(write + j) = *(read + i)
i += 1
j += 1
}
m += 1
} else {
v2 := v[m]
loop if *(read + i) == '\0' break else {
if *(read + i) == '{' & *(read + i + 1) == '}' {
j += format(v2, write + j, opts) + 1
i += 2
break
} else {
*(write + j) = *(read + i)
i += 1
j += 1
}
}
m += 1
}
}
return j
} else if n > 1 {
panic("Format string has multiple '{}' but value provided is not a tuple.\0")
} else {
i := 0
j := 0
loop if *(read + i) == '\0' break else {
if *(read + i) == '{' & *(read + i + 1) == '}' {
j += format(v, write + j, opts) + 1
i += 2
} else {
*(write + j) = *(read + i)
i += 1
j += 1
}
}
return j
}
}