case *dotNode:
return dot
case *numberNode:
- // These are ideal constants but we don't know the type
- // and we have no context. (If it was a method argument,
- // we'd know what we need.) The syntax guides us to some extent.
- switch {
- case word.isComplex:
- return reflect.ValueOf(word.complex128) // incontrovertible.
- case word.isFloat && strings.IndexAny(word.text, ".eE") >= 0:
- return reflect.ValueOf(word.float64)
- case word.isInt:
- return reflect.ValueOf(word.int64)
- case word.isUint:
- return reflect.ValueOf(word.uint64)
- }
+ return s.idealConstant(word)
case *stringNode:
return reflect.ValueOf(word.text)
}
panic("not reached")
}
+// idealConstant is called to return the value of a number in a context where
+// we don't know the type. In that case, the syntax of the number tells us
+// its type, and we use Go rules to resolve. Note there is no such thing as
+// a uint ideal constant in this situation - the value must be of int type.
+func (s *state) idealConstant(constant *numberNode) reflect.Value {
+ // These are ideal constants but we don't know the type
+ // and we have no context. (If it was a method argument,
+ // we'd know what we need.) The syntax guides us to some extent.
+ switch {
+ case constant.isComplex:
+ return reflect.ValueOf(constant.complex128) // incontrovertible.
+ case constant.isFloat && strings.IndexAny(constant.text, ".eE") >= 0:
+ return reflect.ValueOf(constant.float64)
+ case constant.isInt:
+ n := int(constant.int64)
+ if int64(n) != constant.int64 {
+ s.errorf("%s overflows int", constant.text)
+ }
+ return reflect.ValueOf(n)
+ case constant.isUint:
+ s.errorf("%s overflows int", constant.text)
+ }
+ return zero
+}
+
func (s *state) evalFieldNode(dot reflect.Value, field *fieldNode, args []node, final reflect.Value) reflect.Value {
return s.evalFieldChain(dot, dot, field.ident, args, final)
}
case *identifierNode:
return s.evalFunction(dot, n.ident, nil, zero)
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)
- }
+ return s.idealConstant(n)
case *stringNode:
return reflect.ValueOf(n.text)
case *variableNode:
"bytes"
"fmt"
"os"
+ "reflect"
"sort"
"strings"
"testing"
return false, nil
}
+func typeOf(arg interface{}) string {
+ return fmt.Sprintf("%T", arg)
+}
+
type execTest struct {
name string
input string
ok bool
}
+// bigInt and bigUint are hex string representing numbers either side
+// of the max int boundary.
+// We do it this way so the test doesn't depend on ints being 32 bits.
+var (
+ bigInt = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeOf(0).Bits()-1)-1))
+ bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeOf(0).Bits()-1)))
+)
+
var execTests = []execTest{
// Trivial cases.
{"empty", "", "", nil, true},
{"text", "some text", "some text", nil, true},
+ // Ideal constants.
+ {"ideal int", "{{typeOf 3}}", "int", 0, true},
+ {"ideal float", "{{typeOf 1.0}}", "float64", 0, true},
+ {"ideal exp float", "{{typeOf 1e1}}", "float64", 0, true},
+ {"ideal complex", "{{typeOf 1i}}", "complex128", 0, true},
+ {"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true},
+ {"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false},
+
// Fields of structs.
{".X", "-{{.X}}-", "-x-", tVal, true},
{".U.V", "-{{.U.V}}-", "-v-", tVal, true},
func testExecute(execTests []execTest, set *Set, t *testing.T) {
b := new(bytes.Buffer)
- funcs := FuncMap{"zeroArgs": zeroArgs, "oneArg": oneArg}
+ funcs := FuncMap{"zeroArgs": zeroArgs, "oneArg": oneArg, "typeOf": typeOf}
for _, test := range execTests {
tmpl := New(test.name).Funcs(funcs)
err := tmpl.Parse(test.input)