.{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 } }