odin-jsonschema

Implementation of JSON schema for Odin
Log | Files | Refs | LICENSE

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 }