forked from AbleOS/ableos
279 lines
6.5 KiB
Plaintext
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
|
|
}
|
|
} |