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