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 *MethodCallExpr:
429 return e.evalMethodCall(x)
430
431 case *FilterExpr:
432 return e.evalFilter(x)
433
434 case *DefaultExpr:
435 val, err := e.evalExpr(x.Expr)
436 if err != nil || !e.isDefined(val) {
437 return e.evalExpr(x.Default)
438 }
439 return val, nil
440 }
441
442 return nil, fmt.Errorf("unknown expression type: %T", expr)
443}
444
445// evalBinary evaluates a binary expression
446func (e *Evaluator) evalBinary(b *BinaryExpr) (any, error) {
447 left, err := e.evalExpr(b.Left)
448 if err != nil {
449 return nil, err
450 }
451 right, err := e.evalExpr(b.Right)
452 if err != nil {
453 return nil, err
454 }
455
456 switch b.Op {
457 case TokenPlus:
458 // If either operand is a string, do string concatenation
459 if e.isString(left) || e.isString(right) {
460 return e.toString(left) + e.toString(right), nil
461 }
462 return e.toFloat(left) + e.toFloat(right), nil
463
464 case TokenMinus:
465 return e.toFloat(left) - e.toFloat(right), nil
466
467 case TokenMul:
468 return e.toFloat(left) * e.toFloat(right), nil
469
470 case TokenDiv:
471 r := e.toFloat(right)
472 if r == 0 {
473 return nil, &EvalError{Pos: b.Position, Message: "division by zero"}
474 }
475 return e.toFloat(left) / r, nil
476
477 case TokenMod:
478 return int(e.toFloat(left)) % int(e.toFloat(right)), nil
479
480 case TokenEq:
481 return e.equals(left, right), nil
482
483 case TokenNe:
484 return !e.equals(left, right), nil
485
486 case TokenLt:
487 return e.toFloat(left) < e.toFloat(right), nil
488
489 case TokenLe:
490 return e.toFloat(left) <= e.toFloat(right), nil
491
492 case TokenGt:
493 return e.toFloat(left) > e.toFloat(right), nil
494
495 case TokenGe:
496 return e.toFloat(left) >= e.toFloat(right), nil
497
498 case TokenAnd:
499 return e.isTruthy(left) && e.isTruthy(right), nil
500
501 case TokenOr:
502 return e.isTruthy(left) || e.isTruthy(right), nil
503 }
504
505 return nil, fmt.Errorf("unknown operator: %v", b.Op)
506}
507
508// evalUnary evaluates a unary expression
509func (e *Evaluator) evalUnary(u *UnaryExpr) (any, error) {
510 val, err := e.evalExpr(u.X)
511 if err != nil {
512 return nil, err
513 }
514
515 switch u.Op {
516 case TokenMinus:
517 return -e.toFloat(val), nil
518 }
519
520 return nil, fmt.Errorf("unknown unary operator: %v", u.Op)
521}
522
523// evalCall evaluates a function call
524// All arguments are converted to strings before being passed to the function
525// The function signature should be: func(args ...string) any
526// or for no-arg functions: func() any
527func (e *Evaluator) evalCall(c *CallExpr) (any, error) {
528 // Parse the function path (e.g., "h.mytest" -> ["h", "mytest"])
529 parts := strings.Split(c.Func, ".")
530
531 // Resolve the function using dot notation without auto-invoking
532 fn, err := e.resolveIdentNoAutoInvoke(parts)
533 if err != nil {
534 return nil, err
535 }
536
537 if fn == nil {
538 return nil, &EvalError{
539 Pos: c.Position,
540 Message: fmt.Sprintf("undefined function: %s", c.Func),
541 }
542 }
543
544 fnValue := reflect.ValueOf(fn)
545 if fnValue.Kind() != reflect.Func {
546 return nil, &EvalError{
547 Pos: c.Position,
548 Message: fmt.Sprintf("%s is not a function", c.Func),
549 }
550 }
551
552 var args []reflect.Value
553 for _, arg := range c.Args {
554 val, err := e.evalExpr(arg)
555 if err != nil {
556 return nil, err
557 }
558 args = append(args, reflect.ValueOf(e.toString(val)))
559 }
560
561 results := fnValue.Call(args)
562 if len(results) > 0 {
563 return results[0].Interface(), nil
564 }
565 return nil, nil
566}
567
568// evalMethodCall evaluates a virtual method call: obj.method(args...)
569// Virtual methods take precedence over function calls in vars map
570func (e *Evaluator) evalMethodCall(m *MethodCallExpr) (any, error) {
571 // First check if receiver is an IdentExpr and obj.method is a function (for backward compatibility)
572 if ident, ok := m.Receiver.(*IdentExpr); ok {
573 // Construct the full path: e.g., ["h", "inner", "nestedFunc"]
574 fullPath := make([]string, len(ident.Parts)+1)
575 copy(fullPath, ident.Parts)
576 fullPath[len(ident.Parts)] = m.Method
577
578 // Try to resolve it as a function
579 fn, err := e.resolveIdentNoAutoInvoke(fullPath)
580 if err == nil && fn != nil {
581 fnValue := reflect.ValueOf(fn)
582 if fnValue.Kind() == reflect.Func {
583 // It's a function, call it as a regular function call
584 var args []reflect.Value
585 for _, arg := range m.Args {
586 val, err := e.evalExpr(arg)
587 if err != nil {
588 return nil, err
589 }
590 args = append(args, reflect.ValueOf(e.toString(val)))
591 }
592 results := fnValue.Call(args)
593 if len(results) > 0 {
594 return results[0].Interface(), nil
595 }
596 return nil, nil
597 }
598 }
599 }
600
601 // Evaluate receiver (the object)
602 receiver, err := e.evalExpr(m.Receiver)
603 if err != nil {
604 return nil, err
605 }
606
607 // Get the virtual method from custom methods
608 methodFn, ok := e.renderer.getVirtualMethod(m.Method)
609 if !ok {
610 // Try built-in virtual methods with args
611 return e.evalBuiltinVirtualMethodWithArgs(receiver, m.Method, m.Args)
612 }
613
614 // Evaluate arguments
615 var args []any
616 for _, arg := range m.Args {
617 val, err := e.evalExpr(arg)
618 if err != nil {
619 return nil, err
620 }
621 args = append(args, val)
622 }
623
624 // Call virtual method: func(receiver, ...args) (any, bool)
625 result, ok := methodFn(receiver, args...)
626 if !ok {
627 return nil, &EvalError{
628 Pos: m.Position,
629 Message: fmt.Sprintf("virtual method '%s' returned false for %T", m.Method, receiver),
630 }
631 }
632
633 return result, nil
634}
635
636// evalBuiltinVirtualMethodWithArgs evaluates built-in virtual methods that accept arguments
637func (e *Evaluator) evalBuiltinVirtualMethodWithArgs(receiver any, method string, args []Expr) (any, error) {
638 // Convert args to evaluated values
639 var argValues []any
640 for _, arg := range args {
641 val, err := e.evalExpr(arg)
642 if err != nil {
643 return nil, err
644 }
645 argValues = append(argValues, val)
646 }
647
648 switch method {
649 case "get":
650 // Dynamic map access: map.get(key)
651 if len(argValues) != 1 {
652 return nil, &EvalError{
653 Pos: args[0].(Expr).(*LiteralExpr).Position,
654 Message: fmt.Sprintf("%s() expects 1 argument, got %d", method, len(argValues)),
655 }
656 }
657 if m, ok := receiver.(map[string]any); ok {
658 key := e.toString(argValues[0])
659 val, exists := m[key]
660 if exists {
661 return val, nil
662 }
663 return nil, nil
664 }
665 return nil, &EvalError{
666 Message: fmt.Sprintf("get() not supported for %T", receiver),
667 }
668 }
669
670 return nil, &EvalError{
671 Message: fmt.Sprintf("unknown virtual method: %s", method),
672 }
673}
674
675// evalFilter evaluates a filter expression
676func (e *Evaluator) evalFilter(f *FilterExpr) (any, error) {
677 input, err := e.evalExpr(f.Input)
678 if err != nil {
679 return nil, err
680 }
681
682 inputStr := e.toString(input)
683
684 filter, ok := e.renderer.getFilter(f.Filter)
685 if !ok {
686 return inputStr + fmt.Sprintf("[Filter '%s' not found]", f.Filter), nil
687 }
688
689 var args []string
690 for _, arg := range f.Args {
691 val, err := e.evalExpr(arg)
692 if err != nil {
693 return nil, err
694 }
695 args = append(args, e.toString(val))
696 }
697
698 return filter(inputStr, args...), nil
699}
700
701// resolveIdent resolves a dot-notation identifier
702func (e *Evaluator) resolveIdent(parts []string) (any, error) {
703 val, err := e.resolveIdentNoAutoInvoke(parts)
704 if err != nil {
705 return nil, err
706 }
707
708 if val == nil {
709 return nil, nil
710 }
711
712 // Auto-invoke functions at the end of dot notation (for bareword syntax)
713 fnValue := reflect.ValueOf(val)
714 if fnValue.Kind() == reflect.Func {
715 fnType := fnValue.Type()
716 // Check if function takes no required arguments (0 args, or variadic that accepts 0)
717 canCallWithNoArgs := fnType.NumIn() == 0 ||
718 (fnType.NumIn() == 1 && fnType.IsVariadic())
719 if canCallWithNoArgs {
720 // Call the function with no arguments
721 results := fnValue.Call([]reflect.Value{})
722 if len(results) > 0 {
723 return results[0].Interface(), nil
724 }
725 return nil, nil
726 }
727 // Function requires arguments but called without parens - return nil
728 return nil, nil
729 }
730
731 return val, nil
732}
733
734// resolveIdentNoAutoInvoke resolves a dot-notation identifier without auto-invoking functions
735// This is used when we need to resolve a function reference for calling
736func (e *Evaluator) resolveIdentNoAutoInvoke(parts []string) (any, error) {
737 if len(parts) == 0 {
738 return nil, nil
739 }
740
741 // Get the root variable
742 val, ok := e.vars[parts[0]]
743
744 // Handle .exists virtual method at top level
745 if len(parts) == 2 && parts[1] == "exists" {
746 return ok, nil
747 }
748
749 // Handle .defined virtual method at top level
750 if len(parts) == 2 && parts[1] == "defined" {
751 if !ok {
752 return false, nil
753 }
754 return e.isDefined(val), nil
755 }
756
757 if !ok {
758 return nil, nil
759 }
760
761 if len(parts) == 1 {
762 return val, nil
763 }
764
765 // Navigate through the remaining parts
766 for i := 1; i < len(parts); i++ {
767 property := parts[i]
768
769 // Virtual methods
770 if property == "exists" {
771 return true, nil
772 }
773 if property == "defined" {
774 return e.isDefined(val), nil
775 }
776
777 // Check custom virtual methods
778 if method, ok := e.renderer.getVirtualMethod(property); ok {
779 result, _ := method(val)
780 val = result
781 continue
782 }
783
784 // Built-in virtual methods with args (called without parens)
785 switch property {
786 case "get":
787 // get() called without args - return nil (requires args)
788 val = nil
789 continue
790 }
791
792 // Built-in virtual methods
793 switch property {
794 case "length", "size":
795 switch v := val.(type) {
796 case string:
797 return len(v), nil
798 case map[string]any:
799 return len(v), nil
800 default:
801 rv := reflect.ValueOf(val)
802 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
803 return rv.Len(), nil
804 }
805 return nil, nil
806 }
807
808 case "first":
809 rv := reflect.ValueOf(val)
810 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
811 if rv.Len() > 0 {
812 val = rv.Index(0).Interface()
813 continue
814 }
815 }
816 return nil, nil
817
818 case "last":
819 rv := reflect.ValueOf(val)
820 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
821 if rv.Len() > 0 {
822 val = rv.Index(rv.Len() - 1).Interface()
823 continue
824 }
825 }
826 return nil, nil
827
828 default:
829 // Try map access
830 if m, ok := val.(map[string]any); ok {
831 if v, exists := m[property]; exists {
832 val = v
833 continue
834 }
835 return nil, nil
836 }
837
838 // Try struct field access via reflection
839 rv := reflect.ValueOf(val)
840 if rv.Kind() == reflect.Ptr {
841 rv = rv.Elem()
842 }
843 if rv.Kind() == reflect.Struct {
844 field := rv.FieldByName(property)
845 if field.IsValid() {
846 val = field.Interface()
847 continue
848 }
849 }
850
851 return nil, nil
852 }
853 }
854
855 return val, nil
856}
857
858// ---- Helper methods ----
859
860// withVar creates a new evaluator with an additional variable
861func (e *Evaluator) withVar(name string, value any) *Evaluator {
862 newVars := e.copyVars()
863 newVars[name] = value
864 eval := NewEvaluator(e.renderer, newVars)
865 for k, v := range e.blocks {
866 eval.blocks[k] = v
867 }
868 return eval
869}
870
871// copyVars creates a shallow copy of the variables map
872func (e *Evaluator) copyVars() map[string]any {
873 newVars := make(map[string]any, len(e.vars))
874 for k, v := range e.vars {
875 newVars[k] = v
876 }
877 return newVars
878}
879
880// isTruthy determines if a value is considered "true"
881func (e *Evaluator) isTruthy(val any) bool {
882 if val == nil {
883 return false
884 }
885
886 switch v := val.(type) {
887 case bool:
888 return v
889 case string:
890 return v != ""
891 case int:
892 return v != 0
893 case int64:
894 return v != 0
895 case float64:
896 return v != 0
897 case float32:
898 return v != 0
899 default:
900 rv := reflect.ValueOf(val)
901 switch rv.Kind() {
902 case reflect.Slice, reflect.Array:
903 return rv.Len() > 0
904 case reflect.Map:
905 return rv.Len() > 0
906 }
907 return true
908 }
909}
910
911// isDefined checks if a value is "defined" (non-nil and non-empty for strings)
912func (e *Evaluator) isDefined(val any) bool {
913 if val == nil {
914 return false
915 }
916
917 switch v := val.(type) {
918 case string:
919 return v != ""
920 case map[string]any:
921 return v != nil
922 default:
923 rv := reflect.ValueOf(val)
924 if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
925 return !rv.IsNil()
926 }
927 return true
928 }
929}
930
931// isString checks if a value is a string
932func (e *Evaluator) isString(v any) bool {
933 _, ok := v.(string)
934 return ok
935}
936
937// toString converts a value to string
938func (e *Evaluator) toString(v any) string {
939 switch val := v.(type) {
940 case string:
941 return val
942 case nil:
943 return ""
944 case bool:
945 if val {
946 return "true"
947 }
948 return "false"
949 case float64:
950 if val == float64(int64(val)) {
951 return strconv.FormatInt(int64(val), 10)
952 }
953 return strconv.FormatFloat(val, 'f', -1, 64)
954 case int:
955 return strconv.Itoa(val)
956 case int64:
957 return strconv.FormatInt(val, 10)
958 default:
959 return fmt.Sprintf("%v", val)
960 }
961}
962
963// toFloat converts a value to float64
964func (e *Evaluator) toFloat(v any) float64 {
965 switch val := v.(type) {
966 case float64:
967 return val
968 case float32:
969 return float64(val)
970 case int:
971 return float64(val)
972 case int64:
973 return float64(val)
974 case int32:
975 return float64(val)
976 case string:
977 f, _ := strconv.ParseFloat(val, 64)
978 return f
979 default:
980 return 0
981 }
982}
983
984// equals compares two values for equality
985func (e *Evaluator) equals(left, right any) bool {
986 // Try numeric comparison
987 leftNum, leftOk := e.tryFloat(left)
988 rightNum, rightOk := e.tryFloat(right)
989 if leftOk && rightOk {
990 return leftNum == rightNum
991 }
992
993 // Fall back to string comparison
994 return fmt.Sprintf("%v", left) == fmt.Sprintf("%v", right)
995}
996
997// tryFloat attempts to convert a value to float64
998func (e *Evaluator) tryFloat(v any) (float64, bool) {
999 switch val := v.(type) {
1000 case float64:
1001 return val, true
1002 case float32:
1003 return float64(val), true
1004 case int:
1005 return float64(val), true
1006 case int64:
1007 return float64(val), true
1008 case int32:
1009 return float64(val), true
1010 case string:
1011 if f, err := strconv.ParseFloat(val, 64); err == nil {
1012 return f, true
1013 }
1014 }
1015 return 0, false
1016}
1017
1018// ---- Error type ----
1019
1020// EvalError represents an error during evaluation
1021type EvalError struct {
1022 Pos Position
1023 Message string
1024}
1025
1026func (e *EvalError) Error() string {
1027 return fmt.Sprintf("line %d, column %d: %s", e.Pos.Line, e.Pos.Column, e.Message)
1028}
1029
1030// ParseError represents an error during parsing
1031type ParseError struct {
1032 Pos Position
1033 Message string
1034}
1035
1036func (e *ParseError) Error() string {
1037 return fmt.Sprintf("parse error at line %d, column %d: %s", e.Pos.Line, e.Pos.Column, e.Message)
1038}