A go template renderer based on Perl's Template Toolkit
1package gott
2
3import (
4 "fmt"
5 "reflect"
6 "sort"
7 "strconv"
8 "strings"
9)
10
11// Evaluator evaluates an AST with the given variables
12type Evaluator struct {
13 renderer *Renderer // for filters, virtual methods, includes
14 vars map[string]any // current variable scope
15 blocks map[string]*BlockStmt // defined blocks
16 output strings.Builder // accumulated output
17}
18
19// NewEvaluator creates a new evaluator
20func NewEvaluator(r *Renderer, vars map[string]any) *Evaluator {
21 if vars == nil {
22 vars = make(map[string]any)
23 }
24 return &Evaluator{
25 renderer: r,
26 vars: vars,
27 blocks: make(map[string]*BlockStmt),
28 }
29}
30
31// Eval evaluates the template and returns the output string
32func (e *Evaluator) Eval(t *Template) (string, error) {
33 // First pass: collect block definitions
34 for _, node := range t.Nodes {
35 if block, ok := node.(*BlockStmt); ok {
36 e.blocks[block.Name] = block
37 }
38 }
39
40 // Second pass: evaluate nodes
41 for _, node := range t.Nodes {
42 // Skip block definitions in output (they're just definitions)
43 if _, ok := node.(*BlockStmt); ok {
44 continue
45 }
46 if err := e.evalNode(node); err != nil {
47 return "", err
48 }
49 }
50
51 return e.output.String(), nil
52}
53
54// evalNode evaluates a single AST node
55func (e *Evaluator) evalNode(node Node) error {
56 switch n := node.(type) {
57 case *TextNode:
58 e.output.WriteString(n.Text)
59
60 case *OutputStmt:
61 val, err := e.evalExpr(n.Expr)
62 if err != nil {
63 return err
64 }
65 if val != nil {
66 e.output.WriteString(e.toString(val))
67 }
68
69 case *IfStmt:
70 return e.evalIf(n)
71
72 case *UnlessStmt:
73 return e.evalUnless(n)
74
75 case *ForeachStmt:
76 return e.evalForeach(n)
77
78 case *IncludeStmt:
79 return e.evalInclude(n)
80
81 case *WrapperStmt:
82 return e.evalWrapper(n)
83
84 case *SetStmt:
85 val, err := e.evalExpr(n.Value)
86 if err != nil {
87 return err
88 }
89 e.vars[n.Var] = val
90
91 case *TryStmt:
92 return e.evalTry(n)
93
94 case *BlockStmt:
95 // Block definitions are handled in first pass, skip here
96 }
97
98 return nil
99}
100
101// evalNodes evaluates a slice of nodes
102func (e *Evaluator) evalNodes(nodes []Node) error {
103 for _, node := range nodes {
104 if err := e.evalNode(node); err != nil {
105 return err
106 }
107 }
108 return nil
109}
110
111// evalIf evaluates an IF statement
112func (e *Evaluator) evalIf(n *IfStmt) error {
113 cond, err := e.evalExpr(n.Condition)
114 if err != nil {
115 return err
116 }
117
118 if e.isTruthy(cond) {
119 return e.evalNodes(n.Body)
120 }
121
122 // Check ELSIF chain
123 for _, elsif := range n.ElsIf {
124 cond, err := e.evalExpr(elsif.Condition)
125 if err != nil {
126 return err
127 }
128 if e.isTruthy(cond) {
129 return e.evalNodes(elsif.Body)
130 }
131 }
132
133 // Fall through to ELSE
134 if n.Else != nil {
135 return e.evalNodes(n.Else)
136 }
137
138 return nil
139}
140
141// evalUnless evaluates an UNLESS statement
142func (e *Evaluator) evalUnless(n *UnlessStmt) error {
143 cond, err := e.evalExpr(n.Condition)
144 if err != nil {
145 return err
146 }
147
148 if !e.isTruthy(cond) {
149 return e.evalNodes(n.Body)
150 }
151
152 if n.Else != nil {
153 return e.evalNodes(n.Else)
154 }
155
156 return nil
157}
158
159// evalForeach evaluates a FOREACH loop
160func (e *Evaluator) evalForeach(n *ForeachStmt) error {
161 list, err := e.evalExpr(n.ListExpr)
162 if err != nil {
163 return err
164 }
165
166 if list == nil {
167 return nil
168 }
169
170 rv := reflect.ValueOf(list)
171
172 switch rv.Kind() {
173 case reflect.Slice, reflect.Array:
174 for i := 0; i < rv.Len(); i++ {
175 // Create new scope with loop variable
176 loopEval := e.withVar(n.ItemVar, rv.Index(i).Interface())
177 if err := loopEval.evalNodes(n.Body); err != nil {
178 return err
179 }
180 e.output.WriteString(loopEval.output.String())
181 }
182
183 case reflect.Map:
184 // TT2-style: iterate as key/value pairs, sorted by key
185 keys := rv.MapKeys()
186 sort.Slice(keys, func(i, j int) bool {
187 return fmt.Sprintf("%v", keys[i].Interface()) < fmt.Sprintf("%v", keys[j].Interface())
188 })
189
190 for _, key := range keys {
191 // Each iteration gets a map with "key" and "value" fields
192 entry := map[string]any{
193 "key": key.Interface(),
194 "value": rv.MapIndex(key).Interface(),
195 }
196
197 loopEval := e.withVar(n.ItemVar, entry)
198 if err := loopEval.evalNodes(n.Body); err != nil {
199 return err
200 }
201 e.output.WriteString(loopEval.output.String())
202 }
203
204 default:
205 return &EvalError{
206 Pos: n.Position,
207 Message: fmt.Sprintf("cannot iterate over %T", list),
208 }
209 }
210
211 return nil
212}
213
214// evalInclude evaluates an INCLUDE directive
215func (e *Evaluator) evalInclude(n *IncludeStmt) error {
216 // Resolve the path (may be static or dynamic)
217 includeName, err := e.resolvePath(n.Name, n.PathParts)
218 if err != nil {
219 return err
220 }
221
222 // First check if it's a defined block
223 if block, ok := e.blocks[includeName]; ok {
224 return e.evalNodes(block.Body)
225 }
226
227 // Otherwise, try to load from filesystem
228 content, err := e.renderer.loadFile(includeName)
229 if err != nil {
230 return &EvalError{
231 Pos: n.Position,
232 Message: fmt.Sprintf("include '%s' not found", includeName),
233 }
234 }
235
236 // Parse with caching
237 tmpl, err := e.renderer.parseTemplate(includeName, content)
238 if err != nil {
239 return err
240 }
241
242 // Evaluate with same scope
243 includeEval := NewEvaluator(e.renderer, e.copyVars())
244 // Copy blocks
245 for name, block := range e.blocks {
246 includeEval.blocks[name] = block
247 }
248 result, err := includeEval.Eval(tmpl)
249 if err != nil {
250 return err
251 }
252 e.output.WriteString(result)
253
254 return nil
255}
256
257// resolvePath resolves a static or dynamic path to its final string value.
258// If pathParts is non-empty, variables are interpolated; otherwise staticPath is used.
259func (e *Evaluator) resolvePath(staticPath string, pathParts []PathPart) (string, error) {
260 // Static path - no interpolation needed
261 if len(pathParts) == 0 {
262 return staticPath, nil
263 }
264
265 // Dynamic path - interpolate variables
266 var result strings.Builder
267 for _, part := range pathParts {
268 if part.IsVariable {
269 // Resolve the variable
270 val, err := e.resolveIdent(part.Parts)
271 if err != nil {
272 return "", err
273 }
274 if val == nil {
275 return "", &EvalError{
276 Message: fmt.Sprintf("undefined variable in path: $%s", strings.Join(part.Parts, ".")),
277 }
278 }
279 result.WriteString(e.toString(val))
280 } else {
281 result.WriteString(part.Value)
282 }
283 }
284 return result.String(), nil
285}
286
287// evalWrapper evaluates a WRAPPER directive
288func (e *Evaluator) evalWrapper(n *WrapperStmt) error {
289 // Resolve the path (may be static or dynamic)
290 wrapperPath, err := e.resolvePath(n.Name, n.PathParts)
291 if err != nil {
292 return err
293 }
294
295 // First, evaluate the wrapped content
296 contentEval := NewEvaluator(e.renderer, e.copyVars())
297 for name, block := range e.blocks {
298 contentEval.blocks[name] = block
299 }
300 // Collect block definitions from wrapper content so they're available to the wrapper
301 for _, node := range n.Content {
302 if block, ok := node.(*BlockStmt); ok {
303 contentEval.blocks[block.Name] = block
304 }
305 }
306 for _, node := range n.Content {
307 if err := contentEval.evalNode(node); err != nil {
308 return err
309 }
310 }
311 wrappedContent := contentEval.output.String()
312
313 // Load the wrapper template
314 var wrapperSource string
315 var wrapperName string
316 if block, ok := e.blocks[wrapperPath]; ok {
317 // Wrapper is a defined block - evaluate it
318 blockEval := NewEvaluator(e.renderer, e.copyVars())
319 for name, b := range e.blocks {
320 blockEval.blocks[name] = b
321 }
322 for _, node := range block.Body {
323 if err := blockEval.evalNode(node); err != nil {
324 return err
325 }
326 }
327 wrapperSource = blockEval.output.String()
328 wrapperName = "block:" + wrapperPath
329 } else {
330 content, err := e.renderer.loadFile(wrapperPath)
331 if err != nil {
332 return &EvalError{
333 Pos: n.Position,
334 Message: fmt.Sprintf("wrapper '%s' not found", wrapperPath),
335 }
336 }
337 wrapperSource = content
338 wrapperName = wrapperPath
339 }
340
341 // Parse the wrapper template with caching
342 tmpl, err := e.renderer.parseTemplate(wrapperName, wrapperSource)
343 if err != nil {
344 return err
345 }
346
347 // Evaluate wrapper with "content" variable set to wrapped content
348 wrapperVars := e.copyVars()
349 wrapperVars["content"] = wrappedContent
350
351 wrapperEval := NewEvaluator(e.renderer, wrapperVars)
352 for name, block := range e.blocks {
353 wrapperEval.blocks[name] = block
354 }
355 // Include blocks defined in wrapper content
356 for name, block := range contentEval.blocks {
357 wrapperEval.blocks[name] = block
358 }
359 result, err := wrapperEval.Eval(tmpl)
360 if err != nil {
361 return err
362 }
363 e.output.WriteString(result)
364
365 return nil
366}
367
368// evalTry evaluates a TRY/CATCH block
369func (e *Evaluator) evalTry(n *TryStmt) error {
370 // Create a new evaluator for the TRY block to isolate output
371 tryEval := NewEvaluator(e.renderer, e.copyVars())
372 for name, block := range e.blocks {
373 tryEval.blocks[name] = block
374 }
375
376 // Attempt to evaluate the TRY block
377 var tryErr error
378 for _, node := range n.Try {
379 if err := tryEval.evalNode(node); err != nil {
380 tryErr = err
381 break
382 }
383 }
384
385 // If no error, use the TRY output
386 if tryErr == nil {
387 e.output.WriteString(tryEval.output.String())
388 return nil
389 }
390
391 // Error occurred - evaluate CATCH block if present
392 if len(n.Catch) > 0 {
393 catchEval := NewEvaluator(e.renderer, e.copyVars())
394 for name, block := range e.blocks {
395 catchEval.blocks[name] = block
396 }
397
398 for _, node := range n.Catch {
399 if err := catchEval.evalNode(node); err != nil {
400 // Error in CATCH block - propagate it
401 return err
402 }
403 }
404 e.output.WriteString(catchEval.output.String())
405 }
406
407 return nil
408}
409
410// evalExpr evaluates an expression and returns its value
411func (e *Evaluator) evalExpr(expr Expr) (any, error) {
412 switch x := expr.(type) {
413 case *LiteralExpr:
414 return x.Value, nil
415
416 case *IdentExpr:
417 return e.resolveIdent(x.Parts)
418
419 case *BinaryExpr:
420 return e.evalBinary(x)
421
422 case *UnaryExpr:
423 return e.evalUnary(x)
424
425 case *CallExpr:
426 return e.evalCall(x)
427
428 case *FilterExpr:
429 return e.evalFilter(x)
430
431 case *DefaultExpr:
432 val, err := e.evalExpr(x.Expr)
433 if err != nil || !e.isDefined(val) {
434 return e.evalExpr(x.Default)
435 }
436 return val, nil
437 }
438
439 return nil, fmt.Errorf("unknown expression type: %T", expr)
440}
441
442// evalBinary evaluates a binary expression
443func (e *Evaluator) evalBinary(b *BinaryExpr) (any, error) {
444 left, err := e.evalExpr(b.Left)
445 if err != nil {
446 return nil, err
447 }
448 right, err := e.evalExpr(b.Right)
449 if err != nil {
450 return nil, err
451 }
452
453 switch b.Op {
454 case TokenPlus:
455 // If either operand is a string, do string concatenation
456 if e.isString(left) || e.isString(right) {
457 return e.toString(left) + e.toString(right), nil
458 }
459 return e.toFloat(left) + e.toFloat(right), nil
460
461 case TokenMinus:
462 return e.toFloat(left) - e.toFloat(right), nil
463
464 case TokenMul:
465 return e.toFloat(left) * e.toFloat(right), nil
466
467 case TokenDiv:
468 r := e.toFloat(right)
469 if r == 0 {
470 return nil, &EvalError{Pos: b.Position, Message: "division by zero"}
471 }
472 return e.toFloat(left) / r, nil
473
474 case TokenMod:
475 return int(e.toFloat(left)) % int(e.toFloat(right)), nil
476
477 case TokenEq:
478 return e.equals(left, right), nil
479
480 case TokenNe:
481 return !e.equals(left, right), nil
482
483 case TokenLt:
484 return e.toFloat(left) < e.toFloat(right), nil
485
486 case TokenLe:
487 return e.toFloat(left) <= e.toFloat(right), nil
488
489 case TokenGt:
490 return e.toFloat(left) > e.toFloat(right), nil
491
492 case TokenGe:
493 return e.toFloat(left) >= e.toFloat(right), nil
494
495 case TokenAnd:
496 return e.isTruthy(left) && e.isTruthy(right), nil
497
498 case TokenOr:
499 return e.isTruthy(left) || e.isTruthy(right), nil
500 }
501
502 return nil, fmt.Errorf("unknown operator: %v", b.Op)
503}
504
505// evalUnary evaluates a unary expression
506func (e *Evaluator) evalUnary(u *UnaryExpr) (any, error) {
507 val, err := e.evalExpr(u.X)
508 if err != nil {
509 return nil, err
510 }
511
512 switch u.Op {
513 case TokenMinus:
514 return -e.toFloat(val), nil
515 }
516
517 return nil, fmt.Errorf("unknown unary operator: %v", u.Op)
518}
519
520// evalCall evaluates a function call
521func (e *Evaluator) evalCall(c *CallExpr) (any, error) {
522 fn, ok := e.vars[c.Func]
523 if !ok {
524 return nil, &EvalError{
525 Pos: c.Position,
526 Message: fmt.Sprintf("undefined function: %s", c.Func),
527 }
528 }
529
530 fnValue := reflect.ValueOf(fn)
531 if fnValue.Kind() != reflect.Func {
532 return nil, &EvalError{
533 Pos: c.Position,
534 Message: fmt.Sprintf("%s is not a function", c.Func),
535 }
536 }
537
538 var args []reflect.Value
539 for _, arg := range c.Args {
540 val, err := e.evalExpr(arg)
541 if err != nil {
542 return nil, err
543 }
544 args = append(args, reflect.ValueOf(val))
545 }
546
547 results := fnValue.Call(args)
548 if len(results) > 0 {
549 return results[0].Interface(), nil
550 }
551 return nil, nil
552}
553
554// evalFilter evaluates a filter expression
555func (e *Evaluator) evalFilter(f *FilterExpr) (any, error) {
556 input, err := e.evalExpr(f.Input)
557 if err != nil {
558 return nil, err
559 }
560
561 inputStr := e.toString(input)
562
563 filter, ok := e.renderer.getFilter(f.Filter)
564 if !ok {
565 return inputStr + fmt.Sprintf("[Filter '%s' not found]", f.Filter), nil
566 }
567
568 var args []string
569 for _, arg := range f.Args {
570 val, err := e.evalExpr(arg)
571 if err != nil {
572 return nil, err
573 }
574 args = append(args, e.toString(val))
575 }
576
577 return filter(inputStr, args...), nil
578}
579
580// resolveIdent resolves a dot-notation identifier
581func (e *Evaluator) resolveIdent(parts []string) (any, error) {
582 if len(parts) == 0 {
583 return nil, nil
584 }
585
586 // Get the root variable
587 val, ok := e.vars[parts[0]]
588
589 // Handle .exists virtual method at top level
590 if len(parts) == 2 && parts[1] == "exists" {
591 return ok, nil
592 }
593
594 // Handle .defined virtual method at top level
595 if len(parts) == 2 && parts[1] == "defined" {
596 if !ok {
597 return false, nil
598 }
599 return e.isDefined(val), nil
600 }
601
602 if !ok {
603 return nil, nil
604 }
605
606 if len(parts) == 1 {
607 return val, nil
608 }
609
610 // Navigate through the remaining parts
611 for i := 1; i < len(parts); i++ {
612 property := parts[i]
613
614 // Virtual methods
615 if property == "exists" {
616 return true, nil
617 }
618 if property == "defined" {
619 return e.isDefined(val), nil
620 }
621
622 // Check custom virtual methods
623 if method, ok := e.renderer.getVirtualMethod(property); ok {
624 result, _ := method(val)
625 val = result
626 continue
627 }
628
629 // Built-in virtual methods
630 switch property {
631 case "length", "size":
632 switch v := val.(type) {
633 case string:
634 return len(v), nil
635 case map[string]any:
636 return len(v), nil
637 default:
638 rv := reflect.ValueOf(val)
639 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
640 return rv.Len(), nil
641 }
642 return nil, nil
643 }
644
645 case "first":
646 rv := reflect.ValueOf(val)
647 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
648 if rv.Len() > 0 {
649 val = rv.Index(0).Interface()
650 continue
651 }
652 }
653 return nil, nil
654
655 case "last":
656 rv := reflect.ValueOf(val)
657 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
658 if rv.Len() > 0 {
659 val = rv.Index(rv.Len() - 1).Interface()
660 continue
661 }
662 }
663 return nil, nil
664
665 default:
666 // Try map access
667 if m, ok := val.(map[string]any); ok {
668 if v, exists := m[property]; exists {
669 val = v
670 continue
671 }
672 return nil, nil
673 }
674
675 // Try struct field access via reflection
676 rv := reflect.ValueOf(val)
677 if rv.Kind() == reflect.Ptr {
678 rv = rv.Elem()
679 }
680 if rv.Kind() == reflect.Struct {
681 field := rv.FieldByName(property)
682 if field.IsValid() {
683 val = field.Interface()
684 continue
685 }
686 }
687
688 return nil, nil
689 }
690 }
691
692 return val, nil
693}
694
695// ---- Helper methods ----
696
697// withVar creates a new evaluator with an additional variable
698func (e *Evaluator) withVar(name string, value any) *Evaluator {
699 newVars := e.copyVars()
700 newVars[name] = value
701 eval := NewEvaluator(e.renderer, newVars)
702 for k, v := range e.blocks {
703 eval.blocks[k] = v
704 }
705 return eval
706}
707
708// copyVars creates a shallow copy of the variables map
709func (e *Evaluator) copyVars() map[string]any {
710 newVars := make(map[string]any, len(e.vars))
711 for k, v := range e.vars {
712 newVars[k] = v
713 }
714 return newVars
715}
716
717// isTruthy determines if a value is considered "true"
718func (e *Evaluator) isTruthy(val any) bool {
719 if val == nil {
720 return false
721 }
722
723 switch v := val.(type) {
724 case bool:
725 return v
726 case string:
727 return v != ""
728 case int:
729 return v != 0
730 case int64:
731 return v != 0
732 case float64:
733 return v != 0
734 case float32:
735 return v != 0
736 default:
737 rv := reflect.ValueOf(val)
738 switch rv.Kind() {
739 case reflect.Slice, reflect.Array:
740 return rv.Len() > 0
741 case reflect.Map:
742 return rv.Len() > 0
743 }
744 return true
745 }
746}
747
748// isDefined checks if a value is "defined" (non-nil and non-empty for strings)
749func (e *Evaluator) isDefined(val any) bool {
750 if val == nil {
751 return false
752 }
753
754 switch v := val.(type) {
755 case string:
756 return v != ""
757 case map[string]any:
758 return v != nil
759 default:
760 rv := reflect.ValueOf(val)
761 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
762 return !rv.IsNil()
763 }
764 return true
765 }
766}
767
768// isString checks if a value is a string
769func (e *Evaluator) isString(v any) bool {
770 _, ok := v.(string)
771 return ok
772}
773
774// toString converts a value to string
775func (e *Evaluator) toString(v any) string {
776 switch val := v.(type) {
777 case string:
778 return val
779 case nil:
780 return ""
781 case bool:
782 if val {
783 return "true"
784 }
785 return "false"
786 case float64:
787 if val == float64(int64(val)) {
788 return strconv.FormatInt(int64(val), 10)
789 }
790 return strconv.FormatFloat(val, 'f', -1, 64)
791 case int:
792 return strconv.Itoa(val)
793 case int64:
794 return strconv.FormatInt(val, 10)
795 default:
796 return fmt.Sprintf("%v", val)
797 }
798}
799
800// toFloat converts a value to float64
801func (e *Evaluator) toFloat(v any) float64 {
802 switch val := v.(type) {
803 case float64:
804 return val
805 case float32:
806 return float64(val)
807 case int:
808 return float64(val)
809 case int64:
810 return float64(val)
811 case int32:
812 return float64(val)
813 case string:
814 f, _ := strconv.ParseFloat(val, 64)
815 return f
816 default:
817 return 0
818 }
819}
820
821// equals compares two values for equality
822func (e *Evaluator) equals(left, right any) bool {
823 // Try numeric comparison
824 leftNum, leftOk := e.tryFloat(left)
825 rightNum, rightOk := e.tryFloat(right)
826 if leftOk && rightOk {
827 return leftNum == rightNum
828 }
829
830 // Fall back to string comparison
831 return fmt.Sprintf("%v", left) == fmt.Sprintf("%v", right)
832}
833
834// tryFloat attempts to convert a value to float64
835func (e *Evaluator) tryFloat(v any) (float64, bool) {
836 switch val := v.(type) {
837 case float64:
838 return val, true
839 case float32:
840 return float64(val), true
841 case int:
842 return float64(val), true
843 case int64:
844 return float64(val), true
845 case int32:
846 return float64(val), true
847 case string:
848 if f, err := strconv.ParseFloat(val, 64); err == nil {
849 return f, true
850 }
851 }
852 return 0, false
853}
854
855// ---- Error type ----
856
857// EvalError represents an error during evaluation
858type EvalError struct {
859 Pos Position
860 Message string
861}
862
863func (e *EvalError) Error() string {
864 return fmt.Sprintf("line %d, column %d: %s", e.Pos.Line, e.Pos.Column, e.Message)
865}
866
867// ParseError represents an error during parsing
868type ParseError struct {
869 Pos Position
870 Message string
871}
872
873func (e *ParseError) Error() string {
874 return fmt.Sprintf("parse error at line %d, column %d: %s", e.Pos.Line, e.Pos.Column, e.Message)
875}