feat: .Warning added to Error_Kind.

This commit is contained in:
Spencer Brower
2026-08-10 12:48:17 -04:00
parent f0800b8e13
commit b779cd68a5
3 changed files with 78 additions and 53 deletions
+5
View File
@@ -1,5 +1,10 @@
# TODOs # TODOs
## Priority Unclear (Evaluate Later)
- [ ] instead of `warnings: [dynamic]Error` we should use `warnings: [dynamic; 8]Error`
- when limit reached, the template fails and stops rendering.
## General ## General
Remember to polish existing features before moving on to new ones. Remember to polish existing features before moving on to new ones.
+18 -9
View File
@@ -25,6 +25,7 @@ Context_Stack :: [dynamic]any
Error_Kind :: enum { Error_Kind :: enum {
Syntax, // parse-time: malformed template Syntax, // parse-time: malformed template
Data, // render-time: template fine, data wrong (e.g. filter misuse) Data, // render-time: template fine, data wrong (e.g. filter misuse)
Warning, // non-fatal: operation succeeded but user should know
} }
Error_Body :: struct { Error_Body :: struct {
@@ -176,6 +177,7 @@ render :: proc(
data: any, data: any,
partials: map[string]Template = nil, partials: map[string]Template = nil,
allocator := context.allocator, allocator := context.allocator,
warnings: ^[dynamic]Error = nil,
) -> ( ) -> (
result: string, result: string,
err: Error, err: Error,
@@ -201,7 +203,7 @@ render :: proc(
} }
all_nodes := tmpl.nodes[:] all_nodes := tmpl.nodes[:]
err = render_nodes(tmpl, all_nodes, &ctx, partials, &builder) err = render_nodes(tmpl, all_nodes, &ctx, partials, &builder, warnings = warnings)
if err != nil { if err != nil {
return result, err return result, err
} }
@@ -545,6 +547,7 @@ render_template :: proc(
b: ^strings.Builder, b: ^strings.Builder,
blocks: map[string]Block_Override, blocks: map[string]Block_Override,
indent: string, indent: string,
warnings: ^[dynamic]Error = nil,
) -> Error { ) -> Error {
if len(indent) > 0 { if len(indent) > 0 {
state := Indent_State { state := Indent_State {
@@ -552,9 +555,9 @@ render_template :: proc(
at_line_start = false, at_line_start = false,
} }
strings.write_string(b, indent) // first line always gets indent strings.write_string(b, indent) // first line always gets indent
return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, &state) return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, &state, warnings)
} }
return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, nil) return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, nil, warnings)
} }
write_indented :: proc( write_indented :: proc(
@@ -598,6 +601,7 @@ render_nodes :: proc(
b: ^strings.Builder, b: ^strings.Builder,
blocks: map[string]Block_Override = nil, blocks: map[string]Block_Override = nil,
indent_state: ^Indent_State = nil, indent_state: ^Indent_State = nil,
warnings: ^[dynamic]Error = nil,
) -> Error { ) -> Error {
i := 0 i := 0
for i < len(nodes) { for i < len(nodes) {
@@ -621,7 +625,7 @@ render_nodes :: proc(
warn_unknown_key(current, ctx[:], node) warn_unknown_key(current, ctx[:], node)
} }
if len(node.filters) > 0 { if len(node.filters) > 0 {
transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings)
if perr != nil { if perr != nil {
return tag_error(perr, current) return tag_error(perr, current)
} }
@@ -640,7 +644,7 @@ render_nodes :: proc(
warn_unknown_key(current, ctx[:], node) warn_unknown_key(current, ctx[:], node)
} }
if len(node.filters) > 0 { if len(node.filters) > 0 {
transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings)
if perr != nil { if perr != nil {
return tag_error(perr, current) return tag_error(perr, current)
} }
@@ -655,7 +659,7 @@ render_nodes :: proc(
warn_unknown_key(current, ctx[:], node) warn_unknown_key(current, ctx[:], node)
} }
if len(node.filters) > 0 { if len(node.filters) > 0 {
transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings)
if perr != nil { if perr != nil {
return tag_error(perr, current) return tag_error(perr, current)
} }
@@ -677,6 +681,7 @@ render_nodes :: proc(
b, b,
blocks, blocks,
indent_state, indent_state,
warnings,
) or_return ) or_return
} }
} else { } else {
@@ -690,6 +695,7 @@ render_nodes :: proc(
b, b,
blocks, blocks,
indent_state, indent_state,
warnings,
) or_return ) or_return
} }
} }
@@ -701,7 +707,7 @@ render_nodes :: proc(
warn_unknown_key(current, ctx[:], node) warn_unknown_key(current, ctx[:], node)
} }
if len(node.filters) > 0 { if len(node.filters) > 0 {
transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings)
if perr != nil { if perr != nil {
return tag_error(perr, current) return tag_error(perr, current)
} }
@@ -716,6 +722,7 @@ render_nodes :: proc(
b, b,
blocks, blocks,
indent_state, indent_state,
warnings,
) or_return ) or_return
} }
i += 1 + len(node.children) i += 1 + len(node.children)
@@ -730,7 +737,7 @@ render_nodes :: proc(
if !found { if !found {
warn_missing_partial(current, partials, node, name) warn_missing_partial(current, partials, node, name)
} else { } else {
render_template(pt, ctx, partials, b, nil, node.indent) or_return render_template(pt, ctx, partials, b, nil, node.indent, warnings) or_return
if indent_state != nil { if indent_state != nil {
indent_state.at_line_start = false indent_state.at_line_start = false
} }
@@ -765,6 +772,7 @@ render_nodes :: proc(
&temp, &temp,
content_blocks, content_blocks,
nil, nil,
warnings,
) or_return ) or_return
at_ls := true at_ls := true
write_indented(b, node.indent, strings.to_string(temp), &at_ls) write_indented(b, node.indent, strings.to_string(temp), &at_ls)
@@ -777,6 +785,7 @@ render_nodes :: proc(
b, b,
content_blocks, content_blocks,
indent_state, indent_state,
warnings,
) or_return ) or_return
} }
i += 1 + len(node.children) i += 1 + len(node.children)
@@ -789,7 +798,7 @@ render_nodes :: proc(
warn_missing_partial(current, partials, node, node.key) warn_missing_partial(current, partials, node, node.key)
} else { } else {
warn_unmatched_block_overrides(current, pt, parent_children) warn_unmatched_block_overrides(current, pt, parent_children)
render_template(pt, ctx, partials, b, merged, node.indent) or_return render_template(pt, ctx, partials, b, merged, node.indent, warnings) or_return
if indent_state != nil { if indent_state != nil {
indent_state.at_line_start = false indent_state.at_line_start = false
} }
+33 -22
View File
@@ -221,14 +221,26 @@ apply_pipeline :: proc(
pos: int, pos: int,
ctx: []any, ctx: []any,
tmpl: Template, tmpl: Template,
warnings: ^[dynamic]Error = nil,
) -> ( ) -> (
current: any, current: any,
err: Error, err: Error,
) { ) {
current = value current = value
for &filter in filters { for &filter in filters {
current = apply_filter(current, &filter, pos, ctx, tmpl) or_return result, ferr := apply_filter(current, &filter, pos, ctx, tmpl)
log.debugf("applied: filter=%v before=%s after=%s pos=%d", filter, value, current, pos) if ferr != nil {
b := body(ferr)
if b.kind != .Warning {
return result, ferr
}
if warnings != nil {
append(warnings, ferr)
} else {
log.warnf("%s", format_render_error(ferr, tmpl))
}
}
current = result
} }
return return
} }
@@ -438,6 +450,7 @@ take :: proc(
elem_info, count, data := list_info(value) elem_info, count, data := list_info(value)
is_list := elem_info != nil is_list := elem_info != nil
from_start := from_start from_start := from_start
warning: Error = nil
str: string str: string
if !is_list { if !is_list {
@@ -457,15 +470,14 @@ take :: proc(
n := 1 n := 1
if !is_list && len(args) == 0 { if !is_list && len(args) == 0 {
path := tmpl.path != "" ? tmpl.path : "<input>" warning = Error_Body {
diag := format_tag_error( msg = "defaulting to n=1 for string",
path, pos = tag_pos,
tmpl.source, kind = .Warning,
op_pos, source = tmpl.source,
"defaulting to n=1 for string", path = tmpl.path,
"consider specifying n explicitly", hint = "consider specifying n explicitly",
) }
log.warnf("%s", diag)
} }
if len(args) > 0 { if len(args) > 0 {
parsed, ok := strconv.parse_int(args[0]) parsed, ok := strconv.parse_int(args[0])
@@ -494,15 +506,14 @@ take :: proc(
if n < 0 { if n < 0 {
op_name := from_start ? "first" : "last" op_name := from_start ? "first" : "last"
alt_name := from_start ? "last" : "first" alt_name := from_start ? "last" : "first"
path := tmpl.path != "" ? tmpl.path : "<input>" warning = Error_Body {
diag := format_tag_error( msg = fmt.tprintf("%s %d is equivalent to %s %d", op_name, n, alt_name, 0 - n),
path, pos = tag_pos,
tmpl.source, kind = .Warning,
op_pos, source = tmpl.source,
fmt.tprintf("%s %d is equivalent to %s %d", op_name, n, alt_name, 0 - n), path = tmpl.path,
"consider using the clearer form", hint = "consider using the clearer form",
) }
log.warnf("%s", diag)
from_start = !from_start from_start = !from_start
n = 0 - n n = 0 - n
} }
@@ -522,9 +533,9 @@ take :: proc(
for j in start ..< end { for j in start ..< end {
append(&result, extract_list_element(elem_info, data, j)) append(&result, extract_list_element(elem_info, data, j))
} }
return result, nil return any{new_clone(result, context.temp_allocator), typeid_of([dynamic]any)}, warning
} else { } else {
return str[start:end], nil return any{new_clone(str[start:end], context.temp_allocator), typeid_of(string)}, warning
} }
} }