|
@@ -13,6 +13,20 @@ 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 {
|
|
|
|
+ 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,
|
|
minus: bool,
|
|
plus: bool,
|
|
plus: bool,
|
|
space: bool,
|
|
space: bool,
|
|
@@ -21,24 +35,15 @@ Info :: struct {
|
|
width_set: bool,
|
|
width_set: bool,
|
|
prec_set: bool,
|
|
prec_set: bool,
|
|
|
|
|
|
- width: int,
|
|
|
|
- prec: int,
|
|
|
|
- indent: int,
|
|
|
|
-
|
|
|
|
ignore_user_formatters: bool,
|
|
ignore_user_formatters: bool,
|
|
in_bad: bool,
|
|
in_bad: bool,
|
|
|
|
|
|
- writer: io.Writer,
|
|
|
|
- arg: any, // Temporary
|
|
|
|
- indirection_level: int,
|
|
|
|
- record_level: int,
|
|
|
|
-
|
|
|
|
- optional_len: Maybe(int),
|
|
|
|
- use_nul_termination: bool,
|
|
|
|
-
|
|
|
|
- n: int, // bytes written
|
|
|
|
|
|
+ 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:]
|
|
|
|
- if value == "" || value[0] == ',' {
|
|
|
|
- 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'
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ fi := state
|
|
|
|
+
|
|
|
|
+ head, _, tail := strings.partition(value, ",")
|
|
|
|
+
|
|
|
|
+ i := 0
|
|
|
|
+ prefix_loop: for ; i < len(head); i += 1 {
|
|
|
|
+ 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:]
|
|
|
|
- if field_name == "0" {
|
|
|
|
- if use_nul_termination != nil {
|
|
|
|
- use_nul_termination^ = true
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- switch r {
|
|
|
|
- case 's', 'q':
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fi.width, i, fi.width_set = _parse_int(head, i)
|
|
|
|
+ if i < len(head) && head[i] == '.' {
|
|
|
|
+ i += 1
|
|
|
|
+ prev_i := i
|
|
|
|
+ fi.prec, i, fi.prec_set = _parse_int(head, i)
|
|
|
|
+ if i == prev_i {
|
|
|
|
+ fi.prec = 0
|
|
|
|
+ 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) }
|