ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/aya/vendor/github.com/eknkc/amber/compiler.go
Revision: 1.1
Committed: Mon Sep 30 00:42:06 2024 UTC (6 weeks, 4 days ago) by yakumo_izuru
Branch: MAIN
CVS Tags: HEAD
Log Message:
Mirrored from https://git.chaotic.ninja/git/yakumo_izuru/aya

File Contents

# User Rev Content
1 yakumo_izuru 1.1 package amber
2    
3     import (
4     "bytes"
5     "container/list"
6     "errors"
7     "fmt"
8     "go/ast"
9     gp "go/parser"
10     gt "go/token"
11     "html/template"
12     "io"
13     "net/http"
14     "os"
15     "path/filepath"
16     "reflect"
17     "regexp"
18     "sort"
19     "strconv"
20     "strings"
21    
22     "github.com/eknkc/amber/parser"
23     )
24    
25     var builtinFunctions = [...]string{
26     "len",
27     "print",
28     "printf",
29     "println",
30     "urlquery",
31     "js",
32     "json",
33     "index",
34     "html",
35     "unescaped",
36     }
37    
38     const (
39     dollar = "__DOLLAR__"
40     )
41    
42     // Compiler is the main interface of Amber Template Engine.
43     // In order to use an Amber template, it is required to create a Compiler and
44     // compile an Amber source to native Go template.
45     // compiler := amber.New()
46     // // Parse the input file
47     // err := compiler.ParseFile("./input.amber")
48     // if err == nil {
49     // // Compile input file to Go template
50     // tpl, err := compiler.Compile()
51     // if err == nil {
52     // // Check built in html/template documentation for further details
53     // tpl.Execute(os.Stdout, somedata)
54     // }
55     // }
56     type Compiler struct {
57     // Compiler options
58     Options
59     filename string
60     node parser.Node
61     indentLevel int
62     newline bool
63     buffer *bytes.Buffer
64     tempvarIndex int
65     mixins map[string]*parser.Mixin
66     }
67    
68     // New creates and initialize a new Compiler.
69     func New() *Compiler {
70     compiler := new(Compiler)
71     compiler.filename = ""
72     compiler.tempvarIndex = 0
73     compiler.PrettyPrint = true
74     compiler.Options = DefaultOptions
75     compiler.mixins = make(map[string]*parser.Mixin)
76    
77     return compiler
78     }
79    
80     // Options defines template output behavior.
81     type Options struct {
82     // Setting if pretty printing is enabled.
83     // Pretty printing ensures that the output html is properly indented and in human readable form.
84     // If disabled, produced HTML is compact. This might be more suitable in production environments.
85     // Default: true
86     PrettyPrint bool
87     // Setting if line number emitting is enabled
88     // In this form, Amber emits line number comments in the output template. It is usable in debugging environments.
89     // Default: false
90     LineNumbers bool
91     // Setting the virtual filesystem to use
92     // If set, will attempt to use a virtual filesystem provided instead of os.
93     // Default: nil
94     VirtualFilesystem http.FileSystem
95     }
96    
97     // DirOptions is used to provide options to directory compilation.
98     type DirOptions struct {
99     // File extension to match for compilation
100     Ext string
101     // Whether or not to walk subdirectories
102     Recursive bool
103     }
104    
105     // DefaultOptions sets pretty-printing to true and line numbering to false.
106     var DefaultOptions = Options{true, false, nil}
107    
108     // DefaultDirOptions sets expected file extension to ".amber" and recursive search for templates within a directory to true.
109     var DefaultDirOptions = DirOptions{".amber", true}
110    
111     // Compile parses and compiles the supplied amber template string. Returns corresponding Go Template (html/templates) instance.
112     // Necessary runtime functions will be injected and the template will be ready to be executed.
113     func Compile(input string, options Options) (*template.Template, error) {
114     comp := New()
115     comp.Options = options
116    
117     err := comp.Parse(input)
118     if err != nil {
119     return nil, err
120     }
121    
122     return comp.Compile()
123     }
124    
125     // Compile parses and compiles the supplied amber template []byte.
126     // Returns corresponding Go Template (html/templates) instance.
127     // Necessary runtime functions will be injected and the template will be ready to be executed.
128     func CompileData(input []byte, filename string, options Options) (*template.Template, error) {
129     comp := New()
130     comp.Options = options
131    
132     err := comp.ParseData(input, filename)
133     if err != nil {
134     return nil, err
135     }
136    
137     return comp.Compile()
138     }
139    
140     // MustCompile is the same as Compile, except the input is assumed error free. If else, panic.
141     func MustCompile(input string, options Options) *template.Template {
142     t, err := Compile(input, options)
143     if err != nil {
144     panic(err)
145     }
146     return t
147     }
148    
149     // CompileFile parses and compiles the contents of supplied filename. Returns corresponding Go Template (html/templates) instance.
150     // Necessary runtime functions will be injected and the template will be ready to be executed.
151     func CompileFile(filename string, options Options) (*template.Template, error) {
152     comp := New()
153     comp.Options = options
154    
155     err := comp.ParseFile(filename)
156     if err != nil {
157     return nil, err
158     }
159    
160     return comp.Compile()
161     }
162    
163     // MustCompileFile is the same as CompileFile, except the input is assumed error free. If else, panic.
164     func MustCompileFile(filename string, options Options) *template.Template {
165     t, err := CompileFile(filename, options)
166     if err != nil {
167     panic(err)
168     }
169     return t
170     }
171    
172     // CompileDir parses and compiles the contents of a supplied directory path, with options.
173     // Returns a map of a template identifier (key) to a Go Template instance.
174     // Ex: if the dirname="templates/" had a file "index.amber" the key would be "index"
175     // If option for recursive is True, this parses every file of relevant extension
176     // in all subdirectories. The key then is the path e.g: "layouts/layout"
177     func CompileDir(dirname string, dopt DirOptions, opt Options) (map[string]*template.Template, error) {
178     dir, err := os.Open(dirname)
179     if err != nil && opt.VirtualFilesystem != nil {
180     vdir, err := opt.VirtualFilesystem.Open(dirname)
181     if err != nil {
182     return nil, err
183     }
184     dir = vdir.(*os.File)
185     } else if err != nil {
186     return nil, err
187     }
188     defer dir.Close()
189    
190     files, err := dir.Readdir(0)
191     if err != nil {
192     return nil, err
193     }
194    
195     compiled := make(map[string]*template.Template)
196     for _, file := range files {
197     // filename is for example "index.amber"
198     filename := file.Name()
199     fileext := filepath.Ext(filename)
200    
201     // If recursive is true and there's a subdirectory, recurse
202     if dopt.Recursive && file.IsDir() {
203     dirpath := filepath.Join(dirname, filename)
204     subcompiled, err := CompileDir(dirpath, dopt, opt)
205     if err != nil {
206     return nil, err
207     }
208     // Copy templates from subdirectory into parent template mapping
209     for k, v := range subcompiled {
210     // Concat with parent directory name for unique paths
211     key := filepath.Join(filename, k)
212     compiled[key] = v
213     }
214     } else if fileext == dopt.Ext {
215     // Otherwise compile the file and add to mapping
216     fullpath := filepath.Join(dirname, filename)
217     tmpl, err := CompileFile(fullpath, opt)
218     if err != nil {
219     return nil, err
220     }
221     // Strip extension
222     key := filename[0 : len(filename)-len(fileext)]
223     compiled[key] = tmpl
224     }
225     }
226    
227     return compiled, nil
228     }
229    
230     // MustCompileDir is the same as CompileDir, except input is assumed error free. If else, panic.
231     func MustCompileDir(dirname string, dopt DirOptions, opt Options) map[string]*template.Template {
232     m, err := CompileDir(dirname, dopt, opt)
233     if err != nil {
234     panic(err)
235     }
236     return m
237     }
238    
239     // Parse given raw amber template string.
240     func (c *Compiler) Parse(input string) (err error) {
241     defer func() {
242     if r := recover(); r != nil {
243     err = errors.New(r.(string))
244     }
245     }()
246    
247     parser, err := parser.StringParser(input)
248    
249     if err != nil {
250     return
251     }
252    
253     c.node = parser.Parse()
254     return
255     }
256    
257     // Parse given raw amber template bytes, and the filename that belongs with it
258     func (c *Compiler) ParseData(input []byte, filename string) (err error) {
259     defer func() {
260     if r := recover(); r != nil {
261     err = errors.New(r.(string))
262     }
263     }()
264    
265     parser, err := parser.ByteParser(input)
266     parser.SetFilename(filename)
267     if c.VirtualFilesystem != nil {
268     parser.SetVirtualFilesystem(c.VirtualFilesystem)
269     }
270    
271     if err != nil {
272     return
273     }
274    
275     c.node = parser.Parse()
276     return
277     }
278    
279     // ParseFile parses the amber template file in given path.
280     func (c *Compiler) ParseFile(filename string) (err error) {
281     defer func() {
282     if r := recover(); r != nil {
283     err = errors.New(r.(string))
284     }
285     }()
286    
287     p, err := parser.FileParser(filename)
288     if err != nil && c.VirtualFilesystem != nil {
289     p, err = parser.VirtualFileParser(filename, c.VirtualFilesystem)
290     }
291     if err != nil {
292     return
293     }
294    
295     c.node = p.Parse()
296     c.filename = filename
297     return
298     }
299    
300     // Compile amber and create a Go Template (html/templates) instance.
301     // Necessary runtime functions will be injected and the template will be ready to be executed.
302     func (c *Compiler) Compile() (*template.Template, error) {
303     return c.CompileWithName(filepath.Base(c.filename))
304     }
305    
306     // CompileWithName is the same as Compile, but allows to specify a name for the template.
307     func (c *Compiler) CompileWithName(name string) (*template.Template, error) {
308     return c.CompileWithTemplate(template.New(name))
309     }
310    
311     // CompileWithTemplate is the same as Compile but allows to specify a template.
312     func (c *Compiler) CompileWithTemplate(t *template.Template) (*template.Template, error) {
313     data, err := c.CompileString()
314    
315     if err != nil {
316     return nil, err
317     }
318    
319     tpl, err := t.Funcs(FuncMap).Parse(data)
320    
321     if err != nil {
322     return nil, err
323     }
324    
325     return tpl, nil
326     }
327    
328     // CompileWriter compiles amber and writes the Go Template source into given io.Writer instance.
329     // You would not be using this unless debugging / checking the output. Please use Compile
330     // method to obtain a template instance directly.
331     func (c *Compiler) CompileWriter(out io.Writer) (err error) {
332     defer func() {
333     if r := recover(); r != nil {
334     err = errors.New(r.(string))
335     }
336     }()
337    
338     c.buffer = new(bytes.Buffer)
339     c.visit(c.node)
340    
341     if c.buffer.Len() > 0 {
342     c.write("\n")
343     }
344    
345     _, err = c.buffer.WriteTo(out)
346     return
347     }
348    
349     // CompileString compiles the template and returns the Go Template source.
350     // You would not be using this unless debugging / checking the output. Please use Compile
351     // method to obtain a template instance directly.
352     func (c *Compiler) CompileString() (string, error) {
353     var buf bytes.Buffer
354    
355     if err := c.CompileWriter(&buf); err != nil {
356     return "", err
357     }
358    
359     result := buf.String()
360    
361     return result, nil
362     }
363    
364     func (c *Compiler) visit(node parser.Node) {
365     defer func() {
366     if r := recover(); r != nil {
367     if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" {
368     panic(r)
369     }
370    
371     pos := node.Pos()
372    
373     if len(pos.Filename) > 0 {
374     panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength))
375     } else {
376     panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength))
377     }
378     }
379     }()
380    
381     switch node.(type) {
382     case *parser.Block:
383     c.visitBlock(node.(*parser.Block))
384     case *parser.Doctype:
385     c.visitDoctype(node.(*parser.Doctype))
386     case *parser.Comment:
387     c.visitComment(node.(*parser.Comment))
388     case *parser.Tag:
389     c.visitTag(node.(*parser.Tag))
390     case *parser.Text:
391     c.visitText(node.(*parser.Text))
392     case *parser.Condition:
393     c.visitCondition(node.(*parser.Condition))
394     case *parser.Each:
395     c.visitEach(node.(*parser.Each))
396     case *parser.Assignment:
397     c.visitAssignment(node.(*parser.Assignment))
398     case *parser.Mixin:
399     c.visitMixin(node.(*parser.Mixin))
400     case *parser.MixinCall:
401     c.visitMixinCall(node.(*parser.MixinCall))
402     }
403     }
404    
405     func (c *Compiler) write(value string) {
406     c.buffer.WriteString(value)
407     }
408    
409     func (c *Compiler) indent(offset int, newline bool) {
410     if !c.PrettyPrint {
411     return
412     }
413    
414     if newline && c.buffer.Len() > 0 {
415     c.write("\n")
416     }
417    
418     for i := 0; i < c.indentLevel+offset; i++ {
419     c.write("\t")
420     }
421     }
422    
423     func (c *Compiler) tempvar() string {
424     c.tempvarIndex++
425     return "$__amber_" + strconv.Itoa(c.tempvarIndex)
426     }
427    
428     func (c *Compiler) escape(input string) string {
429     return strings.Replace(strings.Replace(input, `\`, `\\`, -1), `"`, `\"`, -1)
430     }
431    
432     func (c *Compiler) visitBlock(block *parser.Block) {
433     for _, node := range block.Children {
434     if _, ok := node.(*parser.Text); !block.CanInline() && ok {
435     c.indent(0, true)
436     }
437    
438     c.visit(node)
439     }
440     }
441    
442     func (c *Compiler) visitDoctype(doctype *parser.Doctype) {
443     c.write(doctype.String())
444     }
445    
446     func (c *Compiler) visitComment(comment *parser.Comment) {
447     if comment.Silent {
448     return
449     }
450    
451     c.indent(0, false)
452    
453     if comment.Block == nil {
454     c.write(`{{unescaped "<!-- ` + c.escape(comment.Value) + ` -->"}}`)
455     } else {
456     c.write(`<!-- ` + comment.Value)
457     c.visitBlock(comment.Block)
458     c.write(` -->`)
459     }
460     }
461    
462     func (c *Compiler) visitCondition(condition *parser.Condition) {
463     c.write(`{{if ` + c.visitRawInterpolation(condition.Expression) + `}}`)
464     c.visitBlock(condition.Positive)
465     if condition.Negative != nil {
466     c.write(`{{else}}`)
467     c.visitBlock(condition.Negative)
468     }
469     c.write(`{{end}}`)
470     }
471    
472     func (c *Compiler) visitEach(each *parser.Each) {
473     if each.Block == nil {
474     return
475     }
476    
477     if len(each.Y) == 0 {
478     c.write(`{{range ` + each.X + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`)
479     } else {
480     c.write(`{{range ` + each.X + `, ` + each.Y + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`)
481     }
482     c.visitBlock(each.Block)
483     c.write(`{{end}}`)
484     }
485    
486     func (c *Compiler) visitAssignment(assgn *parser.Assignment) {
487     c.write(`{{` + assgn.X + ` := ` + c.visitRawInterpolation(assgn.Expression) + `}}`)
488     }
489    
490     func (c *Compiler) visitTag(tag *parser.Tag) {
491     type attrib struct {
492     name string
493     value func() string
494     condition string
495     }
496    
497     attribs := make(map[string]*attrib)
498    
499     for _, item := range tag.Attributes {
500     attritem := item
501     attr := new(attrib)
502     attr.name = item.Name
503    
504     attr.value = func() string {
505     if !attritem.IsRaw {
506     return c.visitInterpolation(attritem.Value)
507     } else if attritem.Value == "" {
508     return ""
509     } else {
510     return attritem.Value
511     }
512     }
513    
514     if len(attritem.Condition) != 0 {
515     attr.condition = c.visitRawInterpolation(attritem.Condition)
516     }
517    
518     if attr.name == "class" && attribs["class"] != nil {
519     prevclass := attribs["class"]
520     prevvalue := prevclass.value
521    
522     prevclass.value = func() string {
523     aval := attr.value()
524    
525     if len(attr.condition) > 0 {
526     aval = `{{if ` + attr.condition + `}}` + aval + `{{end}}`
527     }
528    
529     if len(prevclass.condition) > 0 {
530     return `{{if ` + prevclass.condition + `}}` + prevvalue() + `{{end}} ` + aval
531     }
532    
533     return prevvalue() + " " + aval
534     }
535     } else {
536     attribs[attritem.Name] = attr
537     }
538     }
539    
540     keys := make([]string, 0, len(attribs))
541     for key := range attribs {
542     keys = append(keys, key)
543     }
544     sort.Strings(keys)
545    
546     c.indent(0, true)
547     c.write("<" + tag.Name)
548    
549     for _, name := range keys {
550     value := attribs[name]
551    
552     if len(value.condition) > 0 {
553     c.write(`{{if ` + value.condition + `}}`)
554     }
555    
556     val := value.value()
557    
558     if val == "" {
559     c.write(` ` + name)
560     } else {
561     c.write(` ` + name + `="` + val + `"`)
562     }
563    
564     if len(value.condition) > 0 {
565     c.write(`{{end}}`)
566     }
567     }
568    
569     if tag.IsSelfClosing() {
570     c.write(` />`)
571     } else {
572     c.write(`>`)
573    
574     if tag.Block != nil {
575     if !tag.Block.CanInline() {
576     c.indentLevel++
577     }
578    
579     c.visitBlock(tag.Block)
580    
581     if !tag.Block.CanInline() {
582     c.indentLevel--
583     c.indent(0, true)
584     }
585     }
586    
587     c.write(`</` + tag.Name + `>`)
588     }
589     }
590    
591     var textInterpolateRegexp = regexp.MustCompile(`#\{(.*?)\}`)
592     var textEscapeRegexp = regexp.MustCompile(`\{\{(.*?)\}\}`)
593    
594     func (c *Compiler) visitText(txt *parser.Text) {
595     value := textEscapeRegexp.ReplaceAllStringFunc(txt.Value, func(value string) string {
596     return `{{"{{"}}` + value[2:len(value)-2] + `{{"}}"}}`
597     })
598    
599     value = textInterpolateRegexp.ReplaceAllStringFunc(value, func(value string) string {
600     return c.visitInterpolation(value[2 : len(value)-1])
601     })
602    
603     lines := strings.Split(value, "\n")
604     for i := 0; i < len(lines); i++ {
605     c.write(lines[i])
606    
607     if i < len(lines)-1 {
608     c.write("\n")
609     c.indent(0, false)
610     }
611     }
612     }
613    
614     func (c *Compiler) visitInterpolation(value string) string {
615     return `{{` + c.visitRawInterpolation(value) + `}}`
616     }
617    
618     func (c *Compiler) visitRawInterpolation(value string) string {
619     if value == "" {
620     value = "\"\""
621     }
622    
623     value = strings.Replace(value, "$", dollar, -1)
624     expr, err := gp.ParseExpr(value)
625     if err != nil {
626     panic("Unable to parse expression.")
627     }
628     value = strings.Replace(c.visitExpression(expr), dollar, "$", -1)
629     return value
630     }
631    
632     func (c *Compiler) visitExpression(outerexpr ast.Expr) string {
633     stack := list.New()
634    
635     pop := func() string {
636     if stack.Front() == nil {
637     return ""
638     }
639    
640     val := stack.Front().Value.(string)
641     stack.Remove(stack.Front())
642     return val
643     }
644    
645     var exec func(ast.Expr)
646    
647     exec = func(expr ast.Expr) {
648     switch expr := expr.(type) {
649     case *ast.BinaryExpr:
650     {
651     be := expr
652    
653     exec(be.Y)
654     exec(be.X)
655    
656     negate := false
657     name := c.tempvar()
658     c.write(`{{` + name + ` := `)
659    
660     switch be.Op {
661     case gt.ADD:
662     c.write("__amber_add ")
663     case gt.SUB:
664     c.write("__amber_sub ")
665     case gt.MUL:
666     c.write("__amber_mul ")
667     case gt.QUO:
668     c.write("__amber_quo ")
669     case gt.REM:
670     c.write("__amber_rem ")
671     case gt.LAND:
672     c.write("and ")
673     case gt.LOR:
674     c.write("or ")
675     case gt.EQL:
676     c.write("__amber_eql ")
677     case gt.NEQ:
678     c.write("__amber_eql ")
679     negate = true
680     case gt.LSS:
681     c.write("__amber_lss ")
682     case gt.GTR:
683     c.write("__amber_gtr ")
684     case gt.LEQ:
685     c.write("__amber_gtr ")
686     negate = true
687     case gt.GEQ:
688     c.write("__amber_lss ")
689     negate = true
690     default:
691     panic("Unexpected operator!")
692     }
693    
694     c.write(pop() + ` ` + pop() + `}}`)
695    
696     if !negate {
697     stack.PushFront(name)
698     } else {
699     negname := c.tempvar()
700     c.write(`{{` + negname + ` := not ` + name + `}}`)
701     stack.PushFront(negname)
702     }
703     }
704     case *ast.UnaryExpr:
705     {
706     ue := expr
707    
708     exec(ue.X)
709    
710     name := c.tempvar()
711     c.write(`{{` + name + ` := `)
712    
713     switch ue.Op {
714     case gt.SUB:
715     c.write("__amber_minus ")
716     case gt.ADD:
717     c.write("__amber_plus ")
718     case gt.NOT:
719     c.write("not ")
720     default:
721     panic("Unexpected operator!")
722     }
723    
724     c.write(pop() + `}}`)
725     stack.PushFront(name)
726     }
727     case *ast.ParenExpr:
728     exec(expr.X)
729     case *ast.BasicLit:
730     stack.PushFront(strings.Replace(expr.Value, dollar, "$", -1))
731     case *ast.Ident:
732     name := expr.Name
733     if len(name) >= len(dollar) && name[:len(dollar)] == dollar {
734     if name == dollar {
735     stack.PushFront(`.`)
736     } else {
737     stack.PushFront(`$` + expr.Name[len(dollar):])
738     }
739     } else {
740     stack.PushFront(`.` + expr.Name)
741     }
742     case *ast.SelectorExpr:
743     se := expr
744     exec(se.X)
745     x := pop()
746    
747     if x == "." {
748     x = ""
749     }
750    
751     name := c.tempvar()
752     c.write(`{{` + name + ` := ` + x + `.` + se.Sel.Name + `}}`)
753     stack.PushFront(name)
754     case *ast.CallExpr:
755     ce := expr
756    
757     for i := len(ce.Args) - 1; i >= 0; i-- {
758     exec(ce.Args[i])
759     }
760    
761     name := c.tempvar()
762     builtin := false
763    
764     if ident, ok := ce.Fun.(*ast.Ident); ok {
765     for _, fname := range builtinFunctions {
766     if fname == ident.Name {
767     builtin = true
768     break
769     }
770     }
771     for fname, _ := range FuncMap {
772     if fname == ident.Name {
773     builtin = true
774     break
775     }
776     }
777     }
778    
779     if builtin {
780     stack.PushFront(ce.Fun.(*ast.Ident).Name)
781     c.write(`{{` + name + ` := ` + pop())
782     } else if se, ok := ce.Fun.(*ast.SelectorExpr); ok {
783     exec(se.X)
784     x := pop()
785    
786     if x == "." {
787     x = ""
788     }
789     stack.PushFront(se.Sel.Name)
790     c.write(`{{` + name + ` := ` + x + `.` + pop())
791     } else {
792     exec(ce.Fun)
793     c.write(`{{` + name + ` := call ` + pop())
794     }
795    
796     for i := 0; i < len(ce.Args); i++ {
797     c.write(` `)
798     c.write(pop())
799     }
800    
801     c.write(`}}`)
802    
803     stack.PushFront(name)
804     default:
805     panic("Unable to parse expression. Unsupported: " + reflect.TypeOf(expr).String())
806     }
807     }
808    
809     exec(outerexpr)
810     return pop()
811     }
812    
813     func (c *Compiler) visitMixin(mixin *parser.Mixin) {
814     c.mixins[mixin.Name] = mixin
815     }
816    
817     func (c *Compiler) visitMixinCall(mixinCall *parser.MixinCall) {
818     mixin := c.mixins[mixinCall.Name]
819    
820     switch {
821     case mixin == nil:
822     panic(fmt.Sprintf("unknown mixin %q", mixinCall.Name))
823    
824     case len(mixinCall.Args) < len(mixin.Args):
825     panic(fmt.Sprintf(
826     "not enough arguments in call to mixin %q (have: %d, want: %d)",
827     mixinCall.Name,
828     len(mixinCall.Args),
829     len(mixin.Args),
830     ))
831     case len(mixinCall.Args) > len(mixin.Args):
832     panic(fmt.Sprintf(
833     "too many arguments in call to mixin %q (have: %d, want: %d)",
834     mixinCall.Name,
835     len(mixinCall.Args),
836     len(mixin.Args),
837     ))
838     }
839    
840     for i, arg := range mixin.Args {
841     c.write(fmt.Sprintf(`{{%s := %s}}`, arg, c.visitRawInterpolation(mixinCall.Args[i])))
842     }
843     c.visitBlock(mixin.Block)
844     }