Compare commits

..

10 Commits

Author SHA1 Message Date
PedroEdiaz
7124aeb94e Add partial use of comments 2025-06-19 22:01:03 -06:00
PedroEdiaz
0f76ad666a Pass section 2025-06-19 21:57:18 -06:00
PedroEdiaz
774d1fd8eb Clean up 2025-06-19 01:42:08 -06:00
PedroEdiaz
65c6a901d2 preprocess -> section 2025-06-19 00:35:42 -06:00
PedroEdiaz
f9f4f5d34a Add decoding of map 2025-06-19 00:35:24 -06:00
PedroEdiaz
03292a15c8 Add testing for spec 2025-06-19 00:34:16 -06:00
PedroEdiaz
b707360c85 Merge remote-tracking branch 'refs/remotes/origin/mustacheless' into mustacheless 2025-06-18 16:34:17 -06:00
PedroEdiaz
6388d52499 Add mustache spec 2025-06-18 15:59:50 -06:00
PedroEdiaz
31f2b1c5c9 Add Sections as mustache for arrays 2025-06-18 01:35:19 -06:00
PedroEdiaz
e4e797fe15 Add: Recursivity in decode 2025-06-17 19:43:31 -06:00
5 changed files with 294 additions and 222 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "spec"]
path = spec
url = https://github.com/mustache/spec

View File

@@ -2,80 +2,154 @@ package mustache
import "base:runtime"
import "core:reflect"
import "core:strconv"
import "core:strings"
import "core:mem"
import "core:log"
decode :: proc( v: any, key: string ) -> string {
@private
_clean_html :: proc(s: string) -> string {
ret: strings.Builder
if v == nil do return ""
for c in s do switch c {
case '<':
strings.write_string(&ret, "&lt;")
case '>':
strings.write_string(&ret, "&gt;")
case '&':
strings.write_string(&ret, "&amp;")
case '"':
strings.write_string(&ret, "&quot;")
case '\'':
strings.write_string(&ret, "&apos;")
case:
strings.write_rune(&ret, c)
}
ti := runtime.type_info_base(type_info_of(v.id))
a := any{v.data, ti.id}
return strings.to_string(ret)
}
#partial switch info in ti.variant {
decode :: proc( v: any, key: string ) -> any {
ti := type_info_of(v.id)
variant := runtime.type_info_base(ti).variant
#partial switch i in variant {
case runtime.Type_Info_Enumerated_Array:
return decode(any{v.data, ti.id}, key)
case runtime.Type_Info_Union:
return decode(any{v.data, reflect.union_variant_typeid(v) }, key)
}
if key == "." {
return v
}
#partial switch i in variant {
case runtime.Type_Info_Struct:
return decode( reflect.struct_field_value_by_name(v, key), "." )
newkey, err := strings.split_after_n(key, ".", 2)
case runtime.Type_Info_String:
if key != "." do return ""
switch t in a {
case string:
return t
case cstring:
return string(t)
if err != nil {
return nil
}
case runtime.Type_Info_Integer:
if key != "." do return ""
defer delete(newkey)
buf: [40]byte
u := cast_any_int_to_u128(a)
newkey_0 := newkey[0]
newkey_1 := "."
return strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
case runtime.Type_Info_Enum:
return decode(any{v.data, info.base.id}, key)
if len(newkey) != 2 {
newkey_0 = newkey[0][:len(newkey[0])-1]
newkey_1 = newkey[1]
}
return decode( reflect.struct_field_value_by_name(v, newkey_0), newkey_1 )
case runtime.Type_Info_Map:
newkey, err := strings.split_after_n(key, ".", 2)
if err!= nil {
return nil
}
defer delete(newkey)
newkey_0 := newkey[0]
newkey_1 := "."
if len(newkey) == 2 {
newkey_0 = newkey[0][:len(newkey[0])-1]
newkey_1 = newkey[1]
}
m := (^mem.Raw_Map)(v.data)
ks, vs, _, _, _ := runtime.map_kvh_data_dynamic(m^, i.map_info)
for j in 0..<runtime.map_cap(m^) {
key_data := runtime.map_cell_index_dynamic(ks, i.map_info.ks, uintptr(j))
ky, ok := reflect.as_string(any{rawptr(key_data), i.key.id})
if !ok {
return nil
}
if ky == newkey_0 {
t := any{rawptr(runtime.map_cell_index_dynamic(vs, i.map_info.vs, uintptr(j))), i.value.id}
return decode(t, newkey_1 )
}
}
return nil
}
return nil
}
@private
mustache_section :: proc( r: ^strings.Reader, v, p: any, section_key: string, inv: bool = false ) -> string {
tmp := mustache(r, v, section_key)
defer delete(tmp)
tmp2 := mustache(tmp, p, section_key)
if inv {
return tmp2
}
delete(tmp2)
return ""
}
section :: proc( r: ^strings.Reader, v: any, section_key: string, inv: bool = false ) -> string {
save := r.i
@(private)
cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 {
u: u128 = 0
t := reflect.any_base(decode(v, section_key))
ti := type_info_of(t.id)
switch i in any_int_value {
case i8: u = u128(i)
case i16: u = u128(i)
case i32: u = u128(i)
case i64: u = u128(i)
case i128: u = u128(i)
case int: u = u128(i)
case u8: u = u128(i)
case u16: u = u128(i)
case u32: u = u128(i)
case u64: u = u128(i)
case u128: u = u128(i)
case uint: u = u128(i)
case uintptr: u = u128(i)
#partial switch i in runtime.type_info_base(ti).variant {
case runtime.Type_Info_Slice:
ret : string= ""
for i in 0..<reflect.length(t) {
elem :=reflect.index(t, i);
strings.reader_seek(r, save, .Start)
case i16le: u = u128(i)
case i32le: u = u128(i)
case i64le: u = u128(i)
case u16le: u = u128(i)
case u32le: u = u128(i)
case u64le: u = u128(i)
case u128le: u = u128(i)
case i16be: u = u128(i)
case i32be: u = u128(i)
case i64be: u = u128(i)
case u16be: u = u128(i)
case u32be: u = u128(i)
case u64be: u = u128(i)
case u128be: u = u128(i)
tmp := mustache_section(r, elem, v, section_key, !inv)
defer delete(tmp)
ret = strings.concatenate({ret, tmp})
}
return ret
case runtime.Type_Info_Boolean:
b, _ := reflect.as_bool(t)
return mustache_section(r, t, v, section_key, b~inv)
}
return u;
if t == nil {
return mustache_section(r, t, v, section_key, inv)
}
return mustache_section(r, t, v, section_key, !inv)
}

View File

@@ -1,8 +1,8 @@
package mustache
import "core:fmt"
import "core:strings"
@private
state :: enum {
writing,
reading_key,
@@ -10,36 +10,40 @@ state :: enum {
close_bracket,
}
/*
Input:
- fmt: A string with placeholders in the form `{{key}}`.
- dict: A map from string to string, where each key corresponds to a placeholder in the format string.
mustache :: proc{mustache_reader, mustache_string}
Returns:
A new string with all placeholders replaced by their corresponding values from
the dictionary. If a key is missing in the dictionary, the placeholder is
replaces with an empty string.
*/
mustache :: proc(fmt: string, v: any ) -> string {
mustache_string :: proc(fmt: string, data: any , section_key: string = "") -> string {
r : strings.Reader
strings.reader_init(&r, fmt)
return mustache(&r, data, section_key)
}
mustache_reader :: proc(r: ^strings.Reader, data: any, section_key: string = "" ) -> string {
/*
template works as a state machine, it manipulates `b` (returned string)
and `key` (placeholder string), according to the states. No error
returns are needed because if `key` is not close it writes it as
it's not a placeholder
This is the main parser for mustache templates, it's a recursive decent parser, that works as a state machine,
it manipulates the processed template with data, `ret`, and the key to element on data `key`, according to the states.
This approach let us process the data as fast as posible.
*/
b, key: strings.Builder
ret, key: strings.Builder
defer strings.builder_destroy(&key)
s:= state.writing
for c in fmt do switch c {
for {
c, _, err := strings.reader_read_rune(r);
if err != nil {
break
}
switch c {
case '{':
switch s {
case .open_bracket:
s=.reading_key
case .close_bracket:
strings.write_string(&b, "}{" )
strings.write_string(&ret, "}{" )
s=.writing
case .writing:
s=.open_bracket
@@ -49,14 +53,48 @@ mustache :: proc(fmt: string, v: any ) -> string {
case '}':
switch s {
case .open_bracket:
strings.write_string(&b, "{}" )
strings.write_string(&ret, "{}" )
s=.writing
case .close_bracket:
strings.write_string(&b, decode(v, strings.to_string(key) ))
// Work with key
skey := strings.to_string(key)
strings.builder_reset(&key)
s=.writing
if len(skey) == 0 {
break
}
switch skey[0] {
case '/':
if skey[1:] == section_key {
return strings.to_string(ret)
}
case '#':
strings.write_string(&ret, section(r, data, skey[1:]) )
case '^':
strings.write_string(&ret, section(r, data, skey[1:], true) )
case '&':
strings.write_string(&ret, fmt.tprintf("%v",decode(data, skey[1:])) )
case '!':
case:
dec := decode(data, skey)
if dec == nil {
// If not decoded write as key
strings.write_string(&ret, "{{" )
strings.write_string(&ret, skey )
strings.write_string(&ret, "}}" )
break
}
clean := _clean_html(fmt.tprintf("%v", dec))
defer delete(clean)
strings.write_string(&ret, clean)
}
case .writing:
strings.write_rune(&b, '}' )
strings.write_rune(&ret, '}' )
s=.writing
case .reading_key:
s=.close_bracket
@@ -64,35 +102,36 @@ mustache :: proc(fmt: string, v: any ) -> string {
case:
switch s {
case .open_bracket:
strings.write_rune(&b, '{' )
strings.write_rune(&b, c )
strings.write_rune(&ret, '{' )
strings.write_rune(&ret, c )
s=.writing
case .close_bracket:
strings.write_rune(&key, '}' )
strings.write_rune(&key, c )
s=.reading_key
case .writing:
strings.write_rune(&b, c )
strings.write_rune(&ret, c )
s=.writing
case .reading_key:
strings.write_rune(&key, c )
}
}
}
switch s {
case .open_bracket:
strings.write_rune(&b, '{' )
strings.write_rune(&ret, '{' )
case .reading_key:
strings.write_string(&b,"{{")
strings.write_string(&b,strings.to_string(key))
strings.write_string(&ret,"{{")
strings.write_string(&ret,strings.to_string(key))
case .close_bracket:
strings.write_string(&b,"{{")
strings.write_string(&b,strings.to_string(key))
strings.write_rune(&b, '}' )
strings.write_string(&ret,"{{")
strings.write_string(&ret,strings.to_string(key))
strings.write_rune(&ret, '}' )
case .writing:
}
return strings.to_string(b)
return strings.to_string(ret)
}
/*

1
spec Submodule

Submodule spec added at 97c05b0652

View File

@@ -2,106 +2,61 @@
#+private
package mustache
import "core:os"
import "core:log"
import "core:testing"
import "core:encoding/json"
data_struct :: union {
map[string]data_struct,
[]data_struct,
string,
bool,
}
test_struct :: struct {
overview: string,
tests: []struct{
name, desc, template, expected: string,
data: data_struct
}
}
@(test)
test1 :: proc(t: ^testing.T){
fmt := "{"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test2 :: proc(t: ^testing.T){
fmt := "}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test3 :: proc(t: ^testing.T){
fmt := "{{"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test4 :: proc(t: ^testing.T){
fmt := "{{}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test5 :: proc(t: ^testing.T){
fmt := "{{}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test6 :: proc(t: ^testing.T){
fmt := "{{foo}}"
spec_test :: proc(t: ^testing.T){
test_files := []string {
"./spec/specs/sections.json",
"./spec/specs/interpolation.json",
"./spec/specs/inverted.json",
"./spec/specs/comments.json",
/*
"./spec/specs/partials.json",
"./spec/specs/delimiters.json",
*/
}
dict : struct { foo: string } = {"var"}
for i in test_files {
data, err:=os.read_entire_file_from_filename_or_err(i)
defer delete(data)
tmp := mustache(fmt, dict)
defer delete(tmp)
testing.expect(t, tmp=="var", tmp)
}
@(test)
test7 :: proc(t: ^testing.T){
fmt := "{{{}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test8 :: proc(t: ^testing.T){
fmt := "{{}}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="}", tmp)
}
@(test)
test9 :: proc(t: ^testing.T){
fmt := "{{{}}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="}", tmp)
}
@(test)
test10 :: proc(t: ^testing.T){
fmt := "{{} }}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test11 :: proc(t: ^testing.T){
fmt := "{{{} }}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test12 :: proc(t: ^testing.T){
fmt := " {{{} }}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==" ", tmp)
}
@(test)
test13 :: proc(t: ^testing.T){
fmt := "{{{} }} "
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==" ", tmp)
}
@(test)
test14 :: proc(t: ^testing.T){
fmt := "{{{}}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="}", tmp)
if err != nil {
testing.expectf(t, false, "%v", err)
}
test: test_struct
json.unmarshal(data, &test, allocator=context.temp_allocator)
failed := 0
for j in test.tests {
ret := mustache(j.template, j.data)
defer delete(ret)
if ret!=j.expected {
log.warnf( "[%s:%s]: %s", i, j.name, j.desc )
failed += 1
}
}
log.infof( "[%s] Passed: (%d/%d)", i, len(test.tests)-failed, len(test.tests) )
}
}