1
0
mirror of https://github.com/golang/go synced 2024-09-25 01:20:13 -06:00

text/template/parse: deep Copy method for nodes

This will help html/template copy templates.

R=golang-dev, gri, nigeltao, r
CC=golang-dev
https://golang.org/cl/5653062
This commit is contained in:
Rob Pike 2012-02-11 14:21:16 +11:00
parent ca5da31f83
commit b027a0f118
2 changed files with 128 additions and 2 deletions

View File

@ -17,6 +17,10 @@ import (
type Node interface {
Type() NodeType
String() string
// Copy does a deep copy of the Node and all its components.
// To avoid type assertions, some XxxNodes also have specialized
// CopyXxx methods that return *XxxNode.
Copy() Node
}
// NodeType identifies the type of a parse tree node.
@ -73,6 +77,21 @@ func (l *ListNode) String() string {
return b.String()
}
func (l *ListNode) CopyList() *ListNode {
if l == nil {
return l
}
n := newList()
for _, elem := range l.Nodes {
n.append(elem.Copy())
}
return n
}
func (l *ListNode) Copy() Node {
return l.CopyList()
}
// TextNode holds plain text.
type TextNode struct {
NodeType
@ -87,6 +106,10 @@ func (t *TextNode) String() string {
return fmt.Sprintf("%q", t.Text)
}
func (t *TextNode) Copy() Node {
return &TextNode{NodeType: NodeText, Text: append([]byte{}, t.Text...)}
}
// PipeNode holds a pipeline with optional declaration
type PipeNode struct {
NodeType
@ -123,6 +146,25 @@ func (p *PipeNode) String() string {
return s
}
func (p *PipeNode) CopyPipe() *PipeNode {
if p == nil {
return p
}
var decl []*VariableNode
for _, d := range p.Decl {
decl = append(decl, d.Copy().(*VariableNode))
}
n := newPipeline(p.Line, decl)
for _, c := range p.Cmds {
n.append(c.Copy().(*CommandNode))
}
return n
}
func (p *PipeNode) Copy() Node {
return p.CopyPipe()
}
// ActionNode holds an action (something bounded by delimiters).
// Control actions have their own nodes; ActionNode represents simple
// ones such as field evaluations.
@ -141,6 +183,11 @@ func (a *ActionNode) String() string {
}
func (a *ActionNode) Copy() Node {
return newAction(a.Line, a.Pipe.CopyPipe())
}
// CommandNode holds a command (a pipeline inside an evaluating action).
type CommandNode struct {
NodeType
@ -166,6 +213,17 @@ func (c *CommandNode) String() string {
return s
}
func (c *CommandNode) Copy() Node {
if c == nil {
return c
}
n := newCommand()
for _, c := range c.Args {
n.append(c.Copy())
}
return n
}
// IdentifierNode holds an identifier.
type IdentifierNode struct {
NodeType
@ -181,6 +239,10 @@ func (i *IdentifierNode) String() string {
return i.Ident
}
func (i *IdentifierNode) Copy() Node {
return NewIdentifier(i.Ident)
}
// VariableNode holds a list of variable names. The dollar sign is
// part of the name.
type VariableNode struct {
@ -203,6 +265,10 @@ func (v *VariableNode) String() string {
return s
}
func (v *VariableNode) Copy() Node {
return &VariableNode{NodeType: NodeVariable, Ident: append([]string{}, v.Ident...)}
}
// DotNode holds the special identifier '.'. It is represented by a nil pointer.
type DotNode bool
@ -218,6 +284,10 @@ func (d *DotNode) String() string {
return "."
}
func (d *DotNode) Copy() Node {
return newDot()
}
// FieldNode holds a field (identifier starting with '.').
// The names may be chained ('.x.y').
// The period is dropped from each ident.
@ -238,6 +308,10 @@ func (f *FieldNode) String() string {
return s
}
func (f *FieldNode) Copy() Node {
return &FieldNode{NodeType: NodeField, Ident: append([]string{}, f.Ident...)}
}
// BoolNode holds a boolean constant.
type BoolNode struct {
NodeType
@ -255,6 +329,10 @@ func (b *BoolNode) String() string {
return "false"
}
func (b *BoolNode) Copy() Node {
return newBool(b.True)
}
// NumberNode holds a number: signed or unsigned integer, float, or complex.
// The value is parsed and stored under all the types that can represent the value.
// This simulates in a small amount of code the behavior of Go's ideal constants.
@ -373,6 +451,12 @@ func (n *NumberNode) String() string {
return n.Text
}
func (n *NumberNode) Copy() Node {
nn := new(NumberNode)
*nn = *n // Easy, fast, correct.
return nn
}
// StringNode holds a string constant. The value has been "unquoted".
type StringNode struct {
NodeType
@ -388,6 +472,10 @@ func (s *StringNode) String() string {
return s.Quoted
}
func (s *StringNode) Copy() Node {
return newString(s.Quoted, s.Text)
}
// endNode represents an {{end}} action. It is represented by a nil pointer.
// It does not appear in the final parse tree.
type endNode bool
@ -404,6 +492,10 @@ func (e *endNode) String() string {
return "{{end}}"
}
func (e *endNode) Copy() Node {
return newEnd()
}
// elseNode represents an {{else}} action. Does not appear in the final tree.
type elseNode struct {
NodeType
@ -422,6 +514,10 @@ func (e *elseNode) String() string {
return "{{else}}"
}
func (e *elseNode) Copy() Node {
return newElse(e.Line)
}
// BranchNode is the common representation of if, range, and with.
type BranchNode struct {
NodeType
@ -458,6 +554,10 @@ func newIf(line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
return &IfNode{BranchNode{NodeType: NodeIf, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
}
func (i *IfNode) Copy() Node {
return newIf(i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
}
// RangeNode represents a {{range}} action and its commands.
type RangeNode struct {
BranchNode
@ -467,6 +567,10 @@ func newRange(line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
return &RangeNode{BranchNode{NodeType: NodeRange, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
}
func (r *RangeNode) Copy() Node {
return newRange(r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
}
// WithNode represents a {{with}} action and its commands.
type WithNode struct {
BranchNode
@ -476,6 +580,10 @@ func newWith(line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
return &WithNode{BranchNode{NodeType: NodeWith, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
}
func (w *WithNode) Copy() Node {
return newWith(w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
}
// TemplateNode represents a {{template}} action.
type TemplateNode struct {
NodeType
@ -494,3 +602,7 @@ func (t *TemplateNode) String() string {
}
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
}
func (t *TemplateNode) Copy() Node {
return newTemplate(t.Line, t.Name, t.Pipe.CopyPipe())
}

View File

@ -232,7 +232,7 @@ var builtins = map[string]interface{}{
"printf": fmt.Sprintf,
}
func TestParse(t *testing.T) {
func testParse(doCopy bool, t *testing.T) {
for _, test := range parseTests {
tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
switch {
@ -249,13 +249,27 @@ func TestParse(t *testing.T) {
}
continue
}
result := tmpl.Root.String()
var result string
if doCopy {
result = tmpl.Root.Copy().String()
} else {
result = tmpl.Root.String()
}
if result != test.result {
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
}
}
}
func TestParse(t *testing.T) {
testParse(false, t)
}
// Same as TestParse, but we copy the node first
func TestParseCopy(t *testing.T) {
testParse(true, t)
}
type isEmptyTest struct {
name string
input string