Improve fmt parsing of struct field tags

This commit is contained in:
gingerBill
2024-05-20 14:45:49 +01:00
parent 5ed93563a1
commit f600562ca5
+90 -42
View File
@@ -13,20 +13,7 @@ import "core:unicode/utf8"
// Internal data structure that stores the required information for formatted printing // Internal data structure that stores the required information for formatted printing
Info :: struct { Info :: struct {
minus: bool, using state: Info_State,
plus: bool,
space: bool,
zero: bool,
hash: bool,
width_set: bool,
prec_set: bool,
width: int,
prec: int,
indent: int,
ignore_user_formatters: bool,
in_bad: bool,
writer: io.Writer, writer: io.Writer,
arg: any, // Temporary arg: any, // Temporary
@@ -39,6 +26,24 @@ Info :: struct {
n: int, // bytes written 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,
}
// Custom formatter signature. It returns true if the formatting was successful and false when it could not be done // 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 User_Formatter :: #type proc(fi: ^Info, arg: any, verb: rune) -> bool
@@ -1824,7 +1829,7 @@ fmt_write_array :: proc(fi: ^Info, array_data: rawptr, count: int, elem_size: in
// Returns: A boolean value indicating whether to continue processing the tag // Returns: A boolean value indicating whether to continue processing the tag
// //
@(private) @(private)
handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: ^rune, optional_len: ^int, use_nul_termination: ^bool) -> (do_continue: bool) { handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: ^rune, optional_len: ^int, use_nul_termination: ^bool) -> (do_continue: bool) {
handle_optional_len :: proc(data: rawptr, info: reflect.Type_Info_Struct, field_name: string, optional_len: ^int) { handle_optional_len :: proc(data: rawptr, info: reflect.Type_Info_Struct, field_name: string, optional_len: ^int) {
if optional_len == nil { if optional_len == nil {
return return
@@ -1841,45 +1846,83 @@ handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb:
break break
} }
} }
tag := info.tags[idx] tag := info.tags[idx]
if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok { if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok {
value := strings.trim_space(string(vt)) value := strings.trim_space(string(vt))
switch value { switch value {
case "": return false case "": return false
case "-": return true case "-": return true
} }
r, w := utf8.decode_rune_in_string(value)
value = value[w:] fi := state
if value == "" || value[0] == ',' {
if verb^ == 'w' { head, _, tail := strings.partition(value, ",")
// TODO(bill): is this a good idea overriding that field tags if 'w' is used?
switch r { i := 0
case 's': r = 'q' prefix_loop: for ; i < len(head); i += 1 {
case: r = 'w' switch head[i] {
} case '+':
fi.plus = true
case '-':
fi.minus = true
fi.zero = false
case ' ':
fi.space = true
case '#':
fi.hash = true
case '0':
fi.zero = !fi.minus
case:
break prefix_loop
} }
verb^ = r }
if len(value) > 0 && value[0] == ',' {
field_name := value[1:] fi.width, i, fi.width_set = _parse_int(head, i)
if field_name == "0" { if i < len(head) && head[i] == '.' {
if use_nul_termination != nil { i += 1
use_nul_termination^ = true prev_i := i
} fi.prec, i, fi.prec_set = _parse_int(head, i)
} else { if i == prev_i {
switch r { fi.prec = 0
case 's', 'q': fi.prec_set = true
}
}
r: rune
if i >= len(head) || head[i] == ' ' {
r = 'v'
} else {
r, _ = utf8.decode_rune_in_string(head[i:])
}
if verb^ == 'w' {
// TODO(bill): is this a good idea overriding that field tags if 'w' is used?
switch r {
case 's': r = 'q'
case: r = 'w'
}
}
verb^ = r
if tail != "" {
field_name := tail
if field_name == "0" {
if use_nul_termination != nil {
use_nul_termination^ = true
}
} else {
switch r {
case 's', 'q':
handle_optional_len(data, info, field_name, optional_len)
case 'v', 'w':
#partial switch reflect.type_kind(info.types[idx].id) {
case .String, .Multi_Pointer, .Array, .Slice, .Dynamic_Array:
handle_optional_len(data, info, field_name, optional_len) handle_optional_len(data, info, field_name, optional_len)
case 'v', 'w':
#partial switch reflect.type_kind(info.types[idx].id) {
case .String, .Multi_Pointer, .Array, .Slice, .Dynamic_Array:
handle_optional_len(data, info, field_name, optional_len)
}
} }
} }
} }
} }
} }
return false return
} }
// Formats a struct for output, handling various struct types (e.g., SOA, raw unions) // Formats a struct for output, handling various struct types (e.g., SOA, raw unions)
// //
@@ -2027,7 +2070,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
optional_len: int = -1 optional_len: int = -1
use_nul_termination: bool = false use_nul_termination: bool = false
verb := the_verb if the_verb == 'w' else 'v' verb := the_verb if the_verb == 'w' else 'v'
if handle_tag(v.data, info, i, &verb, &optional_len, &use_nul_termination) {
new_state := fi.state
if handle_tag(&new_state, v.data, info, i, &verb, &optional_len, &use_nul_termination) {
continue continue
} }
field_count += 1 field_count += 1
@@ -2052,8 +2097,11 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
if t := info.types[i]; reflect.is_any(t) { if t := info.types[i]; reflect.is_any(t) {
io.write_string(fi.writer, "any{}", &fi.n) io.write_string(fi.writer, "any{}", &fi.n)
} else { } else {
prev_state := fi.state
fi.state = new_state
data := rawptr(uintptr(v.data) + info.offsets[i]) data := rawptr(uintptr(v.data) + info.offsets[i])
fmt_arg(fi, any{data, t.id}, verb) fmt_arg(fi, any{data, t.id}, verb)
fi.state = prev_state
} }
if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) } if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) }