name.odin (3310B)
1 package jschema 2 3 import "core:strings" 4 import "core:unicode" 5 6 @(private) 7 RESERVED_WORDS :: []string { 8 "any", "asm", "auto_cast", "bit_field", "bit_set", "bool", "break", "byte", 9 "case", "cast", "context", "continue", "cstring", "defer", "distinct", "do", 10 "dynamic", "else", "enum", "f16", "f32", "f64", "fallthrough", "false", "for", 11 "foreign", "i128", "i16", "i32", "i64", "i8", "if", "import", "in", "int", 12 "map", "matrix", "nil", "not_in", "or_break", "or_continue", "or_else", 13 "or_return", "package", "proc", "quaternion", "rawptr", "return", "rune", 14 "string", "struct", "switch", "transmute", "true", "typeid", "u128", "u16", 15 "u32", "u64", "u8", "uint", "uintptr", "union", "using", "when", "where", 16 } 17 18 @(private) 19 is_reserved :: proc(s: string) -> bool { 20 for word in RESERVED_WORDS { 21 if s == word { 22 return true 23 } 24 } 25 return false 26 } 27 28 // A string usable verbatim as an Odin identifier (e.g. an enum variant name). 29 @(private) 30 is_valid_ident :: proc(s: string) -> bool { 31 if len(s) == 0 || is_reserved(s) { 32 return false 33 } 34 for r, i in s { 35 if i == 0 { 36 if !unicode.is_letter(r) && r != '_' { 37 return false 38 } 39 } else if !unicode.is_letter(r) && !unicode.is_digit(r) && r != '_' { 40 return false 41 } 42 } 43 return s != "_" 44 } 45 46 // Splits on non-alphanumeric runes and lower-to-upper camelCase boundaries. 47 @(private) 48 split_words :: proc(s: string, allocator := context.allocator) -> []string { 49 words := make([dynamic]string, allocator) 50 start := -1 51 prev_lower := false 52 for r, i in s { 53 alnum := unicode.is_letter(r) || unicode.is_digit(r) 54 if !alnum { 55 if start >= 0 { 56 append(&words, s[start:i]) 57 start = -1 58 } 59 prev_lower = false 60 continue 61 } 62 if unicode.is_upper(r) && prev_lower && start >= 0 { 63 append(&words, s[start:i]) 64 start = i 65 } 66 if start < 0 { 67 start = i 68 } 69 prev_lower = unicode.is_lower(r) || unicode.is_digit(r) 70 } 71 if start >= 0 { 72 append(&words, s[start:]) 73 } 74 return words[:] 75 } 76 77 // Converts a JSON name to an Odin field identifier: "jsonSchemaDialect" -> 78 // "json_schema_dialect", "$self" -> "self", "type" stays "type". 79 @(private) 80 field_name :: proc(s: string, allocator := context.allocator) -> string { 81 words := split_words(s, allocator) 82 if len(words) == 0 { 83 return "field" 84 } 85 b: strings.Builder 86 strings.builder_init(&b, allocator) 87 for word, i in words { 88 if i > 0 { 89 strings.write_byte(&b, '_') 90 } 91 for r in word { 92 strings.write_rune(&b, unicode.to_lower(r)) 93 } 94 } 95 out := strings.to_string(b) 96 if out[0] >= '0' && out[0] <= '9' { 97 out = strings.concatenate({"_", out}, allocator) 98 } 99 if is_reserved(out) { 100 out = strings.concatenate({out, "_"}, allocator) 101 } 102 return out 103 } 104 105 // Converts a JSON name to an Odin type identifier: "path-item" -> "Path_Item", 106 // "securityScheme" -> "Security_Scheme". 107 @(private) 108 type_name :: proc(s: string, allocator := context.allocator) -> string { 109 words := split_words(s, allocator) 110 if len(words) == 0 { 111 return "Value" 112 } 113 b: strings.Builder 114 strings.builder_init(&b, allocator) 115 for word, i in words { 116 if i > 0 { 117 strings.write_byte(&b, '_') 118 } 119 for r, j in word { 120 strings.write_rune(&b, unicode.to_upper(r) if j == 0 else r) 121 } 122 } 123 out := strings.to_string(b) 124 if out[0] >= '0' && out[0] <= '9' { 125 out = strings.concatenate({"T", out}, allocator) 126 } 127 return out 128 }