func funcMax(v []data.Value) data.Value { if isInt(v[0]) && isInt(v[1]) { if v[0].(data.Int) > v[1].(data.Int) { return v[0] } return v[1] } return data.Float(math.Max(toFloat(v[0]), toFloat(v[1]))) }
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 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) } } }
// 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) } }