Mercurial > yakumo_izuru > aya
comparison 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 |
comparison
equal
deleted
inserted
replaced
65:6d985efa0f7a | 66:787b5ee0289d |
---|---|
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 } |