// walk recursively goes through each node and translates the nodes to // javascript, writing the result to s.wr func (s *state) walk(node ast.Node) { s.at(node) switch node := node.(type) { case *ast.SoyFileNode: s.visitSoyFile(node) case *ast.NamespaceNode: s.visitNamespace(node) case *ast.SoyDocNode: return case *ast.TemplateNode: s.visitTemplate(node) case *ast.ListNode: s.visitChildren(node) // Output nodes ---------- case *ast.RawTextNode: s.writeRawText(node.Text) case *ast.PrintNode: s.visitPrint(node) case *ast.MsgNode: s.walk(node.Body) case *ast.CssNode: if node.Expr != nil { s.jsln(s.bufferName, " += ", node.Expr, " + '-';") } s.writeRawText([]byte(node.Suffix)) case *ast.DebuggerNode: s.jsln("debugger;") case *ast.LogNode: s.bufferName += "_" s.jsln("var ", s.bufferName, " = '';") s.walk(node.Body) s.jsln("console.log(", s.bufferName, ");") s.bufferName = s.bufferName[:len(s.bufferName)-1] // Control flow ---------- case *ast.IfNode: s.visitIf(node) case *ast.ForNode: s.visitFor(node) case *ast.SwitchNode: s.visitSwitch(node) case *ast.CallNode: s.visitCall(node) case *ast.LetValueNode: s.jsln("var ", s.scope.makevar(node.Name), " = ", node.Expr, ";") case *ast.LetContentNode: var oldBufferName = s.bufferName s.bufferName = s.scope.makevar(node.Name) s.jsln("var ", s.bufferName, " = '';") s.walk(node.Body) s.bufferName = oldBufferName // Values ---------- case *ast.NullNode: s.js("null") case *ast.StringNode: s.js("'") template.JSEscape(s.wr, []byte(node.Value)) s.js("'") case *ast.IntNode: s.js(node.String()) case *ast.FloatNode: s.js(node.String()) case *ast.BoolNode: s.js(node.String()) case *ast.GlobalNode: s.visitGlobal(node) case *ast.ListLiteralNode: s.js("[") for i, item := range node.Items { if i != 0 { s.js(",") } s.walk(item) } s.js("]") case *ast.MapLiteralNode: s.js("{") var first = true for k, v := range node.Items { if !first { s.js(",") } first = false s.js(k, ":") s.walk(v) } s.js("}") case *ast.FunctionNode: s.visitFunction(node) case *ast.DataRefNode: s.visitDataRef(node) // Arithmetic operators ---------- case *ast.NegateNode: s.js("(-", node.Arg, ")") case *ast.AddNode: s.op("+", node) case *ast.SubNode: s.op("-", node) case *ast.DivNode: s.op("/", node) case *ast.MulNode: s.op("*", node) case *ast.ModNode: s.op("%", node) // Arithmetic comparisons ---------- case *ast.EqNode: s.op("==", node) case *ast.NotEqNode: s.op("!=", node) case *ast.LtNode: s.op("<", node) case *ast.LteNode: s.op("<=", node) case *ast.GtNode: s.op(">", node) case *ast.GteNode: s.op(">=", node) // Boolean operators ---------- case *ast.NotNode: s.js("!(", node.Arg, ")") case *ast.AndNode: s.op("&&", node) case *ast.OrNode: s.op("||", node) case *ast.ElvisNode: // ?: is specified to check for null. s.js("(", node.Arg1, " != null ? ", node.Arg1, " : ", node.Arg2, ")") case *ast.TernNode: s.js("(", node.Arg1, "?", node.Arg2, ":", node.Arg3, ")") default: s.errorf("unknown node (%T): %v", node, node) } }
// 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) } }