diff vendor/github.com/eknkc/amber/compiler.go @ 66:787b5ee0289d draft

Use vendored modules Signed-off-by: Izuru Yakumo <yakumo.izuru@chaotic.ninja>
author yakumo.izuru
date Sun, 23 Jul 2023 13:18:53 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/github.com/eknkc/amber/compiler.go	Sun Jul 23 13:18:53 2023 +0000
@@ -0,0 +1,844 @@
+package amber
+
+import (
+	"bytes"
+	"container/list"
+	"errors"
+	"fmt"
+	"go/ast"
+	gp "go/parser"
+	gt "go/token"
+	"html/template"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+
+	"github.com/eknkc/amber/parser"
+)
+
+var builtinFunctions = [...]string{
+	"len",
+	"print",
+	"printf",
+	"println",
+	"urlquery",
+	"js",
+	"json",
+	"index",
+	"html",
+	"unescaped",
+}
+
+const (
+	dollar = "__DOLLAR__"
+)
+
+// Compiler is the main interface of Amber Template Engine.
+// In order to use an Amber template, it is required to create a Compiler and
+// compile an Amber source to native Go template.
+//	compiler := amber.New()
+// 	// Parse the input file
+//	err := compiler.ParseFile("./input.amber")
+//	if err == nil {
+//		// Compile input file to Go template
+//		tpl, err := compiler.Compile()
+//		if err == nil {
+//			// Check built in html/template documentation for further details
+//			tpl.Execute(os.Stdout, somedata)
+//		}
+//	}
+type Compiler struct {
+	// Compiler options
+	Options
+	filename     string
+	node         parser.Node
+	indentLevel  int
+	newline      bool
+	buffer       *bytes.Buffer
+	tempvarIndex int
+	mixins       map[string]*parser.Mixin
+}
+
+// New creates and initialize a new Compiler.
+func New() *Compiler {
+	compiler := new(Compiler)
+	compiler.filename = ""
+	compiler.tempvarIndex = 0
+	compiler.PrettyPrint = true
+	compiler.Options = DefaultOptions
+	compiler.mixins = make(map[string]*parser.Mixin)
+
+	return compiler
+}
+
+// Options defines template output behavior.
+type Options struct {
+	// Setting if pretty printing is enabled.
+	// Pretty printing ensures that the output html is properly indented and in human readable form.
+	// If disabled, produced HTML is compact. This might be more suitable in production environments.
+	// Default: true
+	PrettyPrint bool
+	// Setting if line number emitting is enabled
+	// In this form, Amber emits line number comments in the output template. It is usable in debugging environments.
+	// Default: false
+	LineNumbers bool
+	// Setting the virtual filesystem to use
+	// If set, will attempt to use a virtual filesystem provided instead of os.
+	// Default: nil
+	VirtualFilesystem http.FileSystem
+}
+
+// DirOptions is used to provide options to directory compilation.
+type DirOptions struct {
+	// File extension to match for compilation
+	Ext string
+	// Whether or not to walk subdirectories
+	Recursive bool
+}
+
+// DefaultOptions sets pretty-printing to true and line numbering to false.
+var DefaultOptions = Options{true, false, nil}
+
+// DefaultDirOptions sets expected file extension to ".amber" and recursive search for templates within a directory to true.
+var DefaultDirOptions = DirOptions{".amber", true}
+
+// Compile parses and compiles the supplied amber template string. Returns corresponding Go Template (html/templates) instance.
+// Necessary runtime functions will be injected and the template will be ready to be executed.
+func Compile(input string, options Options) (*template.Template, error) {
+	comp := New()
+	comp.Options = options
+
+	err := comp.Parse(input)
+	if err != nil {
+		return nil, err
+	}
+
+	return comp.Compile()
+}
+
+// Compile parses and compiles the supplied amber template []byte.
+// Returns corresponding Go Template (html/templates) instance.
+// Necessary runtime functions will be injected and the template will be ready to be executed.
+func CompileData(input []byte, filename string, options Options) (*template.Template, error) {
+	comp := New()
+	comp.Options = options
+
+	err := comp.ParseData(input, filename)
+	if err != nil {
+		return nil, err
+	}
+
+	return comp.Compile()
+}
+
+// MustCompile is the same as Compile, except the input is assumed error free. If else, panic.
+func MustCompile(input string, options Options) *template.Template {
+	t, err := Compile(input, options)
+	if err != nil {
+		panic(err)
+	}
+	return t
+}
+
+// CompileFile parses and compiles the contents of supplied filename. Returns corresponding Go Template (html/templates) instance.
+// Necessary runtime functions will be injected and the template will be ready to be executed.
+func CompileFile(filename string, options Options) (*template.Template, error) {
+	comp := New()
+	comp.Options = options
+
+	err := comp.ParseFile(filename)
+	if err != nil {
+		return nil, err
+	}
+
+	return comp.Compile()
+}
+
+// MustCompileFile is the same as CompileFile, except the input is assumed error free. If else, panic.
+func MustCompileFile(filename string, options Options) *template.Template {
+	t, err := CompileFile(filename, options)
+	if err != nil {
+		panic(err)
+	}
+	return t
+}
+
+// CompileDir parses and compiles the contents of a supplied directory path, with options.
+// Returns a map of a template identifier (key) to a Go Template instance.
+// Ex: if the dirname="templates/" had a file "index.amber" the key would be "index"
+// If option for recursive is True, this parses every file of relevant extension
+// in all subdirectories. The key then is the path e.g: "layouts/layout"
+func CompileDir(dirname string, dopt DirOptions, opt Options) (map[string]*template.Template, error) {
+	dir, err := os.Open(dirname)
+	if err != nil && opt.VirtualFilesystem != nil {
+		vdir, err := opt.VirtualFilesystem.Open(dirname)
+		if err != nil {
+			return nil, err
+		}
+		dir = vdir.(*os.File)
+	} else if err != nil {
+		return nil, err
+	}
+	defer dir.Close()
+
+	files, err := dir.Readdir(0)
+	if err != nil {
+		return nil, err
+	}
+
+	compiled := make(map[string]*template.Template)
+	for _, file := range files {
+		// filename is for example "index.amber"
+		filename := file.Name()
+		fileext := filepath.Ext(filename)
+
+		// If recursive is true and there's a subdirectory, recurse
+		if dopt.Recursive && file.IsDir() {
+			dirpath := filepath.Join(dirname, filename)
+			subcompiled, err := CompileDir(dirpath, dopt, opt)
+			if err != nil {
+				return nil, err
+			}
+			// Copy templates from subdirectory into parent template mapping
+			for k, v := range subcompiled {
+				// Concat with parent directory name for unique paths
+				key := filepath.Join(filename, k)
+				compiled[key] = v
+			}
+		} else if fileext == dopt.Ext {
+			// Otherwise compile the file and add to mapping
+			fullpath := filepath.Join(dirname, filename)
+			tmpl, err := CompileFile(fullpath, opt)
+			if err != nil {
+				return nil, err
+			}
+			// Strip extension
+			key := filename[0 : len(filename)-len(fileext)]
+			compiled[key] = tmpl
+		}
+	}
+
+	return compiled, nil
+}
+
+// MustCompileDir is the same as CompileDir, except input is assumed error free. If else, panic.
+func MustCompileDir(dirname string, dopt DirOptions, opt Options) map[string]*template.Template {
+	m, err := CompileDir(dirname, dopt, opt)
+	if err != nil {
+		panic(err)
+	}
+	return m
+}
+
+// Parse given raw amber template string.
+func (c *Compiler) Parse(input string) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = errors.New(r.(string))
+		}
+	}()
+
+	parser, err := parser.StringParser(input)
+
+	if err != nil {
+		return
+	}
+
+	c.node = parser.Parse()
+	return
+}
+
+// Parse given raw amber template bytes, and the filename that belongs with it
+func (c *Compiler) ParseData(input []byte, filename string) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = errors.New(r.(string))
+		}
+	}()
+
+	parser, err := parser.ByteParser(input)
+	parser.SetFilename(filename)
+	if c.VirtualFilesystem != nil {
+		parser.SetVirtualFilesystem(c.VirtualFilesystem)
+	}
+
+	if err != nil {
+		return
+	}
+
+	c.node = parser.Parse()
+	return
+}
+
+// ParseFile parses the amber template file in given path.
+func (c *Compiler) ParseFile(filename string) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = errors.New(r.(string))
+		}
+	}()
+
+	p, err := parser.FileParser(filename)
+	if err != nil && c.VirtualFilesystem != nil {
+		p, err = parser.VirtualFileParser(filename, c.VirtualFilesystem)
+	}
+	if err != nil {
+		return
+	}
+
+	c.node = p.Parse()
+	c.filename = filename
+	return
+}
+
+// Compile amber and create a Go Template (html/templates) instance.
+// Necessary runtime functions will be injected and the template will be ready to be executed.
+func (c *Compiler) Compile() (*template.Template, error) {
+	return c.CompileWithName(filepath.Base(c.filename))
+}
+
+// CompileWithName is the same as Compile, but allows to specify a name for the template.
+func (c *Compiler) CompileWithName(name string) (*template.Template, error) {
+	return c.CompileWithTemplate(template.New(name))
+}
+
+// CompileWithTemplate is the same as Compile but allows to specify a template.
+func (c *Compiler) CompileWithTemplate(t *template.Template) (*template.Template, error) {
+	data, err := c.CompileString()
+
+	if err != nil {
+		return nil, err
+	}
+
+	tpl, err := t.Funcs(FuncMap).Parse(data)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return tpl, nil
+}
+
+// CompileWriter compiles amber and writes the Go Template source into given io.Writer instance.
+// You would not be using this unless debugging / checking the output. Please use Compile
+// method to obtain a template instance directly.
+func (c *Compiler) CompileWriter(out io.Writer) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = errors.New(r.(string))
+		}
+	}()
+
+	c.buffer = new(bytes.Buffer)
+	c.visit(c.node)
+
+	if c.buffer.Len() > 0 {
+		c.write("\n")
+	}
+
+	_, err = c.buffer.WriteTo(out)
+	return
+}
+
+// CompileString compiles the template and returns the Go Template source.
+// You would not be using this unless debugging / checking the output. Please use Compile
+// method to obtain a template instance directly.
+func (c *Compiler) CompileString() (string, error) {
+	var buf bytes.Buffer
+
+	if err := c.CompileWriter(&buf); err != nil {
+		return "", err
+	}
+
+	result := buf.String()
+
+	return result, nil
+}
+
+func (c *Compiler) visit(node parser.Node) {
+	defer func() {
+		if r := recover(); r != nil {
+			if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" {
+				panic(r)
+			}
+
+			pos := node.Pos()
+
+			if len(pos.Filename) > 0 {
+				panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength))
+			} else {
+				panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength))
+			}
+		}
+	}()
+
+	switch node.(type) {
+	case *parser.Block:
+		c.visitBlock(node.(*parser.Block))
+	case *parser.Doctype:
+		c.visitDoctype(node.(*parser.Doctype))
+	case *parser.Comment:
+		c.visitComment(node.(*parser.Comment))
+	case *parser.Tag:
+		c.visitTag(node.(*parser.Tag))
+	case *parser.Text:
+		c.visitText(node.(*parser.Text))
+	case *parser.Condition:
+		c.visitCondition(node.(*parser.Condition))
+	case *parser.Each:
+		c.visitEach(node.(*parser.Each))
+	case *parser.Assignment:
+		c.visitAssignment(node.(*parser.Assignment))
+	case *parser.Mixin:
+		c.visitMixin(node.(*parser.Mixin))
+	case *parser.MixinCall:
+		c.visitMixinCall(node.(*parser.MixinCall))
+	}
+}
+
+func (c *Compiler) write(value string) {
+	c.buffer.WriteString(value)
+}
+
+func (c *Compiler) indent(offset int, newline bool) {
+	if !c.PrettyPrint {
+		return
+	}
+
+	if newline && c.buffer.Len() > 0 {
+		c.write("\n")
+	}
+
+	for i := 0; i < c.indentLevel+offset; i++ {
+		c.write("\t")
+	}
+}
+
+func (c *Compiler) tempvar() string {
+	c.tempvarIndex++
+	return "$__amber_" + strconv.Itoa(c.tempvarIndex)
+}
+
+func (c *Compiler) escape(input string) string {
+	return strings.Replace(strings.Replace(input, `\`, `\\`, -1), `"`, `\"`, -1)
+}
+
+func (c *Compiler) visitBlock(block *parser.Block) {
+	for _, node := range block.Children {
+		if _, ok := node.(*parser.Text); !block.CanInline() && ok {
+			c.indent(0, true)
+		}
+
+		c.visit(node)
+	}
+}
+
+func (c *Compiler) visitDoctype(doctype *parser.Doctype) {
+	c.write(doctype.String())
+}
+
+func (c *Compiler) visitComment(comment *parser.Comment) {
+	if comment.Silent {
+		return
+	}
+
+	c.indent(0, false)
+
+	if comment.Block == nil {
+		c.write(`{{unescaped "<!-- ` + c.escape(comment.Value) + ` -->"}}`)
+	} else {
+		c.write(`<!-- ` + comment.Value)
+		c.visitBlock(comment.Block)
+		c.write(` -->`)
+	}
+}
+
+func (c *Compiler) visitCondition(condition *parser.Condition) {
+	c.write(`{{if ` + c.visitRawInterpolation(condition.Expression) + `}}`)
+	c.visitBlock(condition.Positive)
+	if condition.Negative != nil {
+		c.write(`{{else}}`)
+		c.visitBlock(condition.Negative)
+	}
+	c.write(`{{end}}`)
+}
+
+func (c *Compiler) visitEach(each *parser.Each) {
+	if each.Block == nil {
+		return
+	}
+
+	if len(each.Y) == 0 {
+		c.write(`{{range ` + each.X + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`)
+	} else {
+		c.write(`{{range ` + each.X + `, ` + each.Y + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`)
+	}
+	c.visitBlock(each.Block)
+	c.write(`{{end}}`)
+}
+
+func (c *Compiler) visitAssignment(assgn *parser.Assignment) {
+	c.write(`{{` + assgn.X + ` := ` + c.visitRawInterpolation(assgn.Expression) + `}}`)
+}
+
+func (c *Compiler) visitTag(tag *parser.Tag) {
+	type attrib struct {
+		name      string
+		value     func() string
+		condition string
+	}
+
+	attribs := make(map[string]*attrib)
+
+	for _, item := range tag.Attributes {
+		attritem := item
+		attr := new(attrib)
+		attr.name = item.Name
+
+		attr.value = func() string {
+			if !attritem.IsRaw {
+				return c.visitInterpolation(attritem.Value)
+			} else if attritem.Value == "" {
+				return ""
+			} else {
+				return attritem.Value
+			}
+		}
+
+		if len(attritem.Condition) != 0 {
+			attr.condition = c.visitRawInterpolation(attritem.Condition)
+		}
+
+		if attr.name == "class" && attribs["class"] != nil {
+			prevclass := attribs["class"]
+			prevvalue := prevclass.value
+
+			prevclass.value = func() string {
+				aval := attr.value()
+
+				if len(attr.condition) > 0 {
+					aval = `{{if ` + attr.condition + `}}` + aval + `{{end}}`
+				}
+
+				if len(prevclass.condition) > 0 {
+					return `{{if ` + prevclass.condition + `}}` + prevvalue() + `{{end}} ` + aval
+				}
+
+				return prevvalue() + " " + aval
+			}
+		} else {
+			attribs[attritem.Name] = attr
+		}
+	}
+
+	keys := make([]string, 0, len(attribs))
+	for key := range attribs {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+
+	c.indent(0, true)
+	c.write("<" + tag.Name)
+
+	for _, name := range keys {
+		value := attribs[name]
+
+		if len(value.condition) > 0 {
+			c.write(`{{if ` + value.condition + `}}`)
+		}
+
+		val := value.value()
+
+		if val == "" {
+			c.write(` ` + name)
+		} else {
+			c.write(` ` + name + `="` + val + `"`)
+		}
+
+		if len(value.condition) > 0 {
+			c.write(`{{end}}`)
+		}
+	}
+
+	if tag.IsSelfClosing() {
+		c.write(` />`)
+	} else {
+		c.write(`>`)
+
+		if tag.Block != nil {
+			if !tag.Block.CanInline() {
+				c.indentLevel++
+			}
+
+			c.visitBlock(tag.Block)
+
+			if !tag.Block.CanInline() {
+				c.indentLevel--
+				c.indent(0, true)
+			}
+		}
+
+		c.write(`</` + tag.Name + `>`)
+	}
+}
+
+var textInterpolateRegexp = regexp.MustCompile(`#\{(.*?)\}`)
+var textEscapeRegexp = regexp.MustCompile(`\{\{(.*?)\}\}`)
+
+func (c *Compiler) visitText(txt *parser.Text) {
+	value := textEscapeRegexp.ReplaceAllStringFunc(txt.Value, func(value string) string {
+		return `{{"{{"}}` + value[2:len(value)-2] + `{{"}}"}}`
+	})
+
+	value = textInterpolateRegexp.ReplaceAllStringFunc(value, func(value string) string {
+		return c.visitInterpolation(value[2 : len(value)-1])
+	})
+
+	lines := strings.Split(value, "\n")
+	for i := 0; i < len(lines); i++ {
+		c.write(lines[i])
+
+		if i < len(lines)-1 {
+			c.write("\n")
+			c.indent(0, false)
+		}
+	}
+}
+
+func (c *Compiler) visitInterpolation(value string) string {
+	return `{{` + c.visitRawInterpolation(value) + `}}`
+}
+
+func (c *Compiler) visitRawInterpolation(value string) string {
+	if value == "" {
+		value = "\"\""
+	}
+
+	value = strings.Replace(value, "$", dollar, -1)
+	expr, err := gp.ParseExpr(value)
+	if err != nil {
+		panic("Unable to parse expression.")
+	}
+	value = strings.Replace(c.visitExpression(expr), dollar, "$", -1)
+	return value
+}
+
+func (c *Compiler) visitExpression(outerexpr ast.Expr) string {
+	stack := list.New()
+
+	pop := func() string {
+		if stack.Front() == nil {
+			return ""
+		}
+
+		val := stack.Front().Value.(string)
+		stack.Remove(stack.Front())
+		return val
+	}
+
+	var exec func(ast.Expr)
+
+	exec = func(expr ast.Expr) {
+		switch expr := expr.(type) {
+		case *ast.BinaryExpr:
+			{
+				be := expr
+
+				exec(be.Y)
+				exec(be.X)
+
+				negate := false
+				name := c.tempvar()
+				c.write(`{{` + name + ` := `)
+
+				switch be.Op {
+				case gt.ADD:
+					c.write("__amber_add ")
+				case gt.SUB:
+					c.write("__amber_sub ")
+				case gt.MUL:
+					c.write("__amber_mul ")
+				case gt.QUO:
+					c.write("__amber_quo ")
+				case gt.REM:
+					c.write("__amber_rem ")
+				case gt.LAND:
+					c.write("and ")
+				case gt.LOR:
+					c.write("or ")
+				case gt.EQL:
+					c.write("__amber_eql ")
+				case gt.NEQ:
+					c.write("__amber_eql ")
+					negate = true
+				case gt.LSS:
+					c.write("__amber_lss ")
+				case gt.GTR:
+					c.write("__amber_gtr ")
+				case gt.LEQ:
+					c.write("__amber_gtr ")
+					negate = true
+				case gt.GEQ:
+					c.write("__amber_lss ")
+					negate = true
+				default:
+					panic("Unexpected operator!")
+				}
+
+				c.write(pop() + ` ` + pop() + `}}`)
+
+				if !negate {
+					stack.PushFront(name)
+				} else {
+					negname := c.tempvar()
+					c.write(`{{` + negname + ` := not ` + name + `}}`)
+					stack.PushFront(negname)
+				}
+			}
+		case *ast.UnaryExpr:
+			{
+				ue := expr
+
+				exec(ue.X)
+
+				name := c.tempvar()
+				c.write(`{{` + name + ` := `)
+
+				switch ue.Op {
+				case gt.SUB:
+					c.write("__amber_minus ")
+				case gt.ADD:
+					c.write("__amber_plus ")
+				case gt.NOT:
+					c.write("not ")
+				default:
+					panic("Unexpected operator!")
+				}
+
+				c.write(pop() + `}}`)
+				stack.PushFront(name)
+			}
+		case *ast.ParenExpr:
+			exec(expr.X)
+		case *ast.BasicLit:
+			stack.PushFront(strings.Replace(expr.Value, dollar, "$", -1))
+		case *ast.Ident:
+			name := expr.Name
+			if len(name) >= len(dollar) && name[:len(dollar)] == dollar {
+				if name == dollar {
+					stack.PushFront(`.`)
+				} else {
+					stack.PushFront(`$` + expr.Name[len(dollar):])
+				}
+			} else {
+				stack.PushFront(`.` + expr.Name)
+			}
+		case *ast.SelectorExpr:
+			se := expr
+			exec(se.X)
+			x := pop()
+
+			if x == "." {
+				x = ""
+			}
+
+			name := c.tempvar()
+			c.write(`{{` + name + ` := ` + x + `.` + se.Sel.Name + `}}`)
+			stack.PushFront(name)
+		case *ast.CallExpr:
+			ce := expr
+
+			for i := len(ce.Args) - 1; i >= 0; i-- {
+				exec(ce.Args[i])
+			}
+
+			name := c.tempvar()
+			builtin := false
+
+			if ident, ok := ce.Fun.(*ast.Ident); ok {
+				for _, fname := range builtinFunctions {
+					if fname == ident.Name {
+						builtin = true
+						break
+					}
+				}
+				for fname, _ := range FuncMap {
+					if fname == ident.Name {
+						builtin = true
+						break
+					}
+				}
+			}
+
+			if builtin {
+				stack.PushFront(ce.Fun.(*ast.Ident).Name)
+				c.write(`{{` + name + ` := ` + pop())
+			} else if se, ok := ce.Fun.(*ast.SelectorExpr); ok {
+				exec(se.X)
+				x := pop()
+
+				if x == "." {
+					x = ""
+				}
+				stack.PushFront(se.Sel.Name)
+				c.write(`{{` + name + ` := ` + x + `.` + pop())
+			} else {
+				exec(ce.Fun)
+				c.write(`{{` + name + ` := call ` + pop())
+			}
+
+			for i := 0; i < len(ce.Args); i++ {
+				c.write(` `)
+				c.write(pop())
+			}
+
+			c.write(`}}`)
+
+			stack.PushFront(name)
+		default:
+			panic("Unable to parse expression. Unsupported: " + reflect.TypeOf(expr).String())
+		}
+	}
+
+	exec(outerexpr)
+	return pop()
+}
+
+func (c *Compiler) visitMixin(mixin *parser.Mixin) {
+	c.mixins[mixin.Name] = mixin
+}
+
+func (c *Compiler) visitMixinCall(mixinCall *parser.MixinCall) {
+	mixin := c.mixins[mixinCall.Name]
+
+	switch {
+	case mixin == nil:
+		panic(fmt.Sprintf("unknown mixin %q", mixinCall.Name))
+
+	case len(mixinCall.Args) < len(mixin.Args):
+		panic(fmt.Sprintf(
+			"not enough arguments in call to mixin %q (have: %d, want: %d)",
+			mixinCall.Name,
+			len(mixinCall.Args),
+			len(mixin.Args),
+		))
+	case len(mixinCall.Args) > len(mixin.Args):
+		panic(fmt.Sprintf(
+			"too many arguments in call to mixin %q (have: %d, want: %d)",
+			mixinCall.Name,
+			len(mixinCall.Args),
+			len(mixin.Args),
+		))
+	}
+
+	for i, arg := range mixin.Args {
+		c.write(fmt.Sprintf(`{{%s := %s}}`, arg, c.visitRawInterpolation(mixinCall.Args[i])))
+	}
+	c.visitBlock(mixin.Block)
+}