Odin's User Format
Odin's User Format
Odin has a pattern that allows you to create your own formats for use in the printf functions through out odin. With them you will be able to create your own way of formatting your data and be able to print them using standard functions.
To demonstrate this, this paper will take a simple example. If you have an integer print that integer with the caption of "This is the integer: 2". You will be able to do this with printf("%t", 2)
Creating the custom formatter.
The Formatter type
// 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
Parameters for a User_Formatter
- fi
fi is a pointer to fmt.Info. Info looks like this.
// Internal data structure that stores the required information for formatted printing Info :: struct { using state: Info_State, writer: io.Writer, arg: any, // Temporary indirection_level: int, record_level: int, optional_len: Maybe(int), use_nul_termination: bool, n: int, // bytes written } Info_State :: struct { minus: bool, plus: bool, space: bool, zero: bool, hash: bool, width_set: bool, prec_set: bool, ignore_user_formatters: bool, in_bad: bool, width: int, prec: int, indent: int, parent_struct: any, }I won't go into what each field is for. However there are a couple of very important fields
- arg
This is the actual data. Because it is an any type you can access the data through arg.data which is a rawptr to the data. If you need to access the type it is present in arg.type
- verb
This is a rune that is part of the format string. ex "%s" the verb will be 's'
Example Formatter
// Examplt formatter
prefix_integer_formatter::proc(fi: ^Info, arg: any, verb: rune) -> bool {
data: ^int = cast(^int)arg.data
switch verb {
case 'p':
fi.ignore_user_formatters = true
wprintf(fi.writer, "This is the integer: %i", data^)
fi.ignore_user_formatters = false
case:
fi.ignore_user_formatters = true
fmt.fmt_value(fi, arg, verb)
fi.ignore_user_formatters = false
}
return true
}
About the Example Formatter
- If you are calling wprintf or any other fmt functions, be sure to set fi.ignore_users_formatters to true. This prevents an infinite loop since if it is false it will keep calling your custom formatter.
- if you change fi.ignore_users_formatters set it back to its original value. Each formatter call for a wprintf uses the same fi so if you want your custom formatters to continue to be called ignore_users_formatters must be set to false
Using the prefix_integer_formatter
Registering the formatter
When registering a formatter you must first create an map where the formatters will be listed. This only needs to be done once. In fact ODIN will prevent you from creating more then one.
user_formatters := make(map[typeid]fmt.User_Formatter) fmt.set_user_formatters(&user_formatters) // can only be called once per program
Once the map has been set you can then register your formatters
err := fmt.register_user_formatter(int, prefix_integer_formatter)
Using the new formatter.
Using the new formatter is as simple as using the new verb
printf("%p", 2)
Conclusion
User Formatters give you another way to do some complex formatting and encapsulate it into a single function. They are fun to write and a nice way to add test your ODIN skills.
Appendix
Adding C's way of truncating strings from printf
ODIN does not support using %.5s to truncate the string to 5 characters. This is a standard in C, Go, and Java. I wrote a custom formatter to add this functionality.
package formatters
import "core:fmt"
import "core:strings"
@(private = "file")
STRING_VERB :: 's'
// formatter intended for strings. Odin does not support using precision in strings
// to specify the maximum length of the string. This will fix that problem.
truncate_string :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool {
data: ^string = cast(^string)arg.data
new_string := format_string(fi, data^)
fmt.fmt_string(fi, new_string, verb)
return true
}
// Do the actual truncation if the precision has been set.
// params:
// fi: Pointer to the format info
// data: The string to work with
// returns:
// The newly formatted string
@(private = "file")
format_string :: proc(fi: ^fmt.Info, data: string) -> (result: string) {
result = data
if fi.prec_set {
result, _ = strings.substring_to(data, clamp(fi.prec, 0, strings.rune_count(data)))
}
return
}
Just presented as another user formatter that may come in useful.