273 lines
6.6 KiB
Plaintext
273 lines
6.6 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
|
|
prefix_len := 0
|
|
if is_negative {
|
|
v = -v
|
|
str[0] = '-'
|
|
prefix_len += 1
|
|
}
|
|
|
|
if radix == 16 {
|
|
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0x".ptr)
|
|
prefix_len += 2
|
|
} else if radix == 2 {
|
|
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0b".ptr)
|
|
prefix_len += 2
|
|
} else if radix == 8 {
|
|
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0o".ptr)
|
|
prefix_len += 2
|
|
}
|
|
|
|
if v == 0 {
|
|
str[prefix_len] = '0'
|
|
return prefix_len + 1
|
|
}
|
|
|
|
i := prefix_len
|
|
loop if v <= 0 break else {
|
|
remainder := v % radix
|
|
v /= radix
|
|
if remainder > 9 {
|
|
str[i] = @intcast(remainder - 10 + 'A')
|
|
} else {
|
|
str[i] = @intcast(remainder + '0')
|
|
}
|
|
i += 1
|
|
}
|
|
|
|
string.reverse(str[prefix_len..i])
|
|
return i
|
|
}
|
|
|
|
fmt_bool := fn(v: bool, str: []u8): uint {
|
|
if v {
|
|
*@as(^[4]u8, @bitcast(str.ptr)) = *@bitcast("true".ptr)
|
|
return 4
|
|
} else {
|
|
*@as(^[5]u8, @bitcast(str.ptr)) = *@bitcast("false".ptr)
|
|
return 5
|
|
}
|
|
}
|
|
|
|
$FP_TOLERANCE := 0.00000001
|
|
|
|
fmt_float := fn(v: @Any(), str: []u8, precision: uint, radix: int): uint {
|
|
is_negative := v < 0
|
|
|
|
prefix_len := 0
|
|
|
|
if is_negative {
|
|
v = -v
|
|
str[0] = '-'
|
|
prefix_len += 1
|
|
}
|
|
|
|
if radix == 16 {
|
|
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0x".ptr)
|
|
prefix_len += 2
|
|
} else if radix == 2 {
|
|
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0b".ptr)
|
|
prefix_len += 2
|
|
} else if radix == 8 {
|
|
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0o".ptr)
|
|
prefix_len += 2
|
|
}
|
|
|
|
integer_part := @fti(v)
|
|
fractional_part := v - @itf(integer_part)
|
|
|
|
i := prefix_len
|
|
loop if integer_part == 0 & i > prefix_len break else {
|
|
remainder := integer_part % radix
|
|
integer_part /= radix
|
|
if remainder > 9 {
|
|
str[i] = @intcast(remainder - 10 + 'A')
|
|
} else {
|
|
str[i] = @intcast(remainder + '0')
|
|
}
|
|
i += 1
|
|
}
|
|
|
|
string.reverse(str[prefix_len..i])
|
|
str[i] = '.'
|
|
i += 1
|
|
|
|
p := precision
|
|
loop if p <= 0 | fractional_part < FP_TOLERANCE break else {
|
|
fractional_part *= @itf(radix)
|
|
digit := @fti(fractional_part)
|
|
if digit > 9 {
|
|
str[i] = @intcast(digit - 10 + 'A')
|
|
} else {
|
|
str[i] = @intcast(digit + '0')
|
|
}
|
|
i += 1
|
|
fractional_part -= @itf(digit)
|
|
p -= 1
|
|
}
|
|
|
|
return 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(^[@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)
|
|
len += 2
|
|
} else if kind == .Slice {
|
|
*@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)
|
|
len += 2
|
|
} else if kind == .Tuple {
|
|
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(".(".ptr)
|
|
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.ptr + len)) = *@bitcast(", ".ptr)
|
|
len += 2
|
|
}
|
|
}
|
|
if kind == .Struct | kind == .Tuple {
|
|
*@as(^[1]u8, @bitcast(str.ptr + len)) = *@bitcast(")".ptr)
|
|
len += 1
|
|
} else if kind == .Slice {
|
|
*@as(^[1]u8, @bitcast(str.ptr + len)) = *@bitcast("]".ptr)
|
|
len += 1
|
|
}
|
|
return len
|
|
}
|
|
|
|
fmt_nullable := fn(v: @Any(), str: []u8, opts: FormatOptions): uint {
|
|
if v == null {
|
|
*@as(^[4]u8, @bitcast(str.ptr)) = *@bitcast("null".ptr)
|
|
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 := @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)
|
|
len += 2
|
|
len += fmt_int(@as(usize(T), @bitcast(v)), str[len..], 10);
|
|
*@as(^[2]u8, @bitcast(str.ptr + len)) = *@bitcast(")".ptr)
|
|
return len + 1
|
|
}
|
|
|
|
/* 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 => return @inline(fmt_int, @as(uint, @bitcast(v)), str, 16),
|
|
.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 => 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."),
|
|
}
|
|
}
|
|
|
|
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.")
|
|
if Kind.of(T) == .Tuple {
|
|
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) {
|
|
loop if i == read.len break else {
|
|
write[j] = read[i]
|
|
i += 1
|
|
j += 1
|
|
}
|
|
m += 1
|
|
} else {
|
|
v2 := v[m]
|
|
loop if i == read.len break else {
|
|
if read[i] == '{' & read[i + 1] == '}' {
|
|
j += format(v2, write[j..], opts)
|
|
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.")
|
|
} else {
|
|
i := 0
|
|
j := 0
|
|
loop if i == read.len break else {
|
|
if read[i] == '{' & read[i + 1] == '}' {
|
|
j += format(v, write[j..], opts)
|
|
i += 2
|
|
} else {
|
|
write[j] = read[i]
|
|
i += 1
|
|
j += 1
|
|
}
|
|
}
|
|
return j
|
|
}
|
|
} |