.{string, primitive, unsigned_int, signed_int, float, integer, memory, panic} := @use("stn") format_int := fn($T: type, v: T, str: ^u8): uint { if integer(T) { is_negative := signed_int(T) & v < 0 i := 0 if is_negative v = -v if v == 0 { *str = '0' return 1 } loop if v == 0 break else { remainder := v % 10 v /= 10; *(str + i) = @intcast(remainder + '0') i += 1 } if is_negative { *(str + i) = '-' i += 1 } @inline(string.reverse, str) return i } else { panic("Type is not an integer\0") } } format_bool := fn(v: bool, str: ^u8): uint { if v { memory.copy(u8, "true\0", str, 4) return 4 } else { memory.copy(u8, "false\0", str, 5) return 5 } } format_float := fn($T: type, v: T, str: ^u8, precision: uint): uint { if float(T) { is_negative := v < 0 i := 0 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 % 10 integer_part /= 10; *(str + i) = @intcast(remainder + '0') i += 1 } if is_negative { *(str + i) = '-' i += 1 } @inline(string.reverse, str); *(str + i) = '.' i += 1 tolerance := @as(T, 0.000000001) p := precision loop if p <= 0 | fractional_part < tolerance break else { fractional_part *= 10.0 digit := @fti(fractional_part); *(str + i) = @intcast(digit + '0') i += 1 fractional_part -= @itf(digit) p -= 1 } return i } else { panic("Type is not a floating point\0") } } format_inner := fn($T: type, v: T, str: ^u8, opts: FormatOptions): uint { if integer(T) { return @inline(format_int, T, v, str) } else if T == bool { return @inline(format_bool, v, str) } else if float(T) { return @inline(format_float, T, v, str, opts.decimal_digits) } else if !primitive(T) { i := 0 // name := @nameof(T) // len := string.length(name) // memory.copy(u8, name, str, len) len := 0 memory.copy(u8, ".(\0", str + len, 2) len += 2 $loop if i == @len(T) break else { v_sub := v[i] TSub := @TypeOf(v_sub) len += @inline(format_inner, TSub, v_sub, str + len, opts) if i != @len(T) - 1 { memory.copy(u8, ", \0", str + len, 2) len += 2 } else { memory.copy(u8, ")\0", str + len, 1) len += 1 } i += 1 } return len } else { panic("Unsupported formatter type\0") } } /* TODO: * Custom formatters using struct methods (T._fmt(self, str): uint), * Format struct names "Name.(x, y, z)" * Format struct fields "Name.{a: x, b: y, c: z}" * Optimise (so many instructions...) * -> Consider switching `memory.copy` to loop for comptime stuff * Optionally tabulate * Add more FormatOption fields * Support radices for integers * Support scientific notation for floating point * Support format string (impossible right now) * Support nullables (impossible right now) * Support pointers */ FormatOptions := struct { decimal_digits: uint, } $DEFAULT_OPTS := FormatOptions.(2) format := fn($T: type, v: T, str: ^u8): ^u8 return @inline(format_args, T, v, str, DEFAULT_OPTS) format_args := fn($T: type, v: T, str: ^u8, opts: FormatOptions): ^u8 { @inline(string.clear, str) _ = @inline(format_inner, T, v, str, opts) return str }