Browse Source

Improve `fmt` parsing of struct field tags

gingerBill 1 year ago
parent
commit
f600562ca5
1 changed files with 89 additions and 41 deletions
  1. 89 41
      core/fmt/fmt.odin

+ 89 - 41
core/fmt/fmt.odin

@@ -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) }