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