1
0
mirror of https://github.com/golang/go synced 2024-11-21 16:04:45 -07:00

exp/template: split the parse tree into a separate package exp/template/parse

Mostly a mechanical change, with a few cleanups to make the split easier.
The external interface to exp/template is unaffected.

In another round I will play with the function map setup to see if I can
avoid exposing reflect across the boundary, but that will require some
structural changes I did not want to mix into this CL.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4849049
This commit is contained in:
Rob Pike 2011-08-09 15:42:53 +10:00
parent 476150f4bf
commit c66917d2b6
15 changed files with 1174 additions and 1035 deletions

View File

@ -83,6 +83,7 @@ DIRS=\
exp/norm\
exp/regexp/syntax\
exp/template\
exp/template/parse\
expvar\
flag\
fmt\

View File

@ -9,7 +9,6 @@ GOFILES=\
exec.go\
funcs.go\
helper.go\
lex.go\
parse.go\
set.go\

View File

@ -5,10 +5,12 @@
package template
import (
"exp/template/parse"
"fmt"
"io"
"os"
"reflect"
"runtime"
"strings"
)
@ -63,7 +65,7 @@ var zero reflect.Value
// errorf formats the error and terminates processing.
func (s *state) errorf(format string, args ...interface{}) {
format = fmt.Sprintf("template: %s:%d: %s", s.tmpl.name, s.line, format)
format = fmt.Sprintf("template: %s:%d: %s", s.tmpl.Name(), s.line, format)
panic(fmt.Errorf(format, args...))
}
@ -72,10 +74,22 @@ func (s *state) error(err os.Error) {
s.errorf("%s", err)
}
// errRecover is the handler that turns panics into returns from the top
// level of Parse.
func errRecover(errp *os.Error) {
e := recover()
if e != nil {
if _, ok := e.(runtime.Error); ok {
panic(e)
}
*errp = e.(os.Error)
}
}
// Execute applies a parsed template to the specified data object,
// writing the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
defer t.recover(&err)
defer errRecover(&err)
value := reflect.ValueOf(data)
state := &state{
tmpl: t,
@ -83,45 +97,45 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
line: 1,
vars: []variable{{"$", value}},
}
if t.root == nil {
if t.Root == nil {
state.errorf("must be parsed before execution")
}
state.walk(value, t.root)
state.walk(value, t.Root)
return
}
// Walk functions step through the major pieces of the template structure,
// generating output as they go.
func (s *state) walk(dot reflect.Value, n node) {
func (s *state) walk(dot reflect.Value, n parse.Node) {
switch n := n.(type) {
case *actionNode:
s.line = n.line
case *parse.ActionNode:
s.line = n.Line
// Do not pop variables so they persist until next end.
// Also, if the action declares variables, don't print the result.
val := s.evalPipeline(dot, n.pipe)
if len(n.pipe.decl) == 0 {
val := s.evalPipeline(dot, n.Pipe)
if len(n.Pipe.Decl) == 0 {
s.printValue(n, val)
}
case *ifNode:
s.line = n.line
s.walkIfOrWith(nodeIf, dot, n.pipe, n.list, n.elseList)
case *listNode:
for _, node := range n.nodes {
case *parse.IfNode:
s.line = n.Line
s.walkIfOrWith(parse.NodeIf, dot, n.Pipe, n.List, n.ElseList)
case *parse.ListNode:
for _, node := range n.Nodes {
s.walk(dot, node)
}
case *rangeNode:
s.line = n.line
case *parse.RangeNode:
s.line = n.Line
s.walkRange(dot, n)
case *templateNode:
s.line = n.line
case *parse.TemplateNode:
s.line = n.Line
s.walkTemplate(dot, n)
case *textNode:
if _, err := s.wr.Write(n.text); err != nil {
case *parse.TextNode:
if _, err := s.wr.Write(n.Text); err != nil {
s.error(err)
}
case *withNode:
s.line = n.line
s.walkIfOrWith(nodeWith, dot, n.pipe, n.list, n.elseList)
case *parse.WithNode:
s.line = n.Line
s.walkIfOrWith(parse.NodeWith, dot, n.Pipe, n.List, n.ElseList)
default:
s.errorf("unknown node: %s", n)
}
@ -129,7 +143,7 @@ func (s *state) walk(dot reflect.Value, n node) {
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
// are identical in behavior except that 'with' sets dot.
func (s *state) walkIfOrWith(typ nodeType, dot reflect.Value, pipe *pipeNode, list, elseList *listNode) {
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
defer s.pop(s.mark())
val := s.evalPipeline(dot, pipe)
truth, ok := isTrue(val)
@ -137,7 +151,7 @@ func (s *state) walkIfOrWith(typ nodeType, dot reflect.Value, pipe *pipeNode, li
s.errorf("if/with can't use value of type %T", val.Interface())
}
if truth {
if typ == nodeWith {
if typ == parse.NodeWith {
s.walk(val, list)
} else {
s.walk(dot, list)
@ -171,9 +185,9 @@ func isTrue(val reflect.Value) (truth, ok bool) {
return truth, true
}
func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
defer s.pop(s.mark())
val, _ := indirect(s.evalPipeline(dot, r.pipe))
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
// mark top of stack before any variables in the body are pushed.
mark := s.mark()
switch val.Kind() {
@ -184,14 +198,14 @@ func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
for i := 0; i < val.Len(); i++ {
elem := val.Index(i)
// Set top var (lexically the second if there are two) to the element.
if len(r.pipe.decl) > 0 {
if len(r.Pipe.Decl) > 0 {
s.setVar(1, elem)
}
// Set next var (lexically the first if there are two) to the index.
if len(r.pipe.decl) > 1 {
if len(r.Pipe.Decl) > 1 {
s.setVar(2, reflect.ValueOf(i))
}
s.walk(elem, r.list)
s.walk(elem, r.List)
s.pop(mark)
}
return
@ -202,41 +216,41 @@ func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
for _, key := range val.MapKeys() {
elem := val.MapIndex(key)
// Set top var (lexically the second if there are two) to the element.
if len(r.pipe.decl) > 0 {
if len(r.Pipe.Decl) > 0 {
s.setVar(1, elem)
}
// Set next var (lexically the first if there are two) to the key.
if len(r.pipe.decl) > 1 {
if len(r.Pipe.Decl) > 1 {
s.setVar(2, key)
}
s.walk(elem, r.list)
s.walk(elem, r.List)
s.pop(mark)
}
return
default:
s.errorf("range can't iterate over value of type %T", val.Interface())
}
if r.elseList != nil {
s.walk(dot, r.elseList)
if r.ElseList != nil {
s.walk(dot, r.ElseList)
}
}
func (s *state) walkTemplate(dot reflect.Value, t *templateNode) {
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
set := s.tmpl.set
if set == nil {
s.errorf("no set defined in which to invoke template named %q", t.name)
s.errorf("no set defined in which to invoke template named %q", t.Name)
}
tmpl := set.tmpl[t.name]
tmpl := set.tmpl[t.Name]
if tmpl == nil {
s.errorf("template %q not in set", t.name)
s.errorf("template %q not in set", t.Name)
}
// Variables declared by the pipeline persist.
dot = s.evalPipeline(dot, t.pipe)
dot = s.evalPipeline(dot, t.Pipe)
newState := *s
newState.tmpl = tmpl
// No dynamic scoping: template invocations inherit no variables.
newState.vars = []variable{{"$", dot}}
newState.walk(dot, tmpl.root)
newState.walk(dot, tmpl.Root)
}
// Eval functions evaluate pipelines, commands, and their elements and extract
@ -247,50 +261,50 @@ func (s *state) walkTemplate(dot reflect.Value, t *templateNode) {
// pipeline has a variable declaration, the variable will be pushed on the
// stack. Callers should therefore pop the stack after they are finished
// executing commands depending on the pipeline value.
func (s *state) evalPipeline(dot reflect.Value, pipe *pipeNode) (value reflect.Value) {
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
if pipe == nil {
return
}
for _, cmd := range pipe.cmds {
for _, cmd := range pipe.Cmds {
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
// If the object has type interface{}, dig down one level to the thing inside.
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
value = reflect.ValueOf(value.Interface()) // lovely!
}
}
for _, variable := range pipe.decl {
s.push(variable.ident[0], value)
for _, variable := range pipe.Decl {
s.push(variable.Ident[0], value)
}
return value
}
func (s *state) notAFunction(args []node, final reflect.Value) {
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
if len(args) > 1 || final.IsValid() {
s.errorf("can't give argument to non-function %s", args[0])
}
}
func (s *state) evalCommand(dot reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
firstWord := cmd.args[0]
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
firstWord := cmd.Args[0]
switch n := firstWord.(type) {
case *fieldNode:
return s.evalFieldNode(dot, n, cmd.args, final)
case *identifierNode:
case *parse.FieldNode:
return s.evalFieldNode(dot, n, cmd.Args, final)
case *parse.IdentifierNode:
// Must be a function.
return s.evalFunction(dot, n.ident, cmd.args, final)
case *variableNode:
return s.evalVariableNode(dot, n, cmd.args, final)
return s.evalFunction(dot, n.Ident, cmd.Args, final)
case *parse.VariableNode:
return s.evalVariableNode(dot, n, cmd.Args, final)
}
s.notAFunction(cmd.args, final)
s.notAFunction(cmd.Args, final)
switch word := firstWord.(type) {
case *boolNode:
return reflect.ValueOf(word.true)
case *dotNode:
case *parse.BoolNode:
return reflect.ValueOf(word.True)
case *parse.DotNode:
return dot
case *numberNode:
case *parse.NumberNode:
return s.idealConstant(word)
case *stringNode:
return reflect.ValueOf(word.text)
case *parse.StringNode:
return reflect.ValueOf(word.Text)
}
s.errorf("can't evaluate command %q", firstWord)
panic("not reached")
@ -300,44 +314,44 @@ func (s *state) evalCommand(dot reflect.Value, cmd *commandNode, final reflect.V
// we don't know the type. In that case, the syntax of the number tells us
// its type, and we use Go rules to resolve. Note there is no such thing as
// a uint ideal constant in this situation - the value must be of int type.
func (s *state) idealConstant(constant *numberNode) reflect.Value {
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
// These are ideal constants but we don't know the type
// and we have no context. (If it was a method argument,
// we'd know what we need.) The syntax guides us to some extent.
switch {
case constant.isComplex:
return reflect.ValueOf(constant.complex128) // incontrovertible.
case constant.isFloat && strings.IndexAny(constant.text, ".eE") >= 0:
return reflect.ValueOf(constant.float64)
case constant.isInt:
n := int(constant.int64)
if int64(n) != constant.int64 {
s.errorf("%s overflows int", constant.text)
case constant.IsComplex:
return reflect.ValueOf(constant.Complex128) // incontrovertible.
case constant.IsFloat && strings.IndexAny(constant.Text, ".eE") >= 0:
return reflect.ValueOf(constant.Float64)
case constant.IsInt:
n := int(constant.Int64)
if int64(n) != constant.Int64 {
s.errorf("%s overflows int", constant.Text)
}
return reflect.ValueOf(n)
case constant.isUint:
s.errorf("%s overflows int", constant.text)
case constant.IsUint:
s.errorf("%s overflows int", constant.Text)
}
return zero
}
func (s *state) evalFieldNode(dot reflect.Value, field *fieldNode, args []node, final reflect.Value) reflect.Value {
return s.evalFieldChain(dot, dot, field.ident, args, final)
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
return s.evalFieldChain(dot, dot, field.Ident, args, final)
}
func (s *state) evalVariableNode(dot reflect.Value, v *variableNode, args []node, final reflect.Value) reflect.Value {
func (s *state) evalVariableNode(dot reflect.Value, v *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
value := s.varValue(v.ident[0])
if len(v.ident) == 1 {
value := s.varValue(v.Ident[0])
if len(v.Ident) == 1 {
return value
}
return s.evalFieldChain(dot, value, v.ident[1:], args, final)
return s.evalFieldChain(dot, value, v.Ident[1:], args, final)
}
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
// dot is the environment in which to evaluate arguments, while
// receiver is the value being walked along the chain.
func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args []node, final reflect.Value) reflect.Value {
func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
n := len(ident)
for i := 0; i < n-1; i++ {
receiver = s.evalField(dot, ident[i], nil, zero, receiver)
@ -346,7 +360,7 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args
return s.evalField(dot, ident[n-1], args, final, receiver)
}
func (s *state) evalFunction(dot reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
func (s *state) evalFunction(dot reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value {
function, ok := findFunction(name, s.tmpl, s.tmpl.set)
if !ok {
s.errorf("%q is not a defined function", name)
@ -357,7 +371,7 @@ func (s *state) evalFunction(dot reflect.Value, name string, args []node, final
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
// The 'final' argument represents the return value from the preceding
// value of the pipeline, if any.
func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final, receiver reflect.Value) reflect.Value {
func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node, final, receiver reflect.Value) reflect.Value {
if !receiver.IsValid() {
return zero
}
@ -411,7 +425,7 @@ var (
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
// as the function itself.
func (s *state) evalCall(dot, fun reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
func (s *state) evalCall(dot, fun reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value {
if args != nil {
args = args[1:] // Zeroth arg is function name/node; not passed to function.
}
@ -469,13 +483,13 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
return value
}
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n node) reflect.Value {
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
switch arg := n.(type) {
case *dotNode:
case *parse.DotNode:
return s.validateType(dot, typ)
case *fieldNode:
return s.validateType(s.evalFieldNode(dot, arg, []node{n}, zero), typ)
case *variableNode:
case *parse.FieldNode:
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
case *parse.VariableNode:
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
}
switch typ.Kind() {
@ -500,81 +514,81 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n node) reflect.Val
panic("not reached")
}
func (s *state) evalBool(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*boolNode); ok {
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
if n, ok := n.(*parse.BoolNode); ok {
value := reflect.New(typ).Elem()
value.SetBool(n.true)
value.SetBool(n.True)
return value
}
s.errorf("expected bool; found %s", n)
panic("not reached")
}
func (s *state) evalString(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*stringNode); ok {
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
if n, ok := n.(*parse.StringNode); ok {
value := reflect.New(typ).Elem()
value.SetString(n.text)
value.SetString(n.Text)
return value
}
s.errorf("expected string; found %s", n)
panic("not reached")
}
func (s *state) evalInteger(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isInt {
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
value := reflect.New(typ).Elem()
value.SetInt(n.int64)
value.SetInt(n.Int64)
return value
}
s.errorf("expected integer; found %s", n)
panic("not reached")
}
func (s *state) evalUnsignedInteger(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isUint {
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
value := reflect.New(typ).Elem()
value.SetUint(n.uint64)
value.SetUint(n.Uint64)
return value
}
s.errorf("expected unsigned integer; found %s", n)
panic("not reached")
}
func (s *state) evalFloat(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isFloat {
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
value := reflect.New(typ).Elem()
value.SetFloat(n.float64)
value.SetFloat(n.Float64)
return value
}
s.errorf("expected float; found %s", n)
panic("not reached")
}
func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isComplex {
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
value := reflect.New(typ).Elem()
value.SetComplex(n.complex128)
value.SetComplex(n.Complex128)
return value
}
s.errorf("expected complex; found %s", n)
panic("not reached")
}
func (s *state) evalEmptyInterface(dot reflect.Value, n node) reflect.Value {
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
switch n := n.(type) {
case *boolNode:
return reflect.ValueOf(n.true)
case *dotNode:
case *parse.BoolNode:
return reflect.ValueOf(n.True)
case *parse.DotNode:
return dot
case *fieldNode:
case *parse.FieldNode:
return s.evalFieldNode(dot, n, nil, zero)
case *identifierNode:
return s.evalFunction(dot, n.ident, nil, zero)
case *numberNode:
case *parse.IdentifierNode:
return s.evalFunction(dot, n.Ident, nil, zero)
case *parse.NumberNode:
return s.idealConstant(n)
case *stringNode:
return reflect.ValueOf(n.text)
case *variableNode:
case *parse.StringNode:
return reflect.ValueOf(n.Text)
case *parse.VariableNode:
return s.evalVariableNode(dot, n, nil, zero)
}
s.errorf("can't handle assignment of %s to empty interface argument", n)
@ -598,7 +612,7 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
// printValue writes the textual representation of the value to the output of
// the template.
func (s *state) printValue(n node, v reflect.Value) {
func (s *state) printValue(n parse.Node, v reflect.Value) {
if !v.IsValid() {
fmt.Fprint(s.wr, "<no value>")
return

View File

@ -6,6 +6,7 @@ package template
import (
"bytes"
"flag"
"fmt"
"os"
"reflect"
@ -14,6 +15,8 @@ import (
"testing"
)
var debug = flag.Bool("debug", false, "show the errors produced by the tests")
// T has lots of interesting pieces to use to test execution.
type T struct {
// Basics

View File

@ -22,7 +22,7 @@ import (
// during execution, execution terminates and Execute returns an error.
type FuncMap map[string]interface{}
var funcs = map[string]reflect.Value{
var builtins = map[string]reflect.Value{
"and": reflect.ValueOf(and),
"html": reflect.ValueOf(HTMLEscaper),
"index": reflect.ValueOf(index),
@ -73,7 +73,7 @@ func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) {
return fn, true
}
}
if fn := funcs[name]; fn.IsValid() {
if fn := builtins[name]; fn.IsValid() {
return fn, true
}
return reflect.Value{}, false

View File

@ -5,512 +5,22 @@
package template
import (
"bytes"
"fmt"
"exp/template/parse"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"unicode"
)
// Template is the representation of a parsed template.
type Template struct {
name string
root *listNode
name string
*parse.Tree
funcs map[string]reflect.Value
set *Set // can be nil.
// Parsing only; cleared after parse.
parseSet *Set // for function lookup during parse.
lex *lexer
token [2]item // two-token lookahead for parser.
peekCount int
vars []string // variables defined at the moment.
}
// Name returns the name of the template.
func (t *Template) Name() string {
return t.name
}
// next returns the next token.
func (t *Template) next() item {
if t.peekCount > 0 {
t.peekCount--
} else {
t.token[0] = t.lex.nextItem()
}
return t.token[t.peekCount]
}
// backup backs the input stream up one token.
func (t *Template) backup() {
t.peekCount++
}
// backup2 backs the input stream up two tokens
func (t *Template) backup2(t1 item) {
t.token[1] = t1
t.peekCount = 2
}
// peek returns but does not consume the next token.
func (t *Template) peek() item {
if t.peekCount > 0 {
return t.token[t.peekCount-1]
}
t.peekCount = 1
t.token[0] = t.lex.nextItem()
return t.token[0]
}
// A node is an element in the parse tree. The interface is trivial.
type node interface {
typ() nodeType
String() string
}
type nodeType int
func (t nodeType) typ() nodeType {
return t
}
const (
nodeText nodeType = iota
nodeAction
nodeCommand
nodeDot
nodeElse
nodeEnd
nodeField
nodeIdentifier
nodeIf
nodeList
nodeNumber
nodePipe
nodeRange
nodeString
nodeTemplate
nodeVariable
nodeWith
)
// Nodes.
// listNode holds a sequence of nodes.
type listNode struct {
nodeType
nodes []node
}
func newList() *listNode {
return &listNode{nodeType: nodeList}
}
func (l *listNode) append(n node) {
l.nodes = append(l.nodes, n)
}
func (l *listNode) String() string {
b := new(bytes.Buffer)
fmt.Fprint(b, "[")
for _, n := range l.nodes {
fmt.Fprint(b, n)
}
fmt.Fprint(b, "]")
return b.String()
}
// textNode holds plain text.
type textNode struct {
nodeType
text []byte
}
func newText(text string) *textNode {
return &textNode{nodeType: nodeText, text: []byte(text)}
}
func (t *textNode) String() string {
return fmt.Sprintf("(text: %q)", t.text)
}
// pipeNode holds a pipeline with optional declaration
type pipeNode struct {
nodeType
line int
decl []*variableNode
cmds []*commandNode
}
func newPipeline(line int, decl []*variableNode) *pipeNode {
return &pipeNode{nodeType: nodePipe, line: line, decl: decl}
}
func (p *pipeNode) append(command *commandNode) {
p.cmds = append(p.cmds, command)
}
func (p *pipeNode) String() string {
if p.decl != nil {
return fmt.Sprintf("%v := %v", p.decl, p.cmds)
}
return fmt.Sprintf("%v", p.cmds)
}
// actionNode holds an action (something bounded by delimiters).
type actionNode struct {
nodeType
line int
pipe *pipeNode
}
func newAction(line int, pipe *pipeNode) *actionNode {
return &actionNode{nodeType: nodeAction, line: line, pipe: pipe}
}
func (a *actionNode) String() string {
return fmt.Sprintf("(action: %v)", a.pipe)
}
// commandNode holds a command (a pipeline inside an evaluating action).
type commandNode struct {
nodeType
args []node // identifier, string, or number
}
func newCommand() *commandNode {
return &commandNode{nodeType: nodeCommand}
}
func (c *commandNode) append(arg node) {
c.args = append(c.args, arg)
}
func (c *commandNode) String() string {
return fmt.Sprintf("(command: %v)", c.args)
}
// identifierNode holds an identifier.
type identifierNode struct {
nodeType
ident string
}
func newIdentifier(ident string) *identifierNode {
return &identifierNode{nodeType: nodeIdentifier, ident: ident}
}
func (i *identifierNode) String() string {
return fmt.Sprintf("I=%s", i.ident)
}
// variableNode holds a variable.
type variableNode struct {
nodeType
ident []string
}
func newVariable(ident string) *variableNode {
return &variableNode{nodeType: nodeVariable, ident: strings.Split(ident, ".")}
}
func (v *variableNode) String() string {
return fmt.Sprintf("V=%s", v.ident)
}
// dotNode holds the special identifier '.'. It is represented by a nil pointer.
type dotNode bool
func newDot() *dotNode {
return nil
}
func (d *dotNode) typ() nodeType {
return nodeDot
}
func (d *dotNode) String() string {
return "{{<.>}}"
}
// fieldNode holds a field (identifier starting with '.').
// The names may be chained ('.x.y').
// The period is dropped from each ident.
type fieldNode struct {
nodeType
ident []string
}
func newField(ident string) *fieldNode {
return &fieldNode{nodeType: nodeField, ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
}
func (f *fieldNode) String() string {
return fmt.Sprintf("F=%s", f.ident)
}
// boolNode holds a boolean constant.
type boolNode struct {
nodeType
true bool
}
func newBool(true bool) *boolNode {
return &boolNode{nodeType: nodeString, true: true}
}
func (b *boolNode) String() string {
if b.true {
return fmt.Sprintf("B=true")
}
return fmt.Sprintf("B=false")
}
// numberNode holds a number, signed or unsigned integer, floating, 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.
type numberNode struct {
nodeType
isInt bool // number has an integral value
isUint bool // number has an unsigned integral value
isFloat bool // number has a floating-point value
isComplex bool // number is complex
int64 // the signed integer value
uint64 // the unsigned integer value
float64 // the floating-point value
complex128 // the complex value
text string
}
func newNumber(text string, typ itemType) (*numberNode, os.Error) {
n := &numberNode{nodeType: nodeNumber, text: text}
switch typ {
case itemCharConstant:
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
if err != nil {
return nil, err
}
if tail != "'" {
return nil, fmt.Errorf("malformed character constant: %s", text)
}
n.int64 = int64(rune)
n.isInt = true
n.uint64 = uint64(rune)
n.isUint = true
n.float64 = float64(rune) // odd but those are the rules.
n.isFloat = true
return n, nil
case itemComplex:
// fmt.Sscan can parse the pair, so let it do the work.
if _, err := fmt.Sscan(text, &n.complex128); err != nil {
return nil, err
}
n.isComplex = true
n.simplifyComplex()
return n, nil
}
// Imaginary constants can only be complex unless they are zero.
if len(text) > 0 && text[len(text)-1] == 'i' {
f, err := strconv.Atof64(text[:len(text)-1])
if err == nil {
n.isComplex = true
n.complex128 = complex(0, f)
n.simplifyComplex()
return n, nil
}
}
// Do integer test first so we get 0x123 etc.
u, err := strconv.Btoui64(text, 0) // will fail for -0; fixed below.
if err == nil {
n.isUint = true
n.uint64 = u
}
i, err := strconv.Btoi64(text, 0)
if err == nil {
n.isInt = true
n.int64 = i
if i == 0 {
n.isUint = true // in case of -0.
n.uint64 = u
}
}
// If an integer extraction succeeded, promote the float.
if n.isInt {
n.isFloat = true
n.float64 = float64(n.int64)
} else if n.isUint {
n.isFloat = true
n.float64 = float64(n.uint64)
} else {
f, err := strconv.Atof64(text)
if err == nil {
n.isFloat = true
n.float64 = f
// If a floating-point extraction succeeded, extract the int if needed.
if !n.isInt && float64(int64(f)) == f {
n.isInt = true
n.int64 = int64(f)
}
if !n.isUint && float64(uint64(f)) == f {
n.isUint = true
n.uint64 = uint64(f)
}
}
}
if !n.isInt && !n.isUint && !n.isFloat {
return nil, fmt.Errorf("illegal number syntax: %q", text)
}
return n, nil
}
// simplifyComplex pulls out any other types that are represented by the complex number.
// These all require that the imaginary part be zero.
func (n *numberNode) simplifyComplex() {
n.isFloat = imag(n.complex128) == 0
if n.isFloat {
n.float64 = real(n.complex128)
n.isInt = float64(int64(n.float64)) == n.float64
if n.isInt {
n.int64 = int64(n.float64)
}
n.isUint = float64(uint64(n.float64)) == n.float64
if n.isUint {
n.uint64 = uint64(n.float64)
}
}
}
func (n *numberNode) String() string {
return fmt.Sprintf("N=%s", n.text)
}
// stringNode holds a quoted string.
type stringNode struct {
nodeType
text string
}
func newString(text string) *stringNode {
return &stringNode{nodeType: nodeString, text: text}
}
func (s *stringNode) String() string {
return fmt.Sprintf("S=%#q", s.text)
}
// endNode represents an {{end}} action. It is represented by a nil pointer.
type endNode bool
func newEnd() *endNode {
return nil
}
func (e *endNode) typ() nodeType {
return nodeEnd
}
func (e *endNode) String() string {
return "{{end}}"
}
// elseNode represents an {{else}} action.
type elseNode struct {
nodeType
line int
}
func newElse(line int) *elseNode {
return &elseNode{nodeType: nodeElse, line: line}
}
func (e *elseNode) typ() nodeType {
return nodeElse
}
func (e *elseNode) String() string {
return "{{else}}"
}
// ifNode represents an {{if}} action and its commands.
// TODO: what should evaluation look like? is a pipe enough?
type ifNode struct {
nodeType
line int
pipe *pipeNode
list *listNode
elseList *listNode
}
func newIf(line int, pipe *pipeNode, list, elseList *listNode) *ifNode {
return &ifNode{nodeType: nodeIf, line: line, pipe: pipe, list: list, elseList: elseList}
}
func (i *ifNode) String() string {
if i.elseList != nil {
return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.pipe, i.list, i.elseList)
}
return fmt.Sprintf("({{if %s}} %s)", i.pipe, i.list)
}
// rangeNode represents a {{range}} action and its commands.
type rangeNode struct {
nodeType
line int
pipe *pipeNode
list *listNode
elseList *listNode
}
func newRange(line int, pipe *pipeNode, list, elseList *listNode) *rangeNode {
return &rangeNode{nodeType: nodeRange, line: line, pipe: pipe, list: list, elseList: elseList}
}
func (r *rangeNode) String() string {
if r.elseList != nil {
return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipe, r.list, r.elseList)
}
return fmt.Sprintf("({{range %s}} %s)", r.pipe, r.list)
}
// templateNode represents a {{template}} action.
type templateNode struct {
nodeType
line int
name string
pipe *pipeNode
}
func newTemplate(line int, name string, pipe *pipeNode) *templateNode {
return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipe: pipe}
}
func (t *templateNode) String() string {
if t.pipe == nil {
return fmt.Sprintf("{{template %q}}", t.name)
}
return fmt.Sprintf("{{template %q %s}}", t.name, t.pipe)
}
// withNode represents a {{with}} action and its commands.
type withNode struct {
nodeType
line int
pipe *pipeNode
list *listNode
elseList *listNode
}
func newWith(line int, pipe *pipeNode, list, elseList *listNode) *withNode {
return &withNode{nodeType: nodeWith, line: line, pipe: pipe, list: list, elseList: elseList}
}
func (w *withNode) String() string {
if w.elseList != nil {
return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.pipe, w.list, w.elseList)
}
return fmt.Sprintf("({{with %s}} %s)", w.pipe, w.list)
return t.Tree.Name
}
// Parsing.
@ -532,89 +42,13 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
return t
}
// errorf formats the error and terminates processing.
func (t *Template) errorf(format string, args ...interface{}) {
t.root = nil
format = fmt.Sprintf("template: %s:%d: %s", t.name, t.lex.lineNumber(), format)
panic(fmt.Errorf(format, args...))
}
// error terminates processing.
func (t *Template) error(err os.Error) {
t.errorf("%s", err)
}
// expect consumes the next token and guarantees it has the required type.
func (t *Template) expect(expected itemType, context string) item {
token := t.next()
if token.typ != expected {
t.errorf("expected %s in %s; got %s", expected, context, token)
}
return token
}
// unexpected complains about the token and terminates processing.
func (t *Template) unexpected(token item, context string) {
t.errorf("unexpected %s in %s", token, context)
}
// recover is the handler that turns panics into returns from the top
// level of Parse or Execute.
func (t *Template) recover(errp *os.Error) {
e := recover()
if e != nil {
if _, ok := e.(runtime.Error); ok {
panic(e)
}
t.stopParse()
*errp = e.(os.Error)
}
return
}
// startParse starts the template parsing from the lexer.
func (t *Template) startParse(set *Set, lex *lexer) {
t.root = nil
t.lex = lex
t.vars = []string{"$"}
t.parseSet = set
}
// stopParse terminates parsing.
func (t *Template) stopParse() {
t.lex = nil
t.vars = nil
t.parseSet = nil
}
// atEOF returns true if, possibly after spaces, we're at EOF.
func (t *Template) atEOF() bool {
for {
token := t.peek()
switch token.typ {
case itemEOF:
return true
case itemText:
for _, r := range token.val {
if !unicode.IsSpace(r) {
return false
}
}
t.next() // skip spaces.
continue
}
break
}
return false
}
// Parse parses the template definition string to construct an internal
// representation of the template for execution.
func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
defer t.recover(&err)
t.startParse(t.set, lex(t.name, s))
t.parse(true)
t.stopParse()
t.Tree, err = parse.New(t.name).Parse(s, t.funcs, builtins)
if err != nil {
return nil, err
}
return t, nil
}
@ -623,10 +57,14 @@ func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
// to the set.
// Function bindings are checked against those in the set.
func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err os.Error) {
defer t.recover(&err)
t.startParse(set, lex(t.name, s))
t.parse(true)
t.stopParse()
var setFuncs map[string]reflect.Value
if set != nil {
setFuncs = set.funcs
}
t.Tree, err = parse.New(t.name).Parse(s, t.funcs, setFuncs, builtins)
if err != nil {
return nil, err
}
t.addToSet(set)
return t, nil
}
@ -639,271 +77,3 @@ func (t *Template) addToSet(set *Set) {
// If double-assigned, Add will panic and we will turn that into an error.
set.Add(t)
}
// parse is the helper for Parse.
// It triggers an error if we expect EOF but don't reach it.
func (t *Template) parse(toEOF bool) (next node) {
t.root, next = t.itemList(true)
if toEOF && next != nil {
t.errorf("unexpected %s", next)
}
return next
}
// itemList:
// textOrAction*
// Terminates at EOF and at {{end}} or {{else}}, which is returned separately.
// The toEOF flag tells whether we expect to reach EOF.
func (t *Template) itemList(toEOF bool) (list *listNode, next node) {
list = newList()
for t.peek().typ != itemEOF {
n := t.textOrAction()
switch n.typ() {
case nodeEnd, nodeElse:
return list, n
}
list.append(n)
}
if !toEOF {
t.unexpected(t.next(), "input")
}
return list, nil
}
// textOrAction:
// text | action
func (t *Template) textOrAction() node {
switch token := t.next(); token.typ {
case itemText:
return newText(token.val)
case itemLeftDelim:
return t.action()
default:
t.unexpected(token, "input")
}
return nil
}
// Action:
// control
// command ("|" command)*
// Left delim is past. Now get actions.
// First word could be a keyword such as range.
func (t *Template) action() (n node) {
switch token := t.next(); token.typ {
case itemElse:
return t.elseControl()
case itemEnd:
return t.endControl()
case itemIf:
return t.ifControl()
case itemRange:
return t.rangeControl()
case itemTemplate:
return t.templateControl()
case itemWith:
return t.withControl()
}
t.backup()
// Do not pop variables; they persist until "end".
return newAction(t.lex.lineNumber(), t.pipeline("command"))
}
// Pipeline:
// field or command
// pipeline "|" pipeline
func (t *Template) pipeline(context string) (pipe *pipeNode) {
var decl []*variableNode
// Are there declarations?
for {
if v := t.peek(); v.typ == itemVariable {
t.next()
if next := t.peek(); next.typ == itemColonEquals || next.typ == itemChar {
t.next()
variable := newVariable(v.val)
if len(variable.ident) != 1 {
t.errorf("illegal variable in declaration: %s", v.val)
}
decl = append(decl, variable)
t.vars = append(t.vars, v.val)
if next.typ == itemChar && next.val == "," {
if context == "range" && len(decl) < 2 {
continue
}
t.errorf("too many declarations in %s", context)
}
} else {
t.backup2(v)
}
}
break
}
pipe = newPipeline(t.lex.lineNumber(), decl)
for {
switch token := t.next(); token.typ {
case itemRightDelim:
if len(pipe.cmds) == 0 {
t.errorf("missing value for %s", context)
}
return
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
itemVariable, itemNumber, itemRawString, itemString:
t.backup()
pipe.append(t.command())
default:
t.unexpected(token, context)
}
}
return
}
func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) {
lineNum = t.lex.lineNumber()
defer t.popVars(len(t.vars))
pipe = t.pipeline(context)
var next node
list, next = t.itemList(false)
switch next.typ() {
case nodeEnd: //done
case nodeElse:
elseList, next = t.itemList(false)
if next.typ() != nodeEnd {
t.errorf("expected end; found %s", next)
}
elseList = elseList
}
return lineNum, pipe, list, elseList
}
// If:
// {{if pipeline}} itemList {{end}}
// {{if pipeline}} itemList {{else}} itemList {{end}}
// If keyword is past.
func (t *Template) ifControl() node {
return newIf(t.parseControl("if"))
}
// Range:
// {{range pipeline}} itemList {{end}}
// {{range pipeline}} itemList {{else}} itemList {{end}}
// Range keyword is past.
func (t *Template) rangeControl() node {
return newRange(t.parseControl("range"))
}
// With:
// {{with pipeline}} itemList {{end}}
// {{with pipeline}} itemList {{else}} itemList {{end}}
// If keyword is past.
func (t *Template) withControl() node {
return newWith(t.parseControl("with"))
}
// End:
// {{end}}
// End keyword is past.
func (t *Template) endControl() node {
t.expect(itemRightDelim, "end")
return newEnd()
}
// Else:
// {{else}}
// Else keyword is past.
func (t *Template) elseControl() node {
t.expect(itemRightDelim, "else")
return newElse(t.lex.lineNumber())
}
// Template:
// {{template stringValue pipeline}}
// Template keyword is past. The name must be something that can evaluate
// to a string.
func (t *Template) templateControl() node {
var name string
switch token := t.next(); token.typ {
case itemString, itemRawString:
s, err := strconv.Unquote(token.val)
if err != nil {
t.error(err)
}
name = s
default:
t.unexpected(token, "template invocation")
}
var pipe *pipeNode
if t.next().typ != itemRightDelim {
t.backup()
// Do not pop variables; they persist until "end".
pipe = t.pipeline("template")
}
return newTemplate(t.lex.lineNumber(), name, pipe)
}
// command:
// space-separated arguments up to a pipeline character or right delimiter.
// we consume the pipe character but leave the right delim to terminate the action.
func (t *Template) command() *commandNode {
cmd := newCommand()
Loop:
for {
switch token := t.next(); token.typ {
case itemRightDelim:
t.backup()
break Loop
case itemPipe:
break Loop
case itemError:
t.errorf("%s", token.val)
case itemIdentifier:
if _, ok := findFunction(token.val, t, t.parseSet); !ok {
t.errorf("function %q not defined", token.val)
}
cmd.append(newIdentifier(token.val))
case itemDot:
cmd.append(newDot())
case itemVariable:
cmd.append(t.useVar(token.val))
case itemField:
cmd.append(newField(token.val))
case itemBool:
cmd.append(newBool(token.val == "true"))
case itemCharConstant, itemComplex, itemNumber:
number, err := newNumber(token.val, token.typ)
if err != nil {
t.error(err)
}
cmd.append(number)
case itemString, itemRawString:
s, err := strconv.Unquote(token.val)
if err != nil {
t.error(err)
}
cmd.append(newString(s))
default:
t.unexpected(token, "command")
}
}
if len(cmd.args) == 0 {
t.errorf("empty command")
}
return cmd
}
// popVars trims the variable list to the specified length
func (t *Template) popVars(n int) {
t.vars = t.vars[:n]
}
// useVar returns a node for a variable reference. It errors if the
// variable is not defined.
func (t *Template) useVar(name string) node {
v := newVariable(name)
for _, varName := range t.vars {
if varName == v.ident[0] {
return v
}
}
t.errorf("undefined variable %q", v.ident[0])
return nil
}

View File

@ -0,0 +1,14 @@
# Copyright 2011 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include ../../../../Make.inc
TARG=exp/template/parse
GOFILES=\
lex.go\
node.go\
parse.go\
set.go\
include ../../../../Make.pkg

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
package parse
import (
"fmt"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
package parse
import (
"reflect"

View File

@ -0,0 +1,468 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Parse nodes.
package parse
import (
"bytes"
"fmt"
"os"
"strconv"
"strings"
)
// A node is an element in the parse tree. The interface is trivial.
type Node interface {
Type() NodeType
String() string
}
// NodeType identifies the type of a parse tree node.
type NodeType int
// Type returns itself and provides an easy default implementation
// for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeType {
return t
}
const (
NodeText NodeType = iota // Plain text.
NodeAction // An simple action such as field evaluation.
NodeBool // A boolean constant.
NodeCommand // An element of a pipeline.
NodeDot // The cursor, dot.
NodeElse // An else action.
NodeEnd // An end action.
NodeField // A field or method name.
NodeIdentifier // A identifier; always a function name.
NodeIf // An if action.
NodeList // A list of Nodes.
NodeNumber // A numerical constant.
NodePipe // A pipeline of commands.
NodeRange // A range action.
NodeString // A string constant.
NodeTemplate // A template invocation action.
NodeVariable // A $ variable.
NodeWith // A with action.
)
// Nodes.
// ListNode holds a sequence of nodes.
type ListNode struct {
NodeType
Nodes []Node // The element nodes in lexical order.
}
func newList() *ListNode {
return &ListNode{NodeType: NodeList}
}
func (l *ListNode) append(n Node) {
l.Nodes = append(l.Nodes, n)
}
func (l *ListNode) String() string {
b := new(bytes.Buffer)
fmt.Fprint(b, "[")
for _, n := range l.Nodes {
fmt.Fprint(b, n)
}
fmt.Fprint(b, "]")
return b.String()
}
// TextNode holds plain text.
type TextNode struct {
NodeType
Text []byte // The text; may span newlines.
}
func newText(text string) *TextNode {
return &TextNode{NodeType: NodeText, Text: []byte(text)}
}
func (t *TextNode) String() string {
return fmt.Sprintf("(text: %q)", t.Text)
}
// PipeNode holds a pipeline with optional declaration
type PipeNode struct {
NodeType
Line int // The line number in the input.
Decl []*VariableNode // Variable declarations in lexical order.
Cmds []*CommandNode // The commands in lexical order.
}
func newPipeline(line int, decl []*VariableNode) *PipeNode {
return &PipeNode{NodeType: NodePipe, Line: line, Decl: decl}
}
func (p *PipeNode) append(command *CommandNode) {
p.Cmds = append(p.Cmds, command)
}
func (p *PipeNode) String() string {
if p.Decl != nil {
return fmt.Sprintf("%v := %v", p.Decl, p.Cmds)
}
return fmt.Sprintf("%v", p.Cmds)
}
// ActionNode holds an action (something bounded by delimiters).
// Control actions have their own nodes; ActionNode represents simple
// ones such as field evaluations.
type ActionNode struct {
NodeType
Line int // The line number in the input.
Pipe *PipeNode // The pipeline in the action.
}
func newAction(line int, pipe *PipeNode) *ActionNode {
return &ActionNode{NodeType: NodeAction, Line: line, Pipe: pipe}
}
func (a *ActionNode) String() string {
return fmt.Sprintf("(action: %v)", a.Pipe)
}
// CommandNode holds a command (a pipeline inside an evaluating action).
type CommandNode struct {
NodeType
Args []Node // Arguments in lexical order: Identifier, field, or constant.
}
func newCommand() *CommandNode {
return &CommandNode{NodeType: NodeCommand}
}
func (c *CommandNode) append(arg Node) {
c.Args = append(c.Args, arg)
}
func (c *CommandNode) String() string {
return fmt.Sprintf("(command: %v)", c.Args)
}
// IdentifierNode holds an identifier.
type IdentifierNode struct {
NodeType
Ident string // The identifier's name.
}
func newIdentifier(ident string) *IdentifierNode {
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
}
func (i *IdentifierNode) String() string {
return fmt.Sprintf("I=%s", i.Ident)
}
// VariableNode holds a list of variable names. The dollar sign is
// part of the name.
type VariableNode struct {
NodeType
Ident []string // Variable names in lexical order.
}
func newVariable(ident string) *VariableNode {
return &VariableNode{NodeType: NodeVariable, Ident: strings.Split(ident, ".")}
}
func (v *VariableNode) String() string {
return fmt.Sprintf("V=%s", v.Ident)
}
// DotNode holds the special identifier '.'. It is represented by a nil pointer.
type DotNode bool
func newDot() *DotNode {
return nil
}
func (d *DotNode) Type() NodeType {
return NodeDot
}
func (d *DotNode) String() string {
return "{{<.>}}"
}
// FieldNode holds a field (identifier starting with '.').
// The names may be chained ('.x.y').
// The period is dropped from each ident.
type FieldNode struct {
NodeType
Ident []string // The identifiers in lexical order.
}
func newField(ident string) *FieldNode {
return &FieldNode{NodeType: NodeField, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
}
func (f *FieldNode) String() string {
return fmt.Sprintf("F=%s", f.Ident)
}
// BoolNode holds a boolean constant.
type BoolNode struct {
NodeType
True bool // The value of the boolean constant.
}
func newBool(true bool) *BoolNode {
return &BoolNode{NodeType: NodeBool, True: true}
}
func (b *BoolNode) String() string {
return fmt.Sprintf("B=%t", 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.
type NumberNode struct {
NodeType
IsInt bool // Number has an integral value.
IsUint bool // Number has an unsigned integral value.
IsFloat bool // Number has a floating-point value.
IsComplex bool // Number is complex.
Int64 int64 // The signed integer value.
Uint64 uint64 // The unsigned integer value.
Float64 float64 // The floating-point value.
Complex128 complex128 // The complex value.
Text string // The original textual representation from the input.
}
func newNumber(text string, typ itemType) (*NumberNode, os.Error) {
n := &NumberNode{NodeType: NodeNumber, Text: text}
switch typ {
case itemCharConstant:
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
if err != nil {
return nil, err
}
if tail != "'" {
return nil, fmt.Errorf("malformed character constant: %s", text)
}
n.Int64 = int64(rune)
n.IsInt = true
n.Uint64 = uint64(rune)
n.IsUint = true
n.Float64 = float64(rune) // odd but those are the rules.
n.IsFloat = true
return n, nil
case itemComplex:
// fmt.Sscan can parse the pair, so let it do the work.
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
return nil, err
}
n.IsComplex = true
n.simplifyComplex()
return n, nil
}
// Imaginary constants can only be complex unless they are zero.
if len(text) > 0 && text[len(text)-1] == 'i' {
f, err := strconv.Atof64(text[:len(text)-1])
if err == nil {
n.IsComplex = true
n.Complex128 = complex(0, f)
n.simplifyComplex()
return n, nil
}
}
// Do integer test first so we get 0x123 etc.
u, err := strconv.Btoui64(text, 0) // will fail for -0; fixed below.
if err == nil {
n.IsUint = true
n.Uint64 = u
}
i, err := strconv.Btoi64(text, 0)
if err == nil {
n.IsInt = true
n.Int64 = i
if i == 0 {
n.IsUint = true // in case of -0.
n.Uint64 = u
}
}
// If an integer extraction succeeded, promote the float.
if n.IsInt {
n.IsFloat = true
n.Float64 = float64(n.Int64)
} else if n.IsUint {
n.IsFloat = true
n.Float64 = float64(n.Uint64)
} else {
f, err := strconv.Atof64(text)
if err == nil {
n.IsFloat = true
n.Float64 = f
// If a floating-point extraction succeeded, extract the int if needed.
if !n.IsInt && float64(int64(f)) == f {
n.IsInt = true
n.Int64 = int64(f)
}
if !n.IsUint && float64(uint64(f)) == f {
n.IsUint = true
n.Uint64 = uint64(f)
}
}
}
if !n.IsInt && !n.IsUint && !n.IsFloat {
return nil, fmt.Errorf("illegal number syntax: %q", text)
}
return n, nil
}
// simplifyComplex pulls out any other types that are represented by the complex number.
// These all require that the imaginary part be zero.
func (n *NumberNode) simplifyComplex() {
n.IsFloat = imag(n.Complex128) == 0
if n.IsFloat {
n.Float64 = real(n.Complex128)
n.IsInt = float64(int64(n.Float64)) == n.Float64
if n.IsInt {
n.Int64 = int64(n.Float64)
}
n.IsUint = float64(uint64(n.Float64)) == n.Float64
if n.IsUint {
n.Uint64 = uint64(n.Float64)
}
}
}
func (n *NumberNode) String() string {
return fmt.Sprintf("N=%s", n.Text)
}
// StringNode holds a string constant. The value has been "unquoted".
type StringNode struct {
NodeType
Quoted string // The original text of the string, with quotes.
Text string // The string, after quote processing.
}
func newString(orig, text string) *StringNode {
return &StringNode{NodeType: NodeString, Quoted: orig, Text: text}
}
func (s *StringNode) String() string {
return fmt.Sprintf("S=%#q", s.Text)
}
// EndNode represents an {{end}} action. It is represented by a nil pointer.
type EndNode bool
func newEnd() *EndNode {
return nil
}
func (e *EndNode) Type() NodeType {
return NodeEnd
}
func (e *EndNode) String() string {
return "{{end}}"
}
// ElseNode represents an {{else}} action.
type ElseNode struct {
NodeType
Line int // The line number in the input.
}
func newElse(line int) *ElseNode {
return &ElseNode{NodeType: NodeElse, Line: line}
}
func (e *ElseNode) Type() NodeType {
return NodeElse
}
func (e *ElseNode) String() string {
return "{{else}}"
}
// IfNode represents an {{if}} action and its commands.
type IfNode struct {
NodeType
Line int // The line number in the input.
Pipe *PipeNode // The pipeline to be evaluated.
List *ListNode // What to execute if the value is non-empty.
ElseList *ListNode // What to execute if the value is empty (nil if absent).
}
func newIf(line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
return &IfNode{NodeType: NodeIf, Line: line, Pipe: pipe, List: list, ElseList: elseList}
}
func (i *IfNode) String() string {
if i.ElseList != nil {
return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.Pipe, i.List, i.ElseList)
}
return fmt.Sprintf("({{if %s}} %s)", i.Pipe, i.List)
}
// RangeNode represents a {{range}} action and its commands.
type RangeNode struct {
NodeType
Line int // The line number in the input.
Pipe *PipeNode // The pipeline to be evaluated.
List *ListNode // What to execute if the value is non-empty.
ElseList *ListNode // What to execute if the value is empty (nil if absent).
}
func newRange(line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
return &RangeNode{NodeType: NodeRange, Line: line, Pipe: pipe, List: list, ElseList: elseList}
}
func (r *RangeNode) String() string {
if r.ElseList != nil {
return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.Pipe, r.List, r.ElseList)
}
return fmt.Sprintf("({{range %s}} %s)", r.Pipe, r.List)
}
// TemplateNode represents a {{template}} action.
type TemplateNode struct {
NodeType
Line int // The line number in the input.
Name string // The name of the template (unquoted).
Pipe *PipeNode // The command to evaluate as dot for the template.
}
func newTemplate(line int, name string, pipe *PipeNode) *TemplateNode {
return &TemplateNode{NodeType: NodeTemplate, Line: line, Name: name, Pipe: pipe}
}
func (t *TemplateNode) String() string {
if t.Pipe == nil {
return fmt.Sprintf("{{template %q}}", t.Name)
}
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
}
// WithNode represents a {{with}} action and its commands.
type WithNode struct {
NodeType
Line int // The line number in the input.
Pipe *PipeNode // The pipeline to be evaluated.
List *ListNode // What to execute if the value is non-empty.
ElseList *ListNode // What to execute if the value is empty (nil if absent).
}
func newWith(line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
return &WithNode{NodeType: NodeWith, Line: line, Pipe: pipe, List: list, ElseList: elseList}
}
func (w *WithNode) String() string {
if w.ElseList != nil {
return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.Pipe, w.List, w.ElseList)
}
return fmt.Sprintf("({{with %s}} %s)", w.Pipe, w.List)
}

View File

@ -0,0 +1,437 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package parse builds parse trees for templates. The grammar is defined
// in the documents for the exp/template package.
package parse
import (
"fmt"
"os"
"reflect"
"runtime"
"strconv"
"unicode"
)
// Tree is the representation of a parsed template.
type Tree struct {
Name string // Name is the name of the template.
Root *ListNode // Root is the top-level root of the parse tree.
// Parsing only; cleared after parse.
funcs []map[string]reflect.Value
lex *lexer
token [2]item // two-token lookahead for parser.
peekCount int
vars []string // variables defined at the moment.
}
// next returns the next token.
func (t *Tree) next() item {
if t.peekCount > 0 {
t.peekCount--
} else {
t.token[0] = t.lex.nextItem()
}
return t.token[t.peekCount]
}
// backup backs the input stream up one token.
func (t *Tree) backup() {
t.peekCount++
}
// backup2 backs the input stream up two tokens
func (t *Tree) backup2(t1 item) {
t.token[1] = t1
t.peekCount = 2
}
// peek returns but does not consume the next token.
func (t *Tree) peek() item {
if t.peekCount > 0 {
return t.token[t.peekCount-1]
}
t.peekCount = 1
t.token[0] = t.lex.nextItem()
return t.token[0]
}
// Parsing.
// New allocates a new template with the given name.
func New(name string, funcs ...map[string]reflect.Value) *Tree {
return &Tree{
Name: name,
funcs: funcs,
}
}
// errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) {
t.Root = nil
format = fmt.Sprintf("template: %s:%d: %s", t.Name, t.lex.lineNumber(), format)
panic(fmt.Errorf(format, args...))
}
// error terminates processing.
func (t *Tree) error(err os.Error) {
t.errorf("%s", err)
}
// expect consumes the next token and guarantees it has the required type.
func (t *Tree) expect(expected itemType, context string) item {
token := t.next()
if token.typ != expected {
t.errorf("expected %s in %s; got %s", expected, context, token)
}
return token
}
// unexpected complains about the token and terminates processing.
func (t *Tree) unexpected(token item, context string) {
t.errorf("unexpected %s in %s", token, context)
}
// recover is the handler that turns panics into returns from the top level of Parse.
func (t *Tree) recover(errp *os.Error) {
e := recover()
if e != nil {
if _, ok := e.(runtime.Error); ok {
panic(e)
}
if t != nil {
t.stopParse()
}
*errp = e.(os.Error)
}
return
}
// startParse starts the template parsing from the lexer.
func (t *Tree) startParse(funcs []map[string]reflect.Value, lex *lexer) {
t.Root = nil
t.lex = lex
t.vars = []string{"$"}
t.funcs = funcs
}
// stopParse terminates parsing.
func (t *Tree) stopParse() {
t.lex = nil
t.vars = nil
t.funcs = nil
}
// atEOF returns true if, possibly after spaces, we're at EOF.
func (t *Tree) atEOF() bool {
for {
token := t.peek()
switch token.typ {
case itemEOF:
return true
case itemText:
for _, r := range token.val {
if !unicode.IsSpace(r) {
return false
}
}
t.next() // skip spaces.
continue
}
break
}
return false
}
// Parse parses the template definition string to construct an internal
// representation of the template for execution.
func (t *Tree) Parse(s string, funcs ...map[string]reflect.Value) (tree *Tree, err os.Error) {
defer t.recover(&err)
t.startParse(funcs, lex(t.Name, s))
t.parse(true)
t.stopParse()
return t, nil
}
// parse is the helper for Parse.
// It triggers an error if we expect EOF but don't reach it.
func (t *Tree) parse(toEOF bool) (next Node) {
t.Root, next = t.itemList(true)
if toEOF && next != nil {
t.errorf("unexpected %s", next)
}
return next
}
// itemList:
// textOrAction*
// Terminates at EOF and at {{end}} or {{else}}, which is returned separately.
// The toEOF flag tells whether we expect to reach EOF.
func (t *Tree) itemList(toEOF bool) (list *ListNode, next Node) {
list = newList()
for t.peek().typ != itemEOF {
n := t.textOrAction()
switch n.Type() {
case NodeEnd, NodeElse:
return list, n
}
list.append(n)
}
if !toEOF {
t.unexpected(t.next(), "input")
}
return list, nil
}
// textOrAction:
// text | action
func (t *Tree) textOrAction() Node {
switch token := t.next(); token.typ {
case itemText:
return newText(token.val)
case itemLeftDelim:
return t.action()
default:
t.unexpected(token, "input")
}
return nil
}
// Action:
// control
// command ("|" command)*
// Left delim is past. Now get actions.
// First word could be a keyword such as range.
func (t *Tree) action() (n Node) {
switch token := t.next(); token.typ {
case itemElse:
return t.elseControl()
case itemEnd:
return t.endControl()
case itemIf:
return t.ifControl()
case itemRange:
return t.rangeControl()
case itemTemplate:
return t.templateControl()
case itemWith:
return t.withControl()
}
t.backup()
// Do not pop variables; they persist until "end".
return newAction(t.lex.lineNumber(), t.pipeline("command"))
}
// Pipeline:
// field or command
// pipeline "|" pipeline
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
var decl []*VariableNode
// Are there declarations?
for {
if v := t.peek(); v.typ == itemVariable {
t.next()
if next := t.peek(); next.typ == itemColonEquals || next.typ == itemChar {
t.next()
variable := newVariable(v.val)
if len(variable.Ident) != 1 {
t.errorf("illegal variable in declaration: %s", v.val)
}
decl = append(decl, variable)
t.vars = append(t.vars, v.val)
if next.typ == itemChar && next.val == "," {
if context == "range" && len(decl) < 2 {
continue
}
t.errorf("too many declarations in %s", context)
}
} else {
t.backup2(v)
}
}
break
}
pipe = newPipeline(t.lex.lineNumber(), decl)
for {
switch token := t.next(); token.typ {
case itemRightDelim:
if len(pipe.Cmds) == 0 {
t.errorf("missing value for %s", context)
}
return
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
itemVariable, itemNumber, itemRawString, itemString:
t.backup()
pipe.append(t.command())
default:
t.unexpected(token, context)
}
}
return
}
func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, elseList *ListNode) {
lineNum = t.lex.lineNumber()
defer t.popVars(len(t.vars))
pipe = t.pipeline(context)
var next Node
list, next = t.itemList(false)
switch next.Type() {
case NodeEnd: //done
case NodeElse:
elseList, next = t.itemList(false)
if next.Type() != NodeEnd {
t.errorf("expected end; found %s", next)
}
elseList = elseList
}
return lineNum, pipe, list, elseList
}
// If:
// {{if pipeline}} itemList {{end}}
// {{if pipeline}} itemList {{else}} itemList {{end}}
// If keyword is past.
func (t *Tree) ifControl() Node {
return newIf(t.parseControl("if"))
}
// Range:
// {{range pipeline}} itemList {{end}}
// {{range pipeline}} itemList {{else}} itemList {{end}}
// Range keyword is past.
func (t *Tree) rangeControl() Node {
return newRange(t.parseControl("range"))
}
// With:
// {{with pipeline}} itemList {{end}}
// {{with pipeline}} itemList {{else}} itemList {{end}}
// If keyword is past.
func (t *Tree) withControl() Node {
return newWith(t.parseControl("with"))
}
// End:
// {{end}}
// End keyword is past.
func (t *Tree) endControl() Node {
t.expect(itemRightDelim, "end")
return newEnd()
}
// Else:
// {{else}}
// Else keyword is past.
func (t *Tree) elseControl() Node {
t.expect(itemRightDelim, "else")
return newElse(t.lex.lineNumber())
}
// Template:
// {{template stringValue pipeline}}
// Template keyword is past. The name must be something that can evaluate
// to a string.
func (t *Tree) templateControl() Node {
var name string
switch token := t.next(); token.typ {
case itemString, itemRawString:
s, err := strconv.Unquote(token.val)
if err != nil {
t.error(err)
}
name = s
default:
t.unexpected(token, "template invocation")
}
var pipe *PipeNode
if t.next().typ != itemRightDelim {
t.backup()
// Do not pop variables; they persist until "end".
pipe = t.pipeline("template")
}
return newTemplate(t.lex.lineNumber(), name, pipe)
}
// command:
// space-separated arguments up to a pipeline character or right delimiter.
// we consume the pipe character but leave the right delim to terminate the action.
func (t *Tree) command() *CommandNode {
cmd := newCommand()
Loop:
for {
switch token := t.next(); token.typ {
case itemRightDelim:
t.backup()
break Loop
case itemPipe:
break Loop
case itemError:
t.errorf("%s", token.val)
case itemIdentifier:
if _, ok := t.findFunction(token.val); !ok {
t.errorf("function %q not defined", token.val)
}
cmd.append(newIdentifier(token.val))
case itemDot:
cmd.append(newDot())
case itemVariable:
cmd.append(t.useVar(token.val))
case itemField:
cmd.append(newField(token.val))
case itemBool:
cmd.append(newBool(token.val == "true"))
case itemCharConstant, itemComplex, itemNumber:
number, err := newNumber(token.val, token.typ)
if err != nil {
t.error(err)
}
cmd.append(number)
case itemString, itemRawString:
s, err := strconv.Unquote(token.val)
if err != nil {
t.error(err)
}
cmd.append(newString(token.val, s))
default:
t.unexpected(token, "command")
}
}
if len(cmd.Args) == 0 {
t.errorf("empty command")
}
return cmd
}
// findFunction looks for a function in the Tree's maps.
func (t *Tree) findFunction(name string) (reflect.Value, bool) {
for _, funcMap := range t.funcs {
if funcMap == nil {
continue
}
if fn := funcMap[name]; fn.IsValid() {
return fn, true
}
}
return reflect.Value{}, false
}
// popVars trims the variable list to the specified length
func (t *Tree) popVars(n int) {
t.vars = t.vars[:n]
}
// useVar returns a node for a variable reference. It errors if the
// variable is not defined.
func (t *Tree) useVar(name string) Node {
v := newVariable(name)
for _, varName := range t.vars {
if varName == v.Ident[0] {
return v
}
}
t.errorf("undefined variable %q", v.Ident[0])
return nil
}

View File

@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
package parse
import (
"flag"
"fmt"
"reflect"
"testing"
)
@ -100,47 +101,47 @@ func TestNumberParse(t *testing.T) {
}
continue
}
if n.isComplex != test.isComplex {
if n.IsComplex != test.isComplex {
t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
}
if test.isInt {
if !n.isInt {
if !n.IsInt {
t.Errorf("expected integer for %q", test.text)
}
if n.int64 != test.int64 {
t.Errorf("int64 for %q should be %d is %d", test.text, test.int64, n.int64)
if n.Int64 != test.int64 {
t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64)
}
} else if n.isInt {
} else if n.IsInt {
t.Errorf("did not expect integer for %q", test.text)
}
if test.isUint {
if !n.isUint {
if !n.IsUint {
t.Errorf("expected unsigned integer for %q", test.text)
}
if n.uint64 != test.uint64 {
t.Errorf("uint64 for %q should be %d is %d", test.text, test.uint64, n.uint64)
if n.Uint64 != test.uint64 {
t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64)
}
} else if n.isUint {
} else if n.IsUint {
t.Errorf("did not expect unsigned integer for %q", test.text)
}
if test.isFloat {
if !n.isFloat {
if !n.IsFloat {
t.Errorf("expected float for %q", test.text)
}
if n.float64 != test.float64 {
t.Errorf("float64 for %q should be %g is %g", test.text, test.float64, n.float64)
if n.Float64 != test.float64 {
t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64)
}
} else if n.isFloat {
} else if n.IsFloat {
t.Errorf("did not expect float for %q", test.text)
}
if test.isComplex {
if !n.isComplex {
if !n.IsComplex {
t.Errorf("expected complex for %q", test.text)
}
if n.complex128 != test.complex128 {
t.Errorf("complex128 for %q should be %g is %g", test.text, test.complex128, n.complex128)
if n.Complex128 != test.complex128 {
t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128)
}
} else if n.isComplex {
} else if n.IsComplex {
t.Errorf("did not expect complex for %q", test.text)
}
}
@ -161,6 +162,8 @@ const (
var parseTests = []parseTest{
{"empty", "", noError,
`[]`},
{"comment", "{{/*\n\n\n*/}}", noError,
`[]`},
{"spaces", " \t\n", noError,
`[(text: " \t\n")]`},
{"text", "some text", noError,
@ -228,9 +231,13 @@ var parseTests = []parseTest{
{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
}
var builtins = map[string]reflect.Value{
"printf": reflect.ValueOf(fmt.Sprintf),
}
func TestParse(t *testing.T) {
for _, test := range parseTests {
tmpl, err := New(test.name).Parse(test.input)
tmpl, err := New(test.name).Parse(test.input, builtins)
switch {
case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name)
@ -245,7 +252,7 @@ func TestParse(t *testing.T) {
}
continue
}
result := tmpl.root.String()
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)
}

View File

@ -0,0 +1,51 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parse
import (
"fmt"
"os"
"reflect"
"strconv"
)
// Set returns a slice of Trees created by parsing the template set
// definition in the argument string. If an error is encountered,
// parsing stops and an empty slice is returned with the error.
func Set(text string, funcs ...map[string]reflect.Value) (tree map[string]*Tree, err os.Error) {
tree = make(map[string]*Tree)
defer (*Tree)(nil).recover(&err)
lex := lex("set", text)
const context = "define clause"
for {
t := New("set") // name will be updated once we know it.
t.startParse(funcs, lex)
// Expect EOF or "{{ define name }}".
if t.atEOF() {
break
}
t.expect(itemLeftDelim, context)
t.expect(itemDefine, context)
name := t.expect(itemString, context)
t.Name, err = strconv.Unquote(name.val)
if err != nil {
t.error(err)
}
t.expect(itemRightDelim, context)
end := t.parse(false)
if end == nil {
t.errorf("unexpected EOF in %s", context)
}
if end.Type() != NodeEnd {
t.errorf("unexpected %s in %s", end, context)
}
t.stopParse()
if _, present := tree[t.Name]; present {
return nil, fmt.Errorf("template: %q multiply defined", name)
}
tree[t.Name] = t
}
return
}

View File

@ -5,12 +5,11 @@
package template
import (
"exp/template/parse"
"fmt"
"io"
"os"
"reflect"
"runtime"
"strconv"
)
// Set holds a set of related templates that can refer to one another by name.
@ -71,6 +70,11 @@ func (s *Set) Template(name string) *Template {
return s.tmpl[name]
}
// FuncMap returns the set's function map.
func (s *Set) FuncMap() map[string]reflect.Value {
return s.funcs
}
// Execute applies the named template to the specified data object, writing
// the output to wr.
func (s *Set) Execute(wr io.Writer, name string, data interface{}) os.Error {
@ -81,55 +85,21 @@ func (s *Set) Execute(wr io.Writer, name string, data interface{}) os.Error {
return tmpl.Execute(wr, data)
}
// recover is the handler that turns panics into returns from the top
// level of Parse.
func (s *Set) recover(errp *os.Error) {
e := recover()
if e != nil {
if _, ok := e.(runtime.Error); ok {
panic(e)
}
s.tmpl = nil
*errp = e.(os.Error)
}
return
}
// Parse parses a string into a set of named templates. Parse may be called
// multiple times for a given set, adding the templates defined in the string
// to the set. If a template is redefined, the element in the set is
// overwritten with the new definition.
func (s *Set) Parse(text string) (set *Set, err os.Error) {
set = s
func (s *Set) Parse(text string) (*Set, os.Error) {
trees, err := parse.Set(text, s.funcs, builtins)
if err != nil {
return nil, err
}
s.init()
defer s.recover(&err)
lex := lex("set", text)
const context = "define clause"
for {
t := New("set") // name will be updated once we know it.
t.startParse(s, lex)
// Expect EOF or "{{ define name }}".
if t.atEOF() {
return nil, err
}
t.expect(itemLeftDelim, context)
t.expect(itemDefine, context)
name := t.expect(itemString, context)
t.name, err = strconv.Unquote(name.val)
if err != nil {
t.error(err)
}
t.expect(itemRightDelim, context)
end := t.parse(false)
if end == nil {
t.errorf("unexpected EOF in %s", context)
}
if end.typ() != nodeEnd {
t.errorf("unexpected %s in %s", end, context)
}
t.stopParse()
t.addToSet(s)
s.tmpl[t.name] = t
for name, tree := range trees {
tmpl := New(name)
tmpl.Tree = tree
tmpl.addToSet(s)
s.tmpl[name] = tmpl
}
return s, nil
}

View File

@ -9,6 +9,11 @@ import (
"testing"
)
const (
noError = true
hasError = false
)
type setParseTest struct {
name string
input string
@ -66,7 +71,7 @@ func TestSetParse(t *testing.T) {
t.Errorf("%s: can't find template %q", test.name, name)
continue
}
result := tmpl.root.String()
result := tmpl.Root.String()
if result != test.results[i] {
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
}