A go template renderer based on Perl's Template Toolkit
at main 1038 lines 24 kB view raw
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}