aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2011-07-05 14:23:51 +1000
committerRob Pike <r@golang.org>2011-07-05 14:23:51 +1000
commitb177c97803ede6d9cfe0214863a69d0aad977f22 (patch)
tree1ae490bd477c95946c36916c3a074a5ceed6873d
parent9cf37c3723383e3f489ee4b8087ecc152e79bf39 (diff)
downloadgo-b177c97803ede6d9cfe0214863a69d0aad977f22.tar.gz
go-b177c97803ede6d9cfe0214863a69d0aad977f22.zip
exp/template: functions
Add the ability to attach functions to template and template sets. Make variadic functions and methods work. Still to come: static checking of function names during parse. R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/4643068
-rw-r--r--src/pkg/exp/template/Makefile1
-rw-r--r--src/pkg/exp/template/exec.go118
-rw-r--r--src/pkg/exp/template/exec_test.go17
-rw-r--r--src/pkg/exp/template/funcs.go63
-rw-r--r--src/pkg/exp/template/parse.go20
-rw-r--r--src/pkg/exp/template/set.go16
6 files changed, 196 insertions, 39 deletions
diff --git a/src/pkg/exp/template/Makefile b/src/pkg/exp/template/Makefile
index 50a0bd7234..8550b0d522 100644
--- a/src/pkg/exp/template/Makefile
+++ b/src/pkg/exp/template/Makefile
@@ -7,6 +7,7 @@ include ../../../Make.inc
TARG=exp/template
GOFILES=\
exec.go\
+ funcs.go\
lex.go\
parse.go\
set.go\
diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go
index 3eaecd1941..636bc4c334 100644
--- a/src/pkg/exp/template/exec.go
+++ b/src/pkg/exp/template/exec.go
@@ -174,8 +174,11 @@ func (s *state) evalPipeline(data reflect.Value, pipe []*commandNode) reflect.Va
func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
firstWord := cmd.args[0]
- if field, ok := firstWord.(*fieldNode); ok {
- return s.evalFieldNode(data, field, cmd.args, final)
+ switch n := firstWord.(type) {
+ case *fieldNode:
+ return s.evalFieldNode(data, n, cmd.args, final)
+ case *identifierNode:
+ return s.evalFieldOrCall(data, n.ident, cmd.args, final)
}
if len(cmd.args) > 1 || final.IsValid() {
// TODO: functions
@@ -215,7 +218,7 @@ func (s *state) evalFieldNode(data reflect.Value, field *fieldNode, args []node,
data = s.evalField(data, field.ident[i])
}
// Now it can be a field or method and if a method, gets arguments.
- return s.evalMethodOrField(data, field.ident[n-1], args, final)
+ return s.evalFieldOrCall(data, field.ident[n-1], args, final)
}
func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
@@ -238,14 +241,18 @@ func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
panic("not reached")
}
-func (s *state) evalMethodOrField(data reflect.Value, fieldName string, args []node, final reflect.Value) reflect.Value {
+func (s *state) evalFieldOrCall(data reflect.Value, fieldName string, args []node, final reflect.Value) reflect.Value {
+ // Is it a function?
+ if function, ok := findFunction(fieldName, s.tmpl, s.set); ok {
+ return s.evalCall(data, function, fieldName, false, args, final)
+ }
ptr := data
for data.Kind() == reflect.Ptr {
ptr, data = data, reflect.Indirect(data)
}
// Is it a method? We use the pointer because it has value methods too.
if method, ok := ptr.Type().MethodByName(fieldName); ok {
- return s.evalMethod(ptr, method, args, final)
+ return s.evalCall(ptr, method.Func, fieldName, true, args, final)
}
if len(args) > 1 || final.IsValid() {
s.errorf("%s is not a method but has arguments", fieldName)
@@ -263,31 +270,46 @@ var (
osErrorType = reflect.TypeOf(new(os.Error)).Elem()
)
-func (s *state) evalMethod(v reflect.Value, method reflect.Method, args []node, final reflect.Value) reflect.Value {
- typ := method.Type
- fun := method.Func
+func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args []node, final reflect.Value) reflect.Value {
+ typ := fun.Type()
+ if !isMethod && len(args) > 0 { // Args will be nil if it's a niladic call in an argument list
+ args = args[1:] // first arg is name of function; not used in call.
+ }
numIn := len(args)
if final.IsValid() {
numIn++
}
- if !typ.IsVariadic() && numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
- s.errorf("wrong number of args for %s: want %d got %d", method.Name, typ.NumIn(), len(args))
+ numFixed := len(args)
+ if typ.IsVariadic() {
+ numFixed = typ.NumIn() - 1 // last arg is the variadic one.
+ if numIn < numFixed {
+ s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
+ }
+ } else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
+ s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
}
- // We allow methods with 1 result or 2 results where the second is an os.Error.
- switch {
- case typ.NumOut() == 1:
- case typ.NumOut() == 2 && typ.Out(1) == osErrorType:
- default:
- s.errorf("can't handle multiple results from method %q", method.Name)
+ if !goodFunc(typ) {
+ s.errorf("can't handle multiple results from method/function %q", name)
}
// Build the arg list.
argv := make([]reflect.Value, numIn)
// First arg is the receiver.
- argv[0] = v
- // Others must be evaluated.
- for i := 1; i < len(args); i++ {
+ i := 0
+ if isMethod {
+ argv[0] = v
+ i++
+ }
+ // Others must be evaluated. Fixed args first.
+ for ; i < numFixed; i++ {
argv[i] = s.evalArg(v, typ.In(i), args[i])
}
+ // And now the ... args.
+ if typ.IsVariadic() {
+ argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
+ for ; i < len(args); i++ {
+ argv[i] = s.evalArg(v, argType, args[i])
+ }
+ }
// Add final value if necessary.
if final.IsValid() {
argv[len(args)] = final
@@ -310,23 +332,27 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
}
switch typ.Kind() {
case reflect.Bool:
- return s.evalBool(data, typ, n)
+ return s.evalBool(typ, n)
case reflect.String:
- return s.evalString(data, typ, n)
+ return s.evalString(typ, n)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return s.evalInteger(data, typ, n)
+ return s.evalInteger(typ, n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return s.evalUnsignedInteger(data, typ, n)
+ return s.evalUnsignedInteger(typ, n)
case reflect.Float32, reflect.Float64:
- return s.evalFloat(data, typ, n)
+ return s.evalFloat(typ, n)
case reflect.Complex64, reflect.Complex128:
- return s.evalComplex(data, typ, n)
+ return s.evalComplex(typ, n)
+ case reflect.Interface:
+ if typ.NumMethod() == 0 {
+ return s.evalEmptyInterface(data, typ, n)
+ }
}
- s.errorf("can't handle node %s for method arg of type %s", n, typ)
+ s.errorf("can't handle %s for arg of type %s", n, typ)
panic("not reached")
}
-func (s *state) evalBool(v reflect.Value, typ reflect.Type, n node) reflect.Value {
+func (s *state) evalBool(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*boolNode); ok {
value := reflect.New(typ).Elem()
value.SetBool(n.true)
@@ -336,7 +362,7 @@ func (s *state) evalBool(v reflect.Value, typ reflect.Type, n node) reflect.Valu
panic("not reached")
}
-func (s *state) evalString(v reflect.Value, typ reflect.Type, n node) reflect.Value {
+func (s *state) evalString(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*stringNode); ok {
value := reflect.New(typ).Elem()
value.SetString(n.text)
@@ -346,7 +372,7 @@ func (s *state) evalString(v reflect.Value, typ reflect.Type, n node) reflect.Va
panic("not reached")
}
-func (s *state) evalInteger(v reflect.Value, typ reflect.Type, n node) reflect.Value {
+func (s *state) evalInteger(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isInt {
value := reflect.New(typ).Elem()
value.SetInt(n.int64)
@@ -356,7 +382,7 @@ func (s *state) evalInteger(v reflect.Value, typ reflect.Type, n node) reflect.V
panic("not reached")
}
-func (s *state) evalUnsignedInteger(v reflect.Value, typ reflect.Type, n node) reflect.Value {
+func (s *state) evalUnsignedInteger(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isUint {
value := reflect.New(typ).Elem()
value.SetUint(n.uint64)
@@ -366,7 +392,7 @@ func (s *state) evalUnsignedInteger(v reflect.Value, typ reflect.Type, n node) r
panic("not reached")
}
-func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Value {
+func (s *state) evalFloat(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isFloat {
value := reflect.New(typ).Elem()
value.SetFloat(n.float64)
@@ -376,7 +402,7 @@ func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Val
panic("not reached")
}
-func (s *state) evalComplex(v reflect.Value, typ reflect.Type, n node) reflect.Value {
+func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isComplex {
value := reflect.New(typ).Elem()
value.SetComplex(n.complex128)
@@ -386,6 +412,34 @@ func (s *state) evalComplex(v reflect.Value, typ reflect.Type, n node) reflect.V
panic("not reached")
}
+func (s *state) evalEmptyInterface(data reflect.Value, typ reflect.Type, n node) reflect.Value {
+ switch n := n.(type) {
+ case *boolNode:
+ return reflect.ValueOf(n.true)
+ case *fieldNode:
+ return s.evalFieldNode(data, n, nil, reflect.Value{})
+ case *identifierNode:
+ return s.evalFieldOrCall(data, n.ident, nil, reflect.Value{})
+ case *numberNode:
+ if n.isComplex {
+ return reflect.ValueOf(n.complex128)
+ }
+ if n.isInt {
+ return reflect.ValueOf(n.int64)
+ }
+ if n.isUint {
+ return reflect.ValueOf(n.uint64)
+ }
+ if n.isFloat {
+ return reflect.ValueOf(n.float64)
+ }
+ case *stringNode:
+ return reflect.ValueOf(n.text)
+ }
+ s.errorf("can't handle assignment of %s to empty interface argument", n)
+ panic("not reached")
+}
+
// printValue writes the textual representation of the value to the output of
// the template.
func (s *state) printValue(n node, v reflect.Value) {
diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go
index 6e4da692e9..8784a0b9fd 100644
--- a/src/pkg/exp/template/exec_test.go
+++ b/src/pkg/exp/template/exec_test.go
@@ -140,6 +140,16 @@ var execTests = []execTest{
{"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
{"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
+ // Function calls.
+ {"printf", `{{printf "hello, printf"}}`, "hello, printf", tVal, true},
+ {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
+ {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
+ {"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true},
+ {"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true},
+ {"printf function", `{{printf "%#q" gopher}}`, "`gopher`", tVal, true},
+ {"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true},
+ {"printf method", `{{printf "%s" .Method0}}`, "resultOfMethod0", tVal, true},
+ {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) resultOfMethod0", tVal, true},
// With.
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
{"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
@@ -171,10 +181,15 @@ var execTests = []execTest{
{"error method, no error", "{{.EPERM false}}", "false", tVal, true},
}
+func gopher() string {
+ return "gopher"
+}
+
func testExecute(execTests []execTest, set *Set, t *testing.T) {
b := new(bytes.Buffer)
+ funcs := FuncMap{"gopher": gopher}
for _, test := range execTests {
- tmpl := New(test.name)
+ tmpl := New(test.name).Funcs(funcs)
err := tmpl.Parse(test.input)
if err != nil {
t.Errorf("%s: parse error: %s", test.name, err)
diff --git a/src/pkg/exp/template/funcs.go b/src/pkg/exp/template/funcs.go
new file mode 100644
index 0000000000..88f82f3b2c
--- /dev/null
+++ b/src/pkg/exp/template/funcs.go
@@ -0,0 +1,63 @@
+// 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 template
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// FuncMap is the type of the map defining the mapping from names to functions.
+// Each function must have either a single return value, or two return values of
+// which the second has type os.Error.
+type FuncMap map[string]interface{}
+
+var funcs = map[string]reflect.Value{
+ "printf": reflect.ValueOf(fmt.Sprintf),
+}
+
+// addFuncs adds to values the functions in funcs, converting them to reflect.Values.
+func addFuncs(values map[string]reflect.Value, funcMap FuncMap) {
+ for name, fn := range funcMap {
+ v := reflect.ValueOf(fn)
+ if v.Kind() != reflect.Func {
+ panic("value for " + name + " not a function")
+ }
+ if !goodFunc(v.Type()) {
+ panic(fmt.Errorf("can't handle multiple results from method/function %q", name))
+ }
+ values[name] = v
+ }
+}
+
+// goodFunc checks that the function or method has the right result signature.
+func goodFunc(typ reflect.Type) bool {
+ // We allow functions with 1 result or 2 results where the second is an os.Error.
+ switch {
+ case typ.NumOut() == 1:
+ return true
+ case typ.NumOut() == 2 && typ.Out(1) == osErrorType:
+ return true
+ }
+ return false
+}
+
+// findFunction looks for a function in the template, set, and global map.
+func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) {
+ if tmpl != nil {
+ if fn := tmpl.funcs[name]; fn.IsValid() {
+ return fn, true
+ }
+ }
+ if set != nil {
+ if fn := set.funcs[name]; fn.IsValid() {
+ return fn, true
+ }
+ }
+ if fn := funcs[name]; fn.IsValid() {
+ return fn, true
+ }
+ return reflect.Value{}, false
+}
diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go
index 74b5f2c0ae..aaed411d49 100644
--- a/src/pkg/exp/template/parse.go
+++ b/src/pkg/exp/template/parse.go
@@ -8,6 +8,7 @@ import (
"bytes"
"fmt"
"os"
+ "reflect"
"runtime"
"strconv"
"strings"
@@ -16,8 +17,9 @@ import (
// Template is the representation of a parsed template.
type Template struct {
- name string
- root *listNode
+ name string
+ root *listNode
+ funcs map[string]reflect.Value
// Parsing.
lex *lexer
tokens <-chan item
@@ -450,12 +452,23 @@ func (w *withNode) String() string {
// New allocates a new template with the given name.
func New(name string) *Template {
return &Template{
- name: name,
+ name: name,
+ funcs: make(map[string]reflect.Value),
}
}
+// Funcs adds to the template's function map the elements of the
+// argument map. It panics if a value in the map is not a function
+// with appropriate return type.
+// The return value is the template, so calls can be chained.
+func (t *Template) Funcs(funcMap FuncMap) *Template {
+ addFuncs(t.funcs, funcMap)
+ 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...))
}
@@ -488,7 +501,6 @@ func (t *Template) recover(errp *os.Error) {
panic(e)
}
t.stopParse()
- t.root = nil
*errp = e.(os.Error)
}
return
diff --git a/src/pkg/exp/template/set.go b/src/pkg/exp/template/set.go
index 13d93d03ca..3aaabaad5a 100644
--- a/src/pkg/exp/template/set.go
+++ b/src/pkg/exp/template/set.go
@@ -6,6 +6,7 @@ package template
import (
"os"
+ "reflect"
"runtime"
"strconv"
)
@@ -13,16 +14,27 @@ import (
// Set holds a set of related templates that can refer to one another by name.
// A template may be a member of multiple sets.
type Set struct {
- tmpl map[string]*Template
+ tmpl map[string]*Template
+ funcs map[string]reflect.Value
}
// NewSet allocates a new, empty template set.
func NewSet() *Set {
return &Set{
- tmpl: make(map[string]*Template),
+ tmpl: make(map[string]*Template),
+ funcs: make(map[string]reflect.Value),
}
}
+// Funcs adds to the set's function map the elements of the
+// argument map. It panics if a value in the map is not a function
+// with appropriate return type.
+// The return value is the set, so calls can be chained.
+func (s *Set) Funcs(funcMap FuncMap) *Set {
+ addFuncs(s.funcs, funcMap)
+ return s
+}
+
// recover is the handler that turns panics into returns from the top
// level of Parse.
func (s *Set) recover(errp *os.Error) {