mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
refactor: Rewrote the mustache package from scratch.
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package mustache
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:reflect"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
// effective unwraps union variants and strips Named/Distinct layers,
|
||||
// returning the "peeled" any value and its base type info.
|
||||
effective :: proc(a: any) -> (val: any, info: ^runtime.Type_Info) {
|
||||
val = a
|
||||
info = nil
|
||||
if val == nil do return
|
||||
|
||||
ti := type_info_of(val.id)
|
||||
if ti == nil do return
|
||||
|
||||
base := runtime.type_info_base(ti)
|
||||
|
||||
if _, ok := base.variant.(runtime.Type_Info_Union); ok {
|
||||
variant := reflect.get_union_variant(val)
|
||||
if variant == nil do return {}, nil
|
||||
return effective(variant)
|
||||
}
|
||||
|
||||
info = base
|
||||
return
|
||||
}
|
||||
|
||||
// lookup_in resolves a single key in a container (struct or map).
|
||||
// Returns found=false if the key doesn't exist or the container is not a struct/map.
|
||||
lookup_in :: proc(container: any, key: string) -> (result: any, found: bool) {
|
||||
if container == nil do return nil, false
|
||||
|
||||
val, info := effective(container)
|
||||
if info == nil do return nil, false
|
||||
|
||||
#partial switch v in info.variant {
|
||||
case runtime.Type_Info_Struct:
|
||||
result = reflect.struct_field_value_by_name(val, key, allow_using = true)
|
||||
found = result != nil
|
||||
return
|
||||
|
||||
case runtime.Type_Info_Map:
|
||||
mi := v
|
||||
rm_ptr := (^runtime.Raw_Map)(val.data)
|
||||
if rm_ptr.len == 0 do return nil, false
|
||||
|
||||
k := key
|
||||
seed := runtime.map_seed(rm_ptr^)
|
||||
h := mi.map_info.key_hasher(&k, seed)
|
||||
value_ptr := runtime.__dynamic_map_get(rm_ptr, mi.map_info, h, &k)
|
||||
if value_ptr == nil do return nil, false
|
||||
|
||||
result = any{value_ptr, mi.value.id}
|
||||
found = true
|
||||
return
|
||||
|
||||
case:
|
||||
return nil, false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// resolve_name resolves a (possibly dotted) name from the context stack.
|
||||
// The first segment walks the stack top-to-bottom; remaining segments
|
||||
// resolve against the prior result only.
|
||||
resolve_name :: proc(name: string, ctx: []any) -> any {
|
||||
if name == "." {
|
||||
if len(ctx) > 0 do return ctx[len(ctx) - 1]
|
||||
return nil
|
||||
}
|
||||
|
||||
parts: [16]string
|
||||
part_count := 0
|
||||
start := 0
|
||||
for i in 0 ..< len(name) {
|
||||
if name[i] == '.' {
|
||||
if part_count < len(parts) {
|
||||
parts[part_count] = name[start:i]
|
||||
part_count += 1
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if part_count < len(parts) {
|
||||
parts[part_count] = name[start:]
|
||||
part_count += 1
|
||||
}
|
||||
|
||||
if part_count == 0 do return nil
|
||||
dot_parts := parts[:part_count]
|
||||
|
||||
result: any = nil
|
||||
found := false
|
||||
for i := len(ctx) - 1; i >= 0; i -= 1 {
|
||||
result, found = lookup_in(ctx[i], dot_parts[0])
|
||||
if found do break
|
||||
}
|
||||
|
||||
if !found do return nil
|
||||
if len(dot_parts) == 1 do return result
|
||||
|
||||
for i := 1; i < len(dot_parts); i += 1 {
|
||||
result, found = lookup_in(result, dot_parts[i])
|
||||
if !found do return nil
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// is_truthy checks mustache truthiness.
|
||||
is_truthy :: proc(a: any) -> bool {
|
||||
if a == nil do return false
|
||||
|
||||
val, info := effective(a)
|
||||
if info == nil do return false
|
||||
if reflect.is_nil(val) do return false
|
||||
|
||||
#partial switch _ in info.variant {
|
||||
case runtime.Type_Info_Slice, runtime.Type_Info_Dynamic_Array, runtime.Type_Info_Map:
|
||||
return reflect.length(val) > 0
|
||||
case:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// list_info returns element type info, count, and data pointer for a list value.
|
||||
// Returns elem_info=nil if the value is not a list.
|
||||
list_info :: proc(a: any) -> (elem_info: ^runtime.Type_Info, count: int, data: rawptr) {
|
||||
val, info := effective(a)
|
||||
if info == nil do return nil, 0, nil
|
||||
|
||||
#partial switch v in info.variant {
|
||||
case runtime.Type_Info_Slice:
|
||||
raw := (^runtime.Raw_Slice)(val.data)^
|
||||
return v.elem, raw.len, raw.data
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
raw := (^runtime.Raw_Dynamic_Array)(val.data)^
|
||||
return v.elem, raw.len, raw.data
|
||||
case runtime.Type_Info_Array:
|
||||
return v.elem, v.count, val.data
|
||||
case:
|
||||
return nil, 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// any_to_string converts a scalar value to a string using the temp allocator.
|
||||
any_to_string :: proc(a: any) -> string {
|
||||
if a == nil do return ""
|
||||
val, _ := effective(a)
|
||||
|
||||
switch v in val {
|
||||
case string:
|
||||
return v
|
||||
case bool:
|
||||
return "true" if v else "false"
|
||||
case i64:
|
||||
return fmt.tprintf("%d", v)
|
||||
case f64:
|
||||
// return fmt.tprintf("%.3f", v)
|
||||
return format_f64(v)
|
||||
case int:
|
||||
return fmt.tprintf("%d", v)
|
||||
case:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// format_f64 produces the shortest string that round-trips to the same f64.
|
||||
// Works around Odin's strconv not implementing shortest representation.
|
||||
format_f64 :: proc(v: f64) -> string {
|
||||
buf: [64]byte
|
||||
for prec in 1 ..= 17 {
|
||||
s := strconv.write_float(buf[:], v, 'g', prec, 64)
|
||||
if len(s) > 0 && s[0] == '+' do s = s[1:]
|
||||
parsed, ok := strconv.parse_f64(s)
|
||||
if ok && parsed == v {
|
||||
return strings.clone(s, context.temp_allocator)
|
||||
}
|
||||
}
|
||||
s := strconv.write_float(buf[:], v, 'g', -1, 64)
|
||||
if len(s) > 0 && s[0] == '+' do s = s[1:]
|
||||
return strings.clone(s, context.temp_allocator)
|
||||
}
|
||||
|
||||
// write_value stringifies a value and writes it to the builder,
|
||||
// optionally HTML-escaped.
|
||||
write_value :: proc(b: ^strings.Builder, a: any, escape: bool) {
|
||||
s := any_to_string(a)
|
||||
if len(s) == 0 do return
|
||||
|
||||
if !escape {
|
||||
strings.write_string(b, s)
|
||||
return
|
||||
}
|
||||
|
||||
start := 0
|
||||
for i in 0 ..< len(s) {
|
||||
switch s[i] {
|
||||
case '&', '<', '>', '"':
|
||||
if i > start do strings.write_string(b, s[start:i])
|
||||
switch s[i] {
|
||||
case '&':
|
||||
strings.write_string(b, "&")
|
||||
case '<':
|
||||
strings.write_string(b, "<")
|
||||
case '>':
|
||||
strings.write_string(b, ">")
|
||||
case '"':
|
||||
strings.write_string(b, """)
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if start < len(s) {
|
||||
strings.write_string(b, s[start:])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user