mirror of
https://github.com/golang/go
synced 2024-11-21 22:14:41 -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:
parent
476150f4bf
commit
c66917d2b6
@ -83,6 +83,7 @@ DIRS=\
|
|||||||
exp/norm\
|
exp/norm\
|
||||||
exp/regexp/syntax\
|
exp/regexp/syntax\
|
||||||
exp/template\
|
exp/template\
|
||||||
|
exp/template/parse\
|
||||||
expvar\
|
expvar\
|
||||||
flag\
|
flag\
|
||||||
fmt\
|
fmt\
|
||||||
|
@ -9,7 +9,6 @@ GOFILES=\
|
|||||||
exec.go\
|
exec.go\
|
||||||
funcs.go\
|
funcs.go\
|
||||||
helper.go\
|
helper.go\
|
||||||
lex.go\
|
|
||||||
parse.go\
|
parse.go\
|
||||||
set.go\
|
set.go\
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"exp/template/parse"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,7 +65,7 @@ var zero reflect.Value
|
|||||||
|
|
||||||
// errorf formats the error and terminates processing.
|
// errorf formats the error and terminates processing.
|
||||||
func (s *state) errorf(format string, args ...interface{}) {
|
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...))
|
panic(fmt.Errorf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +74,22 @@ func (s *state) error(err os.Error) {
|
|||||||
s.errorf("%s", err)
|
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,
|
// Execute applies a parsed template to the specified data object,
|
||||||
// writing the output to wr.
|
// writing the output to wr.
|
||||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
|
func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
|
||||||
defer t.recover(&err)
|
defer errRecover(&err)
|
||||||
value := reflect.ValueOf(data)
|
value := reflect.ValueOf(data)
|
||||||
state := &state{
|
state := &state{
|
||||||
tmpl: t,
|
tmpl: t,
|
||||||
@ -83,45 +97,45 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
|
|||||||
line: 1,
|
line: 1,
|
||||||
vars: []variable{{"$", value}},
|
vars: []variable{{"$", value}},
|
||||||
}
|
}
|
||||||
if t.root == nil {
|
if t.Root == nil {
|
||||||
state.errorf("must be parsed before execution")
|
state.errorf("must be parsed before execution")
|
||||||
}
|
}
|
||||||
state.walk(value, t.root)
|
state.walk(value, t.Root)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk functions step through the major pieces of the template structure,
|
// Walk functions step through the major pieces of the template structure,
|
||||||
// generating output as they go.
|
// 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) {
|
switch n := n.(type) {
|
||||||
case *actionNode:
|
case *parse.ActionNode:
|
||||||
s.line = n.line
|
s.line = n.Line
|
||||||
// Do not pop variables so they persist until next end.
|
// Do not pop variables so they persist until next end.
|
||||||
// Also, if the action declares variables, don't print the result.
|
// Also, if the action declares variables, don't print the result.
|
||||||
val := s.evalPipeline(dot, n.pipe)
|
val := s.evalPipeline(dot, n.Pipe)
|
||||||
if len(n.pipe.decl) == 0 {
|
if len(n.Pipe.Decl) == 0 {
|
||||||
s.printValue(n, val)
|
s.printValue(n, val)
|
||||||
}
|
}
|
||||||
case *ifNode:
|
case *parse.IfNode:
|
||||||
s.line = n.line
|
s.line = n.Line
|
||||||
s.walkIfOrWith(nodeIf, dot, n.pipe, n.list, n.elseList)
|
s.walkIfOrWith(parse.NodeIf, dot, n.Pipe, n.List, n.ElseList)
|
||||||
case *listNode:
|
case *parse.ListNode:
|
||||||
for _, node := range n.nodes {
|
for _, node := range n.Nodes {
|
||||||
s.walk(dot, node)
|
s.walk(dot, node)
|
||||||
}
|
}
|
||||||
case *rangeNode:
|
case *parse.RangeNode:
|
||||||
s.line = n.line
|
s.line = n.Line
|
||||||
s.walkRange(dot, n)
|
s.walkRange(dot, n)
|
||||||
case *templateNode:
|
case *parse.TemplateNode:
|
||||||
s.line = n.line
|
s.line = n.Line
|
||||||
s.walkTemplate(dot, n)
|
s.walkTemplate(dot, n)
|
||||||
case *textNode:
|
case *parse.TextNode:
|
||||||
if _, err := s.wr.Write(n.text); err != nil {
|
if _, err := s.wr.Write(n.Text); err != nil {
|
||||||
s.error(err)
|
s.error(err)
|
||||||
}
|
}
|
||||||
case *withNode:
|
case *parse.WithNode:
|
||||||
s.line = n.line
|
s.line = n.Line
|
||||||
s.walkIfOrWith(nodeWith, dot, n.pipe, n.list, n.elseList)
|
s.walkIfOrWith(parse.NodeWith, dot, n.Pipe, n.List, n.ElseList)
|
||||||
default:
|
default:
|
||||||
s.errorf("unknown node: %s", n)
|
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
|
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||||
// are identical in behavior except that 'with' sets dot.
|
// 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())
|
defer s.pop(s.mark())
|
||||||
val := s.evalPipeline(dot, pipe)
|
val := s.evalPipeline(dot, pipe)
|
||||||
truth, ok := isTrue(val)
|
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())
|
s.errorf("if/with can't use value of type %T", val.Interface())
|
||||||
}
|
}
|
||||||
if truth {
|
if truth {
|
||||||
if typ == nodeWith {
|
if typ == parse.NodeWith {
|
||||||
s.walk(val, list)
|
s.walk(val, list)
|
||||||
} else {
|
} else {
|
||||||
s.walk(dot, list)
|
s.walk(dot, list)
|
||||||
@ -171,9 +185,9 @@ func isTrue(val reflect.Value) (truth, ok bool) {
|
|||||||
return truth, true
|
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())
|
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 top of stack before any variables in the body are pushed.
|
||||||
mark := s.mark()
|
mark := s.mark()
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
@ -184,14 +198,14 @@ func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
|
|||||||
for i := 0; i < val.Len(); i++ {
|
for i := 0; i < val.Len(); i++ {
|
||||||
elem := val.Index(i)
|
elem := val.Index(i)
|
||||||
// Set top var (lexically the second if there are two) to the element.
|
// 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)
|
s.setVar(1, elem)
|
||||||
}
|
}
|
||||||
// Set next var (lexically the first if there are two) to the index.
|
// 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.setVar(2, reflect.ValueOf(i))
|
||||||
}
|
}
|
||||||
s.walk(elem, r.list)
|
s.walk(elem, r.List)
|
||||||
s.pop(mark)
|
s.pop(mark)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -202,41 +216,41 @@ func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
|
|||||||
for _, key := range val.MapKeys() {
|
for _, key := range val.MapKeys() {
|
||||||
elem := val.MapIndex(key)
|
elem := val.MapIndex(key)
|
||||||
// Set top var (lexically the second if there are two) to the element.
|
// 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)
|
s.setVar(1, elem)
|
||||||
}
|
}
|
||||||
// Set next var (lexically the first if there are two) to the key.
|
// 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.setVar(2, key)
|
||||||
}
|
}
|
||||||
s.walk(elem, r.list)
|
s.walk(elem, r.List)
|
||||||
s.pop(mark)
|
s.pop(mark)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
s.errorf("range can't iterate over value of type %T", val.Interface())
|
s.errorf("range can't iterate over value of type %T", val.Interface())
|
||||||
}
|
}
|
||||||
if r.elseList != nil {
|
if r.ElseList != nil {
|
||||||
s.walk(dot, r.elseList)
|
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
|
set := s.tmpl.set
|
||||||
if set == nil {
|
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 {
|
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.
|
// Variables declared by the pipeline persist.
|
||||||
dot = s.evalPipeline(dot, t.pipe)
|
dot = s.evalPipeline(dot, t.Pipe)
|
||||||
newState := *s
|
newState := *s
|
||||||
newState.tmpl = tmpl
|
newState.tmpl = tmpl
|
||||||
// No dynamic scoping: template invocations inherit no variables.
|
// No dynamic scoping: template invocations inherit no variables.
|
||||||
newState.vars = []variable{{"$", dot}}
|
newState.vars = []variable{{"$", dot}}
|
||||||
newState.walk(dot, tmpl.root)
|
newState.walk(dot, tmpl.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eval functions evaluate pipelines, commands, and their elements and extract
|
// 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
|
// pipeline has a variable declaration, the variable will be pushed on the
|
||||||
// stack. Callers should therefore pop the stack after they are finished
|
// stack. Callers should therefore pop the stack after they are finished
|
||||||
// executing commands depending on the pipeline value.
|
// 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 {
|
if pipe == nil {
|
||||||
return
|
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.
|
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 the object has type interface{}, dig down one level to the thing inside.
|
||||||
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||||
value = reflect.ValueOf(value.Interface()) // lovely!
|
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, variable := range pipe.decl {
|
for _, variable := range pipe.Decl {
|
||||||
s.push(variable.ident[0], value)
|
s.push(variable.Ident[0], value)
|
||||||
}
|
}
|
||||||
return 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() {
|
if len(args) > 1 || final.IsValid() {
|
||||||
s.errorf("can't give argument to non-function %s", args[0])
|
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 {
|
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||||
firstWord := cmd.args[0]
|
firstWord := cmd.Args[0]
|
||||||
switch n := firstWord.(type) {
|
switch n := firstWord.(type) {
|
||||||
case *fieldNode:
|
case *parse.FieldNode:
|
||||||
return s.evalFieldNode(dot, n, cmd.args, final)
|
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||||
case *identifierNode:
|
case *parse.IdentifierNode:
|
||||||
// Must be a function.
|
// Must be a function.
|
||||||
return s.evalFunction(dot, n.ident, cmd.args, final)
|
return s.evalFunction(dot, n.Ident, cmd.Args, final)
|
||||||
case *variableNode:
|
case *parse.VariableNode:
|
||||||
return s.evalVariableNode(dot, n, cmd.args, final)
|
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||||
}
|
}
|
||||||
s.notAFunction(cmd.args, final)
|
s.notAFunction(cmd.Args, final)
|
||||||
switch word := firstWord.(type) {
|
switch word := firstWord.(type) {
|
||||||
case *boolNode:
|
case *parse.BoolNode:
|
||||||
return reflect.ValueOf(word.true)
|
return reflect.ValueOf(word.True)
|
||||||
case *dotNode:
|
case *parse.DotNode:
|
||||||
return dot
|
return dot
|
||||||
case *numberNode:
|
case *parse.NumberNode:
|
||||||
return s.idealConstant(word)
|
return s.idealConstant(word)
|
||||||
case *stringNode:
|
case *parse.StringNode:
|
||||||
return reflect.ValueOf(word.text)
|
return reflect.ValueOf(word.Text)
|
||||||
}
|
}
|
||||||
s.errorf("can't evaluate command %q", firstWord)
|
s.errorf("can't evaluate command %q", firstWord)
|
||||||
panic("not reached")
|
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
|
// 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
|
// 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.
|
// 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
|
// These are ideal constants but we don't know the type
|
||||||
// and we have no context. (If it was a method argument,
|
// and we have no context. (If it was a method argument,
|
||||||
// we'd know what we need.) The syntax guides us to some extent.
|
// we'd know what we need.) The syntax guides us to some extent.
|
||||||
switch {
|
switch {
|
||||||
case constant.isComplex:
|
case constant.IsComplex:
|
||||||
return reflect.ValueOf(constant.complex128) // incontrovertible.
|
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||||
case constant.isFloat && strings.IndexAny(constant.text, ".eE") >= 0:
|
case constant.IsFloat && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||||
return reflect.ValueOf(constant.float64)
|
return reflect.ValueOf(constant.Float64)
|
||||||
case constant.isInt:
|
case constant.IsInt:
|
||||||
n := int(constant.int64)
|
n := int(constant.Int64)
|
||||||
if int64(n) != constant.int64 {
|
if int64(n) != constant.Int64 {
|
||||||
s.errorf("%s overflows int", constant.text)
|
s.errorf("%s overflows int", constant.Text)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(n)
|
return reflect.ValueOf(n)
|
||||||
case constant.isUint:
|
case constant.IsUint:
|
||||||
s.errorf("%s overflows int", constant.text)
|
s.errorf("%s overflows int", constant.Text)
|
||||||
}
|
}
|
||||||
return zero
|
return zero
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalFieldNode(dot reflect.Value, field *fieldNode, args []node, final reflect.Value) reflect.Value {
|
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)
|
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.
|
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||||
value := s.varValue(v.ident[0])
|
value := s.varValue(v.Ident[0])
|
||||||
if len(v.ident) == 1 {
|
if len(v.Ident) == 1 {
|
||||||
return value
|
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.
|
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||||
// dot is the environment in which to evaluate arguments, while
|
// dot is the environment in which to evaluate arguments, while
|
||||||
// receiver is the value being walked along the chain.
|
// 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)
|
n := len(ident)
|
||||||
for i := 0; i < n-1; i++ {
|
for i := 0; i < n-1; i++ {
|
||||||
receiver = s.evalField(dot, ident[i], nil, zero, receiver)
|
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)
|
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)
|
function, ok := findFunction(name, s.tmpl, s.tmpl.set)
|
||||||
if !ok {
|
if !ok {
|
||||||
s.errorf("%q is not a defined function", name)
|
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).
|
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||||
// The 'final' argument represents the return value from the preceding
|
// The 'final' argument represents the return value from the preceding
|
||||||
// value of the pipeline, if any.
|
// 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() {
|
if !receiver.IsValid() {
|
||||||
return zero
|
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
|
// 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]
|
// 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.
|
// 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 {
|
if args != nil {
|
||||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
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
|
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) {
|
switch arg := n.(type) {
|
||||||
case *dotNode:
|
case *parse.DotNode:
|
||||||
return s.validateType(dot, typ)
|
return s.validateType(dot, typ)
|
||||||
case *fieldNode:
|
case *parse.FieldNode:
|
||||||
return s.validateType(s.evalFieldNode(dot, arg, []node{n}, zero), typ)
|
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||||
case *variableNode:
|
case *parse.VariableNode:
|
||||||
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||||
}
|
}
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
@ -500,81 +514,81 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n node) reflect.Val
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalBool(typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
if n, ok := n.(*boolNode); ok {
|
if n, ok := n.(*parse.BoolNode); ok {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetBool(n.true)
|
value.SetBool(n.True)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.errorf("expected bool; found %s", n)
|
s.errorf("expected bool; found %s", n)
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalString(typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
if n, ok := n.(*stringNode); ok {
|
if n, ok := n.(*parse.StringNode); ok {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetString(n.text)
|
value.SetString(n.Text)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.errorf("expected string; found %s", n)
|
s.errorf("expected string; found %s", n)
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalInteger(typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isInt {
|
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetInt(n.int64)
|
value.SetInt(n.Int64)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.errorf("expected integer; found %s", n)
|
s.errorf("expected integer; found %s", n)
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalUnsignedInteger(typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isUint {
|
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetUint(n.uint64)
|
value.SetUint(n.Uint64)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.errorf("expected unsigned integer; found %s", n)
|
s.errorf("expected unsigned integer; found %s", n)
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalFloat(typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isFloat {
|
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetFloat(n.float64)
|
value.SetFloat(n.Float64)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.errorf("expected float; found %s", n)
|
s.errorf("expected float; found %s", n)
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isComplex {
|
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetComplex(n.complex128)
|
value.SetComplex(n.Complex128)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
s.errorf("expected complex; found %s", n)
|
s.errorf("expected complex; found %s", n)
|
||||||
panic("not reached")
|
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) {
|
switch n := n.(type) {
|
||||||
case *boolNode:
|
case *parse.BoolNode:
|
||||||
return reflect.ValueOf(n.true)
|
return reflect.ValueOf(n.True)
|
||||||
case *dotNode:
|
case *parse.DotNode:
|
||||||
return dot
|
return dot
|
||||||
case *fieldNode:
|
case *parse.FieldNode:
|
||||||
return s.evalFieldNode(dot, n, nil, zero)
|
return s.evalFieldNode(dot, n, nil, zero)
|
||||||
case *identifierNode:
|
case *parse.IdentifierNode:
|
||||||
return s.evalFunction(dot, n.ident, nil, zero)
|
return s.evalFunction(dot, n.Ident, nil, zero)
|
||||||
case *numberNode:
|
case *parse.NumberNode:
|
||||||
return s.idealConstant(n)
|
return s.idealConstant(n)
|
||||||
case *stringNode:
|
case *parse.StringNode:
|
||||||
return reflect.ValueOf(n.text)
|
return reflect.ValueOf(n.Text)
|
||||||
case *variableNode:
|
case *parse.VariableNode:
|
||||||
return s.evalVariableNode(dot, n, nil, zero)
|
return s.evalVariableNode(dot, n, nil, zero)
|
||||||
}
|
}
|
||||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
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
|
// printValue writes the textual representation of the value to the output of
|
||||||
// the template.
|
// the template.
|
||||||
func (s *state) printValue(n node, v reflect.Value) {
|
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
fmt.Fprint(s.wr, "<no value>")
|
fmt.Fprint(s.wr, "<no value>")
|
||||||
return
|
return
|
||||||
|
@ -6,6 +6,7 @@ package template
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -14,6 +15,8 @@ import (
|
|||||||
"testing"
|
"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.
|
// T has lots of interesting pieces to use to test execution.
|
||||||
type T struct {
|
type T struct {
|
||||||
// Basics
|
// Basics
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
// during execution, execution terminates and Execute returns an error.
|
// during execution, execution terminates and Execute returns an error.
|
||||||
type FuncMap map[string]interface{}
|
type FuncMap map[string]interface{}
|
||||||
|
|
||||||
var funcs = map[string]reflect.Value{
|
var builtins = map[string]reflect.Value{
|
||||||
"and": reflect.ValueOf(and),
|
"and": reflect.ValueOf(and),
|
||||||
"html": reflect.ValueOf(HTMLEscaper),
|
"html": reflect.ValueOf(HTMLEscaper),
|
||||||
"index": reflect.ValueOf(index),
|
"index": reflect.ValueOf(index),
|
||||||
@ -73,7 +73,7 @@ func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) {
|
|||||||
return fn, true
|
return fn, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fn := funcs[name]; fn.IsValid() {
|
if fn := builtins[name]; fn.IsValid() {
|
||||||
return fn, true
|
return fn, true
|
||||||
}
|
}
|
||||||
return reflect.Value{}, false
|
return reflect.Value{}, false
|
||||||
|
@ -5,512 +5,22 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"exp/template/parse"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Template is the representation of a parsed template.
|
// Template is the representation of a parsed template.
|
||||||
type Template struct {
|
type Template struct {
|
||||||
name string
|
name string
|
||||||
root *listNode
|
*parse.Tree
|
||||||
funcs map[string]reflect.Value
|
funcs map[string]reflect.Value
|
||||||
set *Set // can be nil.
|
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.
|
// Name returns the name of the template.
|
||||||
func (t *Template) Name() string {
|
func (t *Template) Name() string {
|
||||||
return t.name
|
return t.Tree.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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsing.
|
// Parsing.
|
||||||
@ -532,89 +42,13 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
|
|||||||
return t
|
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
|
// Parse parses the template definition string to construct an internal
|
||||||
// representation of the template for execution.
|
// representation of the template for execution.
|
||||||
func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
|
func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
|
||||||
defer t.recover(&err)
|
t.Tree, err = parse.New(t.name).Parse(s, t.funcs, builtins)
|
||||||
t.startParse(t.set, lex(t.name, s))
|
if err != nil {
|
||||||
t.parse(true)
|
return nil, err
|
||||||
t.stopParse()
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,10 +57,14 @@ func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
|
|||||||
// to the set.
|
// to the set.
|
||||||
// Function bindings are checked against those in the set.
|
// Function bindings are checked against those in the set.
|
||||||
func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err os.Error) {
|
func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err os.Error) {
|
||||||
defer t.recover(&err)
|
var setFuncs map[string]reflect.Value
|
||||||
t.startParse(set, lex(t.name, s))
|
if set != nil {
|
||||||
t.parse(true)
|
setFuncs = set.funcs
|
||||||
t.stopParse()
|
}
|
||||||
|
t.Tree, err = parse.New(t.name).Parse(s, t.funcs, setFuncs, builtins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
t.addToSet(set)
|
t.addToSet(set)
|
||||||
return t, nil
|
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.
|
// If double-assigned, Add will panic and we will turn that into an error.
|
||||||
set.Add(t)
|
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
|
|
||||||
}
|
|
||||||
|
14
src/pkg/exp/template/parse/Makefile
Normal file
14
src/pkg/exp/template/parse/Makefile
Normal 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
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package template
|
package parse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package template
|
package parse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
468
src/pkg/exp/template/parse/node.go
Normal file
468
src/pkg/exp/template/parse/node.go
Normal 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)
|
||||||
|
}
|
437
src/pkg/exp/template/parse/parse.go
Normal file
437
src/pkg/exp/template/parse/parse.go
Normal 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
|
||||||
|
}
|
@ -2,11 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package template
|
package parse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -100,47 +101,47 @@ func TestNumberParse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if n.isComplex != test.isComplex {
|
if n.IsComplex != test.isComplex {
|
||||||
t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
|
t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
|
||||||
}
|
}
|
||||||
if test.isInt {
|
if test.isInt {
|
||||||
if !n.isInt {
|
if !n.IsInt {
|
||||||
t.Errorf("expected integer for %q", test.text)
|
t.Errorf("expected integer for %q", test.text)
|
||||||
}
|
}
|
||||||
if n.int64 != test.int64 {
|
if n.Int64 != test.int64 {
|
||||||
t.Errorf("int64 for %q should be %d is %d", test.text, test.int64, n.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)
|
t.Errorf("did not expect integer for %q", test.text)
|
||||||
}
|
}
|
||||||
if test.isUint {
|
if test.isUint {
|
||||||
if !n.isUint {
|
if !n.IsUint {
|
||||||
t.Errorf("expected unsigned integer for %q", test.text)
|
t.Errorf("expected unsigned integer for %q", test.text)
|
||||||
}
|
}
|
||||||
if n.uint64 != test.uint64 {
|
if n.Uint64 != test.uint64 {
|
||||||
t.Errorf("uint64 for %q should be %d is %d", test.text, test.uint64, n.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)
|
t.Errorf("did not expect unsigned integer for %q", test.text)
|
||||||
}
|
}
|
||||||
if test.isFloat {
|
if test.isFloat {
|
||||||
if !n.isFloat {
|
if !n.IsFloat {
|
||||||
t.Errorf("expected float for %q", test.text)
|
t.Errorf("expected float for %q", test.text)
|
||||||
}
|
}
|
||||||
if n.float64 != test.float64 {
|
if n.Float64 != test.float64 {
|
||||||
t.Errorf("float64 for %q should be %g is %g", test.text, test.float64, n.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)
|
t.Errorf("did not expect float for %q", test.text)
|
||||||
}
|
}
|
||||||
if test.isComplex {
|
if test.isComplex {
|
||||||
if !n.isComplex {
|
if !n.IsComplex {
|
||||||
t.Errorf("expected complex for %q", test.text)
|
t.Errorf("expected complex for %q", test.text)
|
||||||
}
|
}
|
||||||
if n.complex128 != test.complex128 {
|
if n.Complex128 != test.complex128 {
|
||||||
t.Errorf("complex128 for %q should be %g is %g", test.text, test.complex128, n.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)
|
t.Errorf("did not expect complex for %q", test.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +162,8 @@ const (
|
|||||||
var parseTests = []parseTest{
|
var parseTests = []parseTest{
|
||||||
{"empty", "", noError,
|
{"empty", "", noError,
|
||||||
`[]`},
|
`[]`},
|
||||||
|
{"comment", "{{/*\n\n\n*/}}", noError,
|
||||||
|
`[]`},
|
||||||
{"spaces", " \t\n", noError,
|
{"spaces", " \t\n", noError,
|
||||||
`[(text: " \t\n")]`},
|
`[(text: " \t\n")]`},
|
||||||
{"text", "some text", noError,
|
{"text", "some text", noError,
|
||||||
@ -228,9 +231,13 @@ var parseTests = []parseTest{
|
|||||||
{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
|
{"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) {
|
func TestParse(t *testing.T) {
|
||||||
for _, test := range parseTests {
|
for _, test := range parseTests {
|
||||||
tmpl, err := New(test.name).Parse(test.input)
|
tmpl, err := New(test.name).Parse(test.input, builtins)
|
||||||
switch {
|
switch {
|
||||||
case err == nil && !test.ok:
|
case err == nil && !test.ok:
|
||||||
t.Errorf("%q: expected error; got none", test.name)
|
t.Errorf("%q: expected error; got none", test.name)
|
||||||
@ -245,7 +252,7 @@ func TestParse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result := tmpl.root.String()
|
result := tmpl.Root.String()
|
||||||
if result != test.result {
|
if result != test.result {
|
||||||
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
|
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
|
||||||
}
|
}
|
51
src/pkg/exp/template/parse/set.go
Normal file
51
src/pkg/exp/template/parse/set.go
Normal 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
|
||||||
|
}
|
@ -5,12 +5,11 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"exp/template/parse"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set holds a set of related templates that can refer to one another by name.
|
// 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]
|
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
|
// Execute applies the named template to the specified data object, writing
|
||||||
// the output to wr.
|
// the output to wr.
|
||||||
func (s *Set) Execute(wr io.Writer, name string, data interface{}) os.Error {
|
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)
|
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
|
// 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
|
// 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
|
// to the set. If a template is redefined, the element in the set is
|
||||||
// overwritten with the new definition.
|
// overwritten with the new definition.
|
||||||
func (s *Set) Parse(text string) (set *Set, err os.Error) {
|
func (s *Set) Parse(text string) (*Set, os.Error) {
|
||||||
set = s
|
trees, err := parse.Set(text, s.funcs, builtins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
s.init()
|
s.init()
|
||||||
defer s.recover(&err)
|
for name, tree := range trees {
|
||||||
lex := lex("set", text)
|
tmpl := New(name)
|
||||||
const context = "define clause"
|
tmpl.Tree = tree
|
||||||
for {
|
tmpl.addToSet(s)
|
||||||
t := New("set") // name will be updated once we know it.
|
s.tmpl[name] = tmpl
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
noError = true
|
||||||
|
hasError = false
|
||||||
|
)
|
||||||
|
|
||||||
type setParseTest struct {
|
type setParseTest struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
@ -66,7 +71,7 @@ func TestSetParse(t *testing.T) {
|
|||||||
t.Errorf("%s: can't find template %q", test.name, name)
|
t.Errorf("%s: can't find template %q", test.name, name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result := tmpl.root.String()
|
result := tmpl.Root.String()
|
||||||
if result != test.results[i] {
|
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])
|
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user