A go template renderer based on Perl's Template Toolkit
at main 22 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 *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}