66
|
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 }
|