diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 5fc26f898..31eaa965d 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -10,7 +10,6 @@ import "core:strings" import "core:reflect" import "intrinsics" - @private DEFAULT_BUFFER_SIZE :: 1<<12; @@ -35,6 +34,34 @@ Info :: struct { record_level: int, } +// Custom formatter signature. It returns true if the formatting was successful and false when it could not be done +User_Formatter :: #type proc(fi: ^Info, arg: any, verb: rune) -> bool; + +Register_User_Formatter_Error :: enum { + None, + No_User_Formatter, + Formatter_Previously_Found, +} + +// NOTE(bill): This is a pointer to prevent accidental additions +// it is prefixed with `_` rather than marked with a private attribute so that users can access it if necessary +_user_formatters: ^map[typeid]User_Formatter; + +set_user_formatters :: proc(m: ^map[typeid]User_Formatter) { + _user_formatters = m; +} +register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Register_User_Formatter_Error { + if _user_formatters == nil { + return .No_User_Formatter; + } + if prev, found := _user_formatters[id]; found && prev != nil { + return .Formatter_Previously_Found; + } + _user_formatters[id] = formatter; + return .None; +} + + fprint :: proc(fd: os.Handle, args: ..any) -> int { data: [DEFAULT_BUFFER_SIZE]byte; buf := strings.builder_from_slice(data[:]); @@ -1189,6 +1216,16 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { return; } + if _user_formatters != nil { + formatter := _user_formatters[v.id]; + if formatter != nil { + if ok := formatter(fi, v, verb); !ok { + fmt_bad_verb(fi, verb); + } + return; + } + } + type_info := type_info_of(v.id); switch info in type_info.variant { case runtime.Type_Info_Any: // Ignore @@ -1795,6 +1832,16 @@ fmt_arg :: proc(fi: ^Info, arg: any, verb: rune) { return; } + if _user_formatters != nil { + formatter := _user_formatters[arg.id]; + if formatter != nil { + if ok := formatter(fi, arg, verb); !ok { + fmt_bad_verb(fi, verb); + } + return; + } + } + custom_types: switch a in arg { case runtime.Source_Code_Location: