func funcRange(v []data.Value) data.Value { var ( increment = 1 init = 0 limit int ) switch len(v) { case 3: increment = int(v[2].(data.Int)) fallthrough case 2: init = int(v[0].(data.Int)) limit = int(v[1].(data.Int)) case 1: limit = int(v[0].(data.Int)) } var indices data.List var i = 0 for index := init; index < limit; index += increment { indices = append(indices, data.Int(index)) i++ } return indices }
func BenchmarkSimpleTemplate_Soy(b *testing.B) { var tofu, err = NewBundle(). AddTemplateString("", ` {namespace small} /** * @param foo * @param bar * @param baz */ {template .test} some {$foo}, some {$bar}, more {$baz} {/template}`). CompileToTofu() if err != nil { panic(err) } b.ResetTimer() var buf = new(bytes.Buffer) for i := 0; i < b.N; i++ { buf.Reset() err = tofu.Render(buf, "small.test", data.Map{ "foo": data.String("foostring"), "bar": data.Int(42), "baz": data.Bool(true), }) if err != nil { b.Error(err) } } }
func funcRound(v []data.Value) data.Value { var digitsAfterPt = 0 if len(v) == 2 { digitsAfterPt = int(v[1].(data.Int)) } var result = round(toFloat(v[0]), digitsAfterPt) if digitsAfterPt <= 0 { return data.Int(result) } return data.Float(result) }
func TestRound(t *testing.T) { type i []interface{} var tests = []struct { input []interface{} expected interface{} }{ {i{0}, 0}, {i{-5}, -5}, {i{5}, 5}, {i{1.01}, 1}, {i{1.99}, 2}, {i{1.0}, 1}, {i{-1.01}, -1}, {i{-1.99}, -2}, {i{-1.5}, -2}, {i{1.2345, 1}, 1.2}, {i{1.2345, 2}, 1.23}, {i{1.2345, 3}, 1.235}, {i{1.2345, 4}, 1.2345}, {i{-1.2345, 1}, -1.2}, {i{-1.2345, 2}, -1.23}, {i{-1.2345, 3}, -1.235}, {i{-1.2345, 4}, -1.2345}, {i{1.0, 5}, 1.0}, {i{123.456, -1}, 120}, {i{123.456, -2}, 100}, {i{123.456, -3}, 000}, } for _, test := range tests { var inputValues []data.Value for _, num := range test.input { inputValues = append(inputValues, data.New(num)) } actual := funcRound(inputValues) if len(inputValues) == 1 { // Passing one arg should have the same result as passing the second as 0 if actual != funcRound(append(inputValues, data.Int(0))) { t.Errorf("round %v returned %v, but changed when passed explicit 0", test.input, actual) } } if actual != data.New(test.expected) { t.Errorf("round %v => %v, expected %v", test.input, actual, test.expected) } } }
func BenchmarkSimpleTemplate_Go(b *testing.B) { var tmpl = template.Must(template.New("").Parse(` {{define "small.test"}} some {{.foo}}, some {{.bar}}, more {{.baz}} {{end}}`)) b.ResetTimer() var buf = new(bytes.Buffer) for i := 0; i < b.N; i++ { buf.Reset() var err = tmpl.ExecuteTemplate(buf, "small.test", data.Map{ "foo": data.String("foostring"), "bar": data.Int(42), "baz": data.Bool(true), }) if err != nil { b.Error(err) } } }
func TestIsnonnull(t *testing.T) { var tests = []struct { input data.Value expected bool }{ {data.Null{}, false}, {data.Undefined{}, false}, {data.Bool(false), true}, {data.Int(0), true}, {data.Float(0), true}, {data.String(""), true}, {data.List{}, true}, {data.Map{}, true}, } for _, test := range tests { var actual = funcIsNonnull([]data.Value{test.input}).(data.Bool) if bool(actual) != test.expected { t.Errorf("isNonnull(%v) => %v, expected %v", test.input, actual, test.expected) } } }
// Ensure that variables have the appropriate scope. // Ensure that the input data map is not updated. // Ensure that let variables are not passed with data="all" func TestLetScopes(t *testing.T) { var m = data.Map{"a": data.Int(1), "z": data.Map{"y": data.Int(9)}} var mcopy = data.Map{"a": data.Int(1), "z": data.Map{"y": data.Int(9)}} runExecTests(t, []execTest{ {"letscopes", "test.main", `{namespace test} /** @param a */ {template .main} // starting value {$a} // reassign with a let {let $a: 2 /} {$a} // data="all" should not pass "let" assignments // $a should not be updated by the {let} in .inner {call .inner data="all"/} {$a} // for loops should create a new scope, not update the existing variable {for $a in range(5, 6)} {$a} {/for} {$a} // reassign to the same value {let $a} {let $b: $a/} {$b} {/let} {$a} // reassign to a different value {let $a:6/} {$a} {/template} /** * @param a * @param? b */ {template .inner} {$a} {if $b}{$b}{/if} {let $a: 3 /} {$a} {call .inner2 data="all"/} {$a} {/template} /** @param a */ {template .inner2} {$a} {let $a: 4 /} {$a} {/template} `, "121314325226", m, true}, {"no-overwrite-map", "test.main", `{namespace test} /** @param z */ {template .main} {call .inner data="$z"/} {/template} /** @param y */ {template .inner} {let $a: 8/} {$y} {$a} {let $y: 7/} {sp}{$y} {/template} `, "9 8 7", m, true}, }) if !reflect.DeepEqual(m, mcopy) { t.Errorf("input data map changed: %v", m) } }
// walk recursively goes through each node and executes the indicated logic and // writes the output func (s *state) walk(node ast.Node) { s.val = data.Undefined{} s.at(node) switch node := node.(type) { case *ast.SoyFileNode: for _, node := range node.Body { s.walk(node) } case *ast.TemplateNode: if node.Autoescape != ast.AutoescapeUnspecified { s.autoescape = node.Autoescape } s.walk(node.Body) case *ast.ListNode: for _, node := range node.Nodes { s.walk(node) } // Output nodes ---------- case *ast.PrintNode: s.evalPrint(node) case *ast.RawTextNode: if _, err := s.wr.Write(node.Text); err != nil { s.errorf("%s", err) } case *ast.MsgNode: s.walk(node.Body) case *ast.CssNode: var prefix = "" if node.Expr != nil { prefix = s.eval(node.Expr).String() + "-" } if _, err := io.WriteString(s.wr, prefix+node.Suffix); err != nil { s.errorf("%s", err) } case *ast.DebuggerNode: // nothing to do case *ast.LogNode: Logger.Print(string(s.renderBlock(node.Body))) // Control flow ---------- case *ast.IfNode: for _, cond := range node.Conds { if cond.Cond == nil || s.eval(cond.Cond).Truthy() { s.walk(cond.Body) break } } case *ast.ForNode: var list, ok = s.eval(node.List).(data.List) if !ok { s.errorf("In for loop %q, %q does not resolve to a list.", node.String(), node.List.String()) } if len(list) == 0 { if node.IfEmpty != nil { s.walk(node.IfEmpty) } break } s.context.push() for i, item := range list { s.context.set(node.Var, item) s.context.set(node.Var+"__index", data.Int(i)) s.context.set(node.Var+"__lastIndex", data.Int(len(list)-1)) s.walk(node.Body) } s.context.pop() case *ast.SwitchNode: var switchValue = s.eval(node.Value) for _, caseNode := range node.Cases { for _, caseValueNode := range caseNode.Values { if switchValue.Equals(s.eval(caseValueNode)) { s.walk(caseNode.Body) return } } if len(caseNode.Values) == 0 { // default/last case s.walk(caseNode.Body) return } } case *ast.CallNode: s.evalCall(node) case *ast.LetValueNode: s.context.set(node.Name, s.eval(node.Expr)) case *ast.LetContentNode: s.context.set(node.Name, data.String(s.renderBlock(node.Body))) // Values ---------- case *ast.NullNode: s.val = data.Null{} case *ast.StringNode: s.val = data.String(node.Value) case *ast.IntNode: s.val = data.Int(node.Value) case *ast.FloatNode: s.val = data.Float(node.Value) case *ast.BoolNode: s.val = data.Bool(node.True) case *ast.GlobalNode: s.val = node.Value case *ast.ListLiteralNode: var items = make(data.List, len(node.Items)) for i, item := range node.Items { items[i] = s.eval(item) } s.val = data.List(items) case *ast.MapLiteralNode: var items = make(data.Map, len(node.Items)) for k, v := range node.Items { items[k] = s.eval(v) } s.val = data.Map(items) case *ast.FunctionNode: s.val = s.evalFunc(node) case *ast.DataRefNode: s.val = s.evalDataRef(node) // Arithmetic operators ---------- case *ast.NegateNode: switch arg := s.evaldef(node.Arg).(type) { case data.Int: s.val = data.Int(-arg) case data.Float: s.val = data.Float(-arg) default: s.errorf("can not negate non-number: %q", arg.String()) } case *ast.AddNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) switch { case isInt(arg1) && isInt(arg2): s.val = data.Int(arg1.(data.Int) + arg2.(data.Int)) case isString(arg1) || isString(arg2): s.val = data.String(arg1.String() + arg2.String()) default: s.val = data.Float(toFloat(arg1) + toFloat(arg2)) } case *ast.SubNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) switch { case isInt(arg1) && isInt(arg2): s.val = data.Int(arg1.(data.Int) - arg2.(data.Int)) default: s.val = data.Float(toFloat(arg1) - toFloat(arg2)) } case *ast.DivNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) s.val = data.Float(toFloat(arg1) / toFloat(arg2)) case *ast.MulNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) switch { case isInt(arg1) && isInt(arg2): s.val = data.Int(arg1.(data.Int) * arg2.(data.Int)) default: s.val = data.Float(toFloat(arg1) * toFloat(arg2)) } case *ast.ModNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) s.val = data.Int(arg1.(data.Int) % arg2.(data.Int)) // Arithmetic comparisons ---------- case *ast.EqNode: s.val = data.Bool(s.eval(node.Arg1).Equals(s.eval(node.Arg2))) case *ast.NotEqNode: s.val = data.Bool(!s.eval(node.Arg1).Equals(s.eval(node.Arg2))) case *ast.LtNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) < toFloat(s.evaldef(node.Arg2))) case *ast.LteNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) <= toFloat(s.evaldef(node.Arg2))) case *ast.GtNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) > toFloat(s.evaldef(node.Arg2))) case *ast.GteNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) >= toFloat(s.evaldef(node.Arg2))) // Boolean operators ---------- case *ast.NotNode: s.val = data.Bool(!s.eval(node.Arg).Truthy()) case *ast.AndNode: s.val = data.Bool(s.eval(node.Arg1).Truthy() && s.eval(node.Arg2).Truthy()) case *ast.OrNode: s.val = data.Bool(s.eval(node.Arg1).Truthy() || s.eval(node.Arg2).Truthy()) case *ast.ElvisNode: var arg1 = s.eval(node.Arg1) if arg1 != (data.Null{}) && arg1 != (data.Undefined{}) { s.val = arg1 } else { s.val = s.eval(node.Arg2) } case *ast.TernNode: var arg1 = s.eval(node.Arg1) if arg1.Truthy() { s.val = s.eval(node.Arg2) } else { s.val = s.eval(node.Arg3) } default: s.errorf("unknown node: %T", node) } }
func funcLength(v []data.Value) data.Value { return data.Int(len(v[0].(data.List))) }
func funcRandomInt(v []data.Value) data.Value { return data.Int(rand.Int63n(int64(v[0].(data.Int)))) }
func funcCeiling(v []data.Value) data.Value { if isInt(v[0]) { return v[0] } return data.Int(math.Ceil(toFloat(v[0]))) }
func funcFloor(v []data.Value) data.Value { if isInt(v[0]) { return v[0] } return data.Int(math.Floor(toFloat(v[0]))) }