refactor: Changed Render_Error from Union to struct.

Also renamed it to `Error`.
This commit is contained in:
Spencer Brower
2026-07-21 15:18:42 -04:00
parent f04532229b
commit cfd01a02d0
7 changed files with 170 additions and 186 deletions
+5 -14
View File
@@ -307,26 +307,17 @@ write_gutter :: proc(sb: ^strings.Builder, width: int, faint: string, reset: str
strings.write_byte(sb, '\n') strings.write_byte(sb, '\n')
} }
// format_render_error dispatches on Render_Error variant and produces a // format_render_error produces a diagnostic for an Error value using the
// diagnostic for it. Returns "" for nil errors. // template's path and source for context. Returns "" for nil errors.
format_render_error :: proc(err: Render_Error, tmpl: Template, colorize: bool = false) -> string { format_render_error :: proc(err: Error, tmpl: Template, colorize: bool = false) -> string {
if err == nil { if err == nil {
return "" return ""
} }
switch e in err {
case Syntax_Error:
path := tmpl.path path := tmpl.path
if path == "" { if path == "" {
path = "<input>" path = "<input>"
} }
return format_error(path, tmpl.source, e.pos, e.msg, colorize = colorize) b := body(err)
case Data_Error: return format_error(path, tmpl.source, b.pos, b.msg, colorize = colorize)
path := tmpl.path
if path == "" {
path = "<input>"
}
return format_error(path, tmpl.source, e.pos, e.msg, colorize = colorize)
}
return ""
} }
+22 -36
View File
@@ -425,13 +425,11 @@ test_format_render_error_dispatch :: proc(t: ^testing.T) {
return return
} }
#partial switch e in parse_err { b := body(parse_err)
case Syntax_Error: out := format_error("test.html", src, b.pos, b.msg, colorize = false)
out := format_error("test.html", src, e.pos, e.msg, colorize = false)
testing.expect(t, strings.contains(out, "unclosed section"), out) testing.expect(t, strings.contains(out, "unclosed section"), out)
testing.expect(t, strings.contains(out, "test.html:"), out) testing.expect(t, strings.contains(out, "test.html:"), out)
} }
}
@(test) @(test)
test_diagnostic_for_pipe_error :: proc(t: ^testing.T) { test_diagnostic_for_pipe_error :: proc(t: ^testing.T) {
@@ -469,20 +467,18 @@ test_parse_error_expected_got_keeps_double_braces :: proc(t: ^testing.T) {
if err == nil { if err == nil {
return return
} }
#partial switch e in err { b := body(err)
case Syntax_Error:
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{/content}}"), strings.contains(b.msg, "{{/content}}"),
fmt.tprintf("msg should contain literal {{/content}}, got %q", e.msg), fmt.tprintf("msg should contain literal {{/content}}, got %q", b.msg),
) )
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{/cotent}}"), strings.contains(b.msg, "{{/cotent}}"),
fmt.tprintf("msg should contain literal {{/cotent}}, got %q", e.msg), fmt.tprintf("msg should contain literal {{/cotent}}, got %q", b.msg),
) )
} }
}
@(test) @(test)
test_parse_error_unclosed_section_keeps_double_braces :: proc(t: ^testing.T) { test_parse_error_unclosed_section_keeps_double_braces :: proc(t: ^testing.T) {
@@ -492,15 +488,13 @@ test_parse_error_unclosed_section_keeps_double_braces :: proc(t: ^testing.T) {
if err == nil { if err == nil {
return return
} }
#partial switch e in err { b := body(err)
case Syntax_Error:
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{#content}}"), strings.contains(b.msg, "{{#content}}"),
fmt.tprintf("msg should contain literal {{#content}}, got %q", e.msg), fmt.tprintf("msg should contain literal {{#content}}, got %q", b.msg),
) )
} }
}
@(test) @(test)
test_parse_error_unexpected_close_keeps_double_braces :: proc(t: ^testing.T) { test_parse_error_unexpected_close_keeps_double_braces :: proc(t: ^testing.T) {
@@ -510,15 +504,13 @@ test_parse_error_unexpected_close_keeps_double_braces :: proc(t: ^testing.T) {
if err == nil { if err == nil {
return return
} }
#partial switch e in err { b := body(err)
case Syntax_Error:
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{/content}}"), strings.contains(b.msg, "{{/content}}"),
fmt.tprintf("msg should contain literal {{/content}}, got %q", e.msg), fmt.tprintf("msg should contain literal {{/content}}, got %q", b.msg),
) )
} }
}
@(test) @(test)
test_parse_error_pipe_in_close_tag_keeps_double_braces :: proc(t: ^testing.T) { test_parse_error_pipe_in_close_tag_keeps_double_braces :: proc(t: ^testing.T) {
@@ -528,15 +520,13 @@ test_parse_error_pipe_in_close_tag_keeps_double_braces :: proc(t: ^testing.T) {
if err == nil { if err == nil {
return return
} }
#partial switch e in err { b := body(err)
case Syntax_Error:
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{/"), strings.contains(b.msg, "{{/"),
fmt.tprintf("msg should contain literal '{{/', got %q", e.msg), fmt.tprintf("msg should contain literal '{{/', got %q", b.msg),
) )
} }
}
@(test) @(test)
test_parse_error_pipe_parse_in_section_keeps_double_braces :: proc(t: ^testing.T) { test_parse_error_pipe_parse_in_section_keeps_double_braces :: proc(t: ^testing.T) {
@@ -546,15 +536,13 @@ test_parse_error_pipe_parse_in_section_keeps_double_braces :: proc(t: ^testing.T
if err == nil { if err == nil {
return return
} }
#partial switch e in err { b := body(err)
case Syntax_Error:
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{#"), strings.contains(b.msg, "{{#"),
fmt.tprintf("msg should contain literal '{{#', got %q", e.msg), fmt.tprintf("msg should contain literal '{{#', got %q", b.msg),
) )
} }
}
@(test) @(test)
test_parse_error_pipe_parse_in_inverted_keeps_double_braces :: proc(t: ^testing.T) { test_parse_error_pipe_parse_in_inverted_keeps_double_braces :: proc(t: ^testing.T) {
@@ -564,13 +552,11 @@ test_parse_error_pipe_parse_in_inverted_keeps_double_braces :: proc(t: ^testing.
if err == nil { if err == nil {
return return
} }
#partial switch e in err { b := body(err)
case Syntax_Error:
testing.expect( testing.expect(
t, t,
strings.contains(e.msg, "{{^"), strings.contains(b.msg, "{{^"),
fmt.tprintf("msg should contain literal '{{^', got %q", e.msg), fmt.tprintf("msg should contain literal '{{^', got %q", b.msg),
) )
} }
}
+37 -22
View File
@@ -8,20 +8,27 @@ import "core:strings"
// Error types // Error types
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// TODO: I don't think this is the best way to do our errors. Error_Kind :: enum {
Syntax_Error :: struct { Syntax, // parse-time: malformed template
msg: string, Data, // render-time: template fine, data wrong (e.g. filter misuse)
pos: int,
} }
Data_Error :: struct { Error_Body :: struct {
msg: string, msg: string,
pos: int, pos: int,
kind: Error_Kind,
} }
Render_Error :: union { // Error is nil when no error occurred.
Syntax_Error, Error :: union { Error_Body }
Data_Error,
// body unwraps the Error_Body from a non-nil Error.
// Precondition: err != nil.
body :: proc(err: Error) -> Error_Body {
switch e in err {
case Error_Body: return e
case: return {}
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -101,7 +108,7 @@ parse :: proc(
tokens_allocator := context.temp_allocator, tokens_allocator := context.temp_allocator,
) -> ( ) -> (
tmpl: Template, tmpl: Template,
err: Render_Error, err: Error,
) { ) {
tokens, terr := tokenize(source, tokens_allocator) tokens, terr := tokenize(source, tokens_allocator)
if terr != nil { if terr != nil {
@@ -126,7 +133,7 @@ render :: proc(
allocator := context.allocator, allocator := context.allocator,
) -> ( ) -> (
result: string, result: string,
err: Render_Error, err: Error,
) { ) {
builder: strings.Builder builder: strings.Builder
strings.builder_init(&builder, allocator) strings.builder_init(&builder, allocator)
@@ -158,7 +165,7 @@ parse_tokens :: proc(
allocator := context.allocator, allocator := context.allocator,
) -> ( ) -> (
nodes: [dynamic]Node, nodes: [dynamic]Node,
err: Render_Error, err: Error,
) { ) {
nodes = make([dynamic]Node, 0, len(tokens), allocator) nodes = make([dynamic]Node, 0, len(tokens), allocator)
pos := 0 pos := 0
@@ -174,7 +181,7 @@ parse_section :: proc(
source: string, source: string,
allocator := context.allocator, allocator := context.allocator,
open_pos: int = 0, open_pos: int = 0,
) -> Render_Error { ) -> Error {
for pos^ < len(tokens) { for pos^ < len(tokens) {
tok := tokens[pos^] tok := tokens[pos^]
@@ -188,9 +195,10 @@ parse_section :: proc(
append(nodes, Node{kind = .Variable, first_child = -1}) append(nodes, Node{kind = .Variable, first_child = -1})
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos) pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
if perr != nil { if perr != nil {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("pipe parse error in '{{{{%s}}}}': %v", tok.value, perr), msg = fmt.tprintf("pipe parse error in '{{{{%s}}}}': %v", tok.value, perr),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
} }
nodes[idx].key = pipe_key nodes[idx].key = pipe_key
@@ -201,9 +209,10 @@ parse_section :: proc(
append(nodes, Node{kind = .Unescaped, first_child = -1}) append(nodes, Node{kind = .Unescaped, first_child = -1})
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos) pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
if perr != nil { if perr != nil {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("pipe parse error in '{{{{&%s}}}}': %v", tok.value, perr), msg = fmt.tprintf("pipe parse error in '{{{{&%s}}}}': %v", tok.value, perr),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
} }
nodes[idx].key = pipe_key nodes[idx].key = pipe_key
@@ -220,9 +229,10 @@ parse_section :: proc(
append(nodes, Node{kind = .Section, first_child = -1, pos = tok.pos}) append(nodes, Node{kind = .Section, first_child = -1, pos = tok.pos})
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos) pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
if perr != nil { if perr != nil {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("pipe parse error in '{{{{#%s}}}}': %v", tok.value, perr), msg = fmt.tprintf("pipe parse error in '{{{{#%s}}}}': %v", tok.value, perr),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
} }
nodes[idx].key = pipe_key nodes[idx].key = pipe_key
@@ -241,9 +251,10 @@ parse_section :: proc(
append(nodes, Node{kind = .Inverted, first_child = -1, pos = tok.pos}) append(nodes, Node{kind = .Inverted, first_child = -1, pos = tok.pos})
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos) pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos)
if perr != nil { if perr != nil {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("pipe parse error in '{{{{^%s}}}}': %v", tok.value, perr), msg = fmt.tprintf("pipe parse error in '{{{{^%s}}}}': %v", tok.value, perr),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
} }
nodes[idx].key = pipe_key nodes[idx].key = pipe_key
@@ -256,12 +267,13 @@ parse_section :: proc(
case .Section_Close: case .Section_Close:
if strings.contains(tok.value, "|") { if strings.contains(tok.value, "|") {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf( msg = fmt.tprintf(
"pipe expression not allowed in close tag '{{{{/%s}}}}' — use the bare key", "pipe expression not allowed in close tag '{{{{/%s}}}}' — use the bare key",
tok.value, tok.value,
), ),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
} }
if end_tag != "" && tok.value == end_tag { if end_tag != "" && tok.value == end_tag {
@@ -269,14 +281,16 @@ parse_section :: proc(
return nil return nil
} }
if end_tag == "" { if end_tag == "" {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("unexpected {{{{/%s}}}}", tok.value), msg = fmt.tprintf("unexpected {{{{/%s}}}}", tok.value),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
} }
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("expected {{{{/%s}}}}, got {{{{/%s}}}}", end_tag, tok.value), msg = fmt.tprintf("expected {{{{/%s}}}}, got {{{{/%s}}}}", end_tag, tok.value),
pos = tok.pos, pos = tok.pos,
kind = .Syntax,
} }
case .Partial: case .Partial:
@@ -330,9 +344,10 @@ parse_section :: proc(
} }
if end_tag != "" { if end_tag != "" {
return Syntax_Error { return Error_Body {
msg = fmt.tprintf("unclosed section '{{{{#%s}}}}'", end_tag), msg = fmt.tprintf("unclosed section '{{{{#%s}}}}'", end_tag),
pos = open_pos, pos = open_pos,
kind = .Syntax,
} }
} }
return nil return nil
@@ -495,7 +510,7 @@ render_template :: proc(
b: ^strings.Builder, b: ^strings.Builder,
blocks: map[string]Block_Override, blocks: map[string]Block_Override,
indent: string, indent: string,
) -> Render_Error { ) -> Error {
if len(indent) > 0 && len(pt.source) > 0 { if len(indent) > 0 && len(pt.source) > 0 {
// Per Mustache spec: the partial's source is indented before rendering, // Per Mustache spec: the partial's source is indented before rendering,
// not its output. This is necessary so that data-injected newlines // not its output. This is necessary so that data-injected newlines
@@ -525,7 +540,7 @@ render_nodes :: proc(
partials: map[string]Template, partials: map[string]Template,
b: ^strings.Builder, b: ^strings.Builder,
blocks: map[string]Block_Override = nil, blocks: map[string]Block_Override = nil,
) -> Render_Error { ) -> Error {
i := 0 i := 0
for i < len(nodes) { for i < len(nodes) {
node := nodes[i] node := nodes[i]
+38 -19
View File
@@ -30,7 +30,7 @@ parse_pipeline :: proc(
pos: int, pos: int,
) -> ( ) -> (
key: string, key: string,
err: Render_Error, err: Error,
) { ) {
if !strings.contains(content, "|") { if !strings.contains(content, "|") {
key = strings.trim_space(content) key = strings.trim_space(content)
@@ -41,19 +41,20 @@ parse_pipeline :: proc(
filter_count := len(segments) - 1 filter_count := len(segments) - 1
if filter_count > MAX_PIPES { if filter_count > MAX_PIPES {
return "", Syntax_Error { return "", Error_Body {
msg = fmt.tprintf( msg = fmt.tprintf(
"pipe expression has %d filters, max is %d", "pipe expression has %d filters, max is %d",
filter_count, filter_count,
MAX_PIPES, MAX_PIPES,
), ),
pos = pos, pos = pos,
kind = .Syntax,
} }
} }
key = strings.trim_space(segments[0]) key = strings.trim_space(segments[0])
if len(key) == 0 { if len(key) == 0 {
return "", Syntax_Error{msg = "pipe expression missing key", pos = pos} return "", Error_Body{msg = "pipe expression missing key", pos = pos, kind = .Syntax}
} }
if filter_count == 0 { if filter_count == 0 {
@@ -63,17 +64,17 @@ parse_pipeline :: proc(
for i in 0 ..< filter_count { for i in 0 ..< filter_count {
seg := strings.trim_space(segments[i + 1]) seg := strings.trim_space(segments[i + 1])
if len(seg) == 0 { if len(seg) == 0 {
return "", Syntax_Error{msg = "empty filter", pos = pos} return "", Error_Body{msg = "empty filter", pos = pos, kind = .Syntax}
} }
tokens := strings.fields(seg) tokens := strings.fields(seg)
if len(tokens) == 0 { if len(tokens) == 0 {
return "", Syntax_Error{msg = "filter missing op name", pos = pos} return "", Error_Body{msg = "filter missing op name", pos = pos, kind = .Syntax}
} }
arg_count := len(tokens) - 1 arg_count := len(tokens) - 1
if arg_count > MAX_PIPE_ARGS { if arg_count > MAX_PIPE_ARGS {
return "", Syntax_Error { return "", Error_Body {
msg = fmt.tprintf( msg = fmt.tprintf(
"filter '%s' has %d args, max is %d", "filter '%s' has %d args, max is %d",
tokens[0], tokens[0],
@@ -81,6 +82,7 @@ parse_pipeline :: proc(
MAX_PIPE_ARGS, MAX_PIPE_ARGS,
), ),
pos = pos, pos = pos,
kind = .Syntax,
} }
} }
@@ -97,7 +99,7 @@ parse_pipeline :: proc(
return key, nil return key, nil
} }
apply_pipeline :: proc(value: any, filters: []Pipe_Filter, pos: int) -> (any, Render_Error) { apply_pipeline :: proc(value: any, filters: []Pipe_Filter, pos: int) -> (any, Error) {
current := value current := value
for &filter in filters { for &filter in filters {
result, err := apply_filter(current, &filter, pos) result, err := apply_filter(current, &filter, pos)
@@ -109,28 +111,33 @@ apply_pipeline :: proc(value: any, filters: []Pipe_Filter, pos: int) -> (any, Re
return current, nil return current, nil
} }
apply_filter :: proc(value: any, filter: ^Pipe_Filter, pos: int) -> (any, Render_Error) { apply_filter :: proc(value: any, filter: ^Pipe_Filter, pos: int) -> (any, Error) {
switch filter.op { switch filter.op {
case "group_by": case "group_by":
return apply_group_by(value, filter.args[:], pos) return apply_group_by(value, filter.args[:], pos)
case "format": case "format":
str, ok := reflect.as_string(value) str, ok := reflect.as_string(value)
if !ok { if !ok {
return value, Data_Error{msg = "format may only be used on dates", pos = pos} return value, Error_Body{
msg = "format may only be used on dates",
pos = pos,
kind = .Data,
}
} else { } else {
return apply_format(str, filter.args[:], pos) return apply_format(str, filter.args[:], pos)
} }
case: case:
return nil, Data_Error{ return nil, Error_Body{
msg = fmt.tprintf("unknown pipe op '%s'", filter.op), msg = fmt.tprintf("unknown pipe op '%s'", filter.op),
pos = pos, pos = pos,
kind = .Data,
} }
} }
} }
// apply_format formats an ISO 8601 date string as a display string // apply_format formats an ISO 8601 date string as a display string
// (e.g. "2026-03-15T08:49:54-04:00" → "15 Mar 2026"). Invalid input // (e.g. "2026-03-15T08:49:54-04:00" → "15 Mar 2026"). Invalid input
// (empty, too-short, or unparseable) returns a `Data_Error`. Templates // (empty, too-short, or unparseable) returns a Data-kind `Error`. Templates
// that need to skip dateless pages should gate with a section: // that need to skip dateless pages should gate with a section:
// {{#date}}<time datetime="{{.}}">{{. | format}}</time>{{/date}} // {{#date}}<time datetime="{{.}}">{{. | format}}</time>{{/date}}
// The section's truthiness check catches empty before the filter runs. // The section's truthiness check catches empty before the filter runs.
@@ -144,9 +151,13 @@ apply_filter :: proc(value: any, filter: ^Pipe_Filter, pos: int) -> (any, Render
// 2023-10-15T13:18:50Z // 2023-10-15T13:18:50Z
// 2023-10-15T13:18:50 // 2023-10-15T13:18:50
// 2023-10-15 // 2023-10-15
apply_format :: proc(iso: string, args: []string, pos: int) -> (result: any, err: Render_Error) { apply_format :: proc(iso: string, args: []string, pos: int) -> (result: any, err: Error) {
if len(iso) < 10 { if len(iso) < 10 {
return nil, Data_Error{msg = "format may only be used on dates", pos = pos} return nil, Error_Body{
msg = "format may only be used on dates",
pos = pos,
kind = .Data,
}
} }
year := iso[:4] year := iso[:4]
@@ -154,7 +165,11 @@ apply_format :: proc(iso: string, args: []string, pos: int) -> (result: any, err
day_num := (int(iso[8]) - 0x30) * 10 + (int(iso[9]) - 0x30) day_num := (int(iso[8]) - 0x30) * 10 + (int(iso[9]) - 0x30)
if month_num < 1 || month_num > 12 { if month_num < 1 || month_num > 12 {
return nil, Data_Error{msg = fmt.tprintf("invalid date: \"%s\"", iso), pos = pos} return nil, Error_Body{
msg = fmt.tprintf("invalid date: \"%s\"", iso),
pos = pos,
kind = .Data,
}
} }
month := fmt.tprintf("%s", time.Month(month_num))[:3] month := fmt.tprintf("%s", time.Month(month_num))[:3]
@@ -162,20 +177,22 @@ apply_format :: proc(iso: string, args: []string, pos: int) -> (result: any, err
} }
// Groups preserve first-appearance order from the input list. // Groups preserve first-appearance order from the input list.
apply_group_by :: proc(value: any, args: []string, pos: int) -> (result: any, err: Render_Error) { apply_group_by :: proc(value: any, args: []string, pos: int) -> (result: any, err: Error) {
if len(args) != 1 { if len(args) != 1 {
return nil, Data_Error{ return nil, Error_Body{
msg = fmt.tprintf("group_by expects 1 argument, got %d", len(args)), msg = fmt.tprintf("group_by expects 1 argument, got %d", len(args)),
pos = pos, pos = pos,
kind = .Data,
} }
} }
field := args[0] field := args[0]
elem_info, count, data := list_info(value) elem_info, count, data := list_info(value)
if elem_info == nil { if elem_info == nil {
return nil, Data_Error{ return nil, Error_Body{
msg = "group_by expects a list", msg = "group_by expects a list",
pos = pos, pos = pos,
kind = .Data,
} }
} }
@@ -189,16 +206,18 @@ apply_group_by :: proc(value: any, args: []string, pos: int) -> (result: any, er
key_val, found := lookup_in(elem, field) key_val, found := lookup_in(elem, field)
if !found { if !found {
return nil, Data_Error { return nil, Error_Body {
msg = fmt.tprintf("group_by: element missing field '%s'", field), msg = fmt.tprintf("group_by: element missing field '%s'", field),
pos = pos, pos = pos,
kind = .Data,
} }
} }
key_str := any_to_string(key_val) key_str := any_to_string(key_val)
if len(key_str) == 0 { if len(key_str) == 0 {
return nil, Data_Error{ return nil, Error_Body{
msg = fmt.tprintf("group_by: field '%s' is empty", field), msg = fmt.tprintf("group_by: field '%s' is empty", field),
pos = pos, pos = pos,
kind = .Data,
} }
} }
+3 -7
View File
@@ -66,13 +66,9 @@ test_pipe_group_by_missing_field_fails :: proc(t: ^testing.T) {
defer delete_template(&tpl) defer delete_template(&tpl)
_, err := render(tpl, data) _, err := render(tpl, data)
testing.expect(t, err != nil, "missing field should error") testing.expect(t, err != nil, "missing field should error")
b := body(err)
is_data_err := false testing.expect(t, b.kind == .Data, "error should be Data kind")
#partial switch e in err { testing.expect(t, len(b.msg) > 0, "error should have non-empty msg")
case Data_Error:
is_data_err = len(e.msg) > 0
}
testing.expect(t, is_data_err, "error should be Data_Error")
} }
@(test) @(test)
+8 -3
View File
@@ -28,7 +28,7 @@ tokenize :: proc(
allocator := context.allocator, allocator := context.allocator,
) -> ( ) -> (
tokens: [dynamic]Token, tokens: [dynamic]Token,
err: Render_Error, err: Error,
) { ) {
tokens = make([dynamic]Token, 0, 8, allocator) tokens = make([dynamic]Token, 0, 8, allocator)
@@ -47,9 +47,10 @@ tokenize :: proc(
content_start := i + 3 content_start := i + 3
idx := strings.index(src[content_start:], "}}}") idx := strings.index(src[content_start:], "}}}")
if idx < 0 { if idx < 0 {
return tokens, Syntax_Error { return tokens, Error_Body {
msg = "unclosed triple mustache '{{{'", msg = "unclosed triple mustache '{{{'",
pos = tag_pos, pos = tag_pos,
kind = .Syntax,
} }
} }
close := content_start + idx close := content_start + idx
@@ -90,7 +91,11 @@ tokenize :: proc(
close_idx := strings.index(src[key_start:], "}}") close_idx := strings.index(src[key_start:], "}}")
if close_idx < 0 { if close_idx < 0 {
return tokens, Syntax_Error{msg = "unclosed tag '{{'", pos = tag_pos} return tokens, Error_Body {
msg = "unclosed tag '{{'",
pos = tag_pos,
kind = .Syntax,
}
} }
close := key_start + close_idx close := key_start + close_idx
+6 -34
View File
@@ -63,31 +63,17 @@ load_template :: proc(vfs: ^VFS, virtual_path: string) -> mustache.Template {
source := string(data) source := string(data)
tpl, err := mustache.parse(source, entry.fs_path) tpl, err := mustache.parse(source, entry.fs_path)
if err != nil { if err != nil {
switch e in err { b := mustache.body(err)
// TODO: Find a way to merge these branches.
case mustache.Syntax_Error:
log.errorf( log.errorf(
"%s", "%s",
mustache.format_error( mustache.format_error(
entry.fs_path, entry.fs_path,
source, source,
e.pos, b.pos,
e.msg, b.msg,
colorize = mustache.should_colorize(), colorize = mustache.should_colorize(),
), ),
) )
case mustache.Data_Error:
log.errorf(
"%s",
mustache.format_error(
entry.fs_path,
source,
e.pos,
e.msg,
colorize = mustache.should_colorize(),
),
)
}
os.exit(1) os.exit(1)
} }
return tpl return tpl
@@ -375,31 +361,17 @@ load_partials :: proc(vfs: ^VFS) -> map[string]mustache.Template {
source := string(data) source := string(data)
tpl, err := mustache.parse(source, entry.fs_path) tpl, err := mustache.parse(source, entry.fs_path)
if err != nil { if err != nil {
// TODO: Find a way to merge these branches. b := mustache.body(err)
switch e in err {
case mustache.Syntax_Error:
log.errorf( log.errorf(
"%s", "%s",
mustache.format_error( mustache.format_error(
entry.fs_path, entry.fs_path,
source, source,
e.pos, b.pos,
e.msg, b.msg,
colorize = mustache.should_colorize(), colorize = mustache.should_colorize(),
), ),
) )
case mustache.Data_Error:
log.errorf(
"%s",
mustache.format_error(
entry.fs_path,
source,
e.pos,
e.msg,
colorize = mustache.should_colorize(),
),
)
}
os.exit(1) os.exit(1)
} }
partials[key] = tpl partials[key] = tpl