mirror of
https://github.com/golang/go
synced 2024-11-19 13:54:56 -07:00
exp/template: fields and methods on variables.
Not strictly necessary (you could achieve the same, clumsily, via with blocks) but great to have: $x.Field, $y.Method. R=golang-dev, adg CC=golang-dev https://golang.org/cl/4678047
This commit is contained in:
parent
96bbcc4256
commit
7b79b3b244
@ -79,7 +79,7 @@ maps, and strings, any value v with len(v)==0 counts as a zero value.
|
||||
|
||||
Arguments
|
||||
|
||||
An argument is a simple value, denoted by one of the following:
|
||||
An argument is a simple value, denoted by one of the following.
|
||||
|
||||
- A boolean, string, character, integer, floating-point, imaginary
|
||||
or complex constant in Go syntax. These behave like Go's untyped
|
||||
@ -100,6 +100,8 @@ An argument is a simple value, denoted by one of the following:
|
||||
The result is the value of the field. Field invocations may be
|
||||
chained:
|
||||
.Field1.Field2
|
||||
Fields can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Field2
|
||||
- The name of a niladic method of the data, preceded by a period,
|
||||
such as
|
||||
.Method
|
||||
@ -111,6 +113,8 @@ An argument is a simple value, denoted by one of the following:
|
||||
Method invocations may be chained, but only the last element of
|
||||
the chain may be a method; other others must be struct fields:
|
||||
.Field1.Field2.Method
|
||||
Methods can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Method
|
||||
- The name of a niladic function, such as
|
||||
fun
|
||||
The result is the value of invoking the function, fun(). The return
|
||||
|
@ -271,7 +271,7 @@ func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) (value reflect.
|
||||
}
|
||||
}
|
||||
if pipe.decl != nil {
|
||||
s.push(pipe.decl.ident, value)
|
||||
s.push(pipe.decl.ident[0], value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
@ -290,6 +290,8 @@ func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.
|
||||
case *identifierNode:
|
||||
// Must be a function.
|
||||
return s.evalFunction(data, n.ident, cmd.args, final)
|
||||
case *variableNode:
|
||||
return s.evalVariableNode(n, cmd.args, final)
|
||||
}
|
||||
s.notAFunction(cmd.args, final)
|
||||
switch word := firstWord.(type) {
|
||||
@ -313,21 +315,32 @@ func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.
|
||||
}
|
||||
case *stringNode:
|
||||
return reflect.ValueOf(word.text)
|
||||
case *variableNode:
|
||||
return s.varValue(word.ident)
|
||||
}
|
||||
s.errorf("can't handle command %q", firstWord)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalFieldNode(data reflect.Value, field *fieldNode, args []node, final reflect.Value) reflect.Value {
|
||||
return s.evalFieldChain(data, field.ident, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalVariableNode(v *variableNode, args []node, final reflect.Value) reflect.Value {
|
||||
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||
data := s.varValue(v.ident[0])
|
||||
if len(v.ident) == 1 {
|
||||
return data
|
||||
}
|
||||
return s.evalFieldChain(data, v.ident[1:], args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalFieldChain(data reflect.Value, ident []string, args []node, final reflect.Value) reflect.Value {
|
||||
// Up to the last entry, it must be a field.
|
||||
n := len(field.ident)
|
||||
n := len(ident)
|
||||
for i := 0; i < n-1; i++ {
|
||||
data = s.evalField(data, field.ident[i], nil, zero, false)
|
||||
data = s.evalField(data, ident[i], nil, zero, false)
|
||||
}
|
||||
// Now it can be a field or method and if a method, gets arguments.
|
||||
return s.evalField(data, field.ident[n-1], args, final, true)
|
||||
return s.evalField(data, ident[n-1], args, final, true)
|
||||
}
|
||||
|
||||
func (s *state) evalFunction(data reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
|
||||
@ -468,7 +481,7 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
|
||||
case *fieldNode:
|
||||
return s.validateType(s.evalFieldNode(data, arg, []node{n}, zero), typ)
|
||||
case *variableNode:
|
||||
return s.validateType(s.varValue(arg.ident), typ)
|
||||
return s.validateType(s.evalVariableNode(arg, nil, zero), typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
@ -578,7 +591,7 @@ func (s *state) evalEmptyInterface(data reflect.Value, n node) reflect.Value {
|
||||
case *stringNode:
|
||||
return reflect.ValueOf(n.text)
|
||||
case *variableNode:
|
||||
return s.varValue(n.ident)
|
||||
return s.evalVariableNode(n, nil, zero)
|
||||
}
|
||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||
panic("not reached")
|
||||
|
@ -165,6 +165,9 @@ var execTests = []execTest{
|
||||
{"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
||||
{"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true},
|
||||
{"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true},
|
||||
{"$.I", "{{$.I}}", "17", tVal, true},
|
||||
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
||||
{"with $x struct.U.V", "{{with $x := $}}{{$.U.V}}{{end}}", "v", tVal, true},
|
||||
|
||||
// Pointers.
|
||||
{"*int", "{{.PI}}", "23", tVal, true},
|
||||
@ -186,6 +189,7 @@ var execTests = []execTest{
|
||||
{".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true},
|
||||
{".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
|
||||
{".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
||||
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
||||
|
||||
// Pipelines.
|
||||
{"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
|
||||
|
@ -329,7 +329,7 @@ Loop:
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r):
|
||||
// absorb.
|
||||
case r == '.' && l.input[l.start] == '.':
|
||||
case r == '.' && (l.input[l.start] == '.' || l.input[l.start] == '$'):
|
||||
// field chaining; absorb into one token.
|
||||
default:
|
||||
l.backup()
|
||||
|
@ -97,7 +97,7 @@ var lexTests = []lexTest{
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"variables", "{{$c := printf $ $hello $23 $.Method}}", []item{
|
||||
{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
|
||||
tLeft,
|
||||
{itemVariable, "$c"},
|
||||
{itemColonEquals, ":="},
|
||||
@ -106,6 +106,7 @@ var lexTests = []lexTest{
|
||||
{itemVariable, "$hello"},
|
||||
{itemVariable, "$23"},
|
||||
{itemVariable, "$"},
|
||||
{itemVariable, "$var.Field"},
|
||||
{itemField, ".Method"},
|
||||
tRight,
|
||||
tEOF,
|
||||
|
@ -214,11 +214,11 @@ func (i *identifierNode) String() string {
|
||||
// variableNode holds a variable.
|
||||
type variableNode struct {
|
||||
nodeType
|
||||
ident string
|
||||
ident []string
|
||||
}
|
||||
|
||||
func newVariable(ident string) *variableNode {
|
||||
return &variableNode{nodeType: nodeVariable, ident: ident}
|
||||
return &variableNode{nodeType: nodeVariable, ident: strings.Split(ident, ".")}
|
||||
}
|
||||
|
||||
func (v *variableNode) String() string {
|
||||
@ -716,6 +716,9 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
|
||||
if ce := t.peek(); ce.typ == itemColonEquals {
|
||||
t.next()
|
||||
decl = newVariable(v.val)
|
||||
if len(decl.ident) != 1 {
|
||||
t.errorf("illegal variable in declaration: %s", v.val)
|
||||
}
|
||||
t.vars = append(t.vars, v.val)
|
||||
} else {
|
||||
t.backup2(v)
|
||||
@ -854,9 +857,10 @@ Loop:
|
||||
case itemDot:
|
||||
cmd.append(newDot())
|
||||
case itemVariable:
|
||||
v := newVariable(token.val)
|
||||
found := false
|
||||
for _, varName := range t.vars {
|
||||
if varName == token.val {
|
||||
if varName == v.ident[0] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@ -864,7 +868,7 @@ Loop:
|
||||
if !found {
|
||||
t.errorf("undefined variable %q", token.val)
|
||||
}
|
||||
cmd.append(newVariable(token.val))
|
||||
cmd.append(v)
|
||||
case itemField:
|
||||
cmd.append(newField(token.val))
|
||||
case itemBool:
|
||||
|
@ -172,15 +172,17 @@ var parseTests = []parseTest{
|
||||
{"simple command", "{{printf}}", noError,
|
||||
`[(action: [(command: [I=printf])])]`},
|
||||
{"$ invocation", "{{$}}", noError,
|
||||
"[(action: [(command: [V=$])])]"},
|
||||
"[(action: [(command: [V=[$]])])]"},
|
||||
{"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
|
||||
"[({{with $x := [(command: [N=3])]}} [(action: [(command: [V=$x N=23])])])]"},
|
||||
"[({{with [$x] := [(command: [N=3])]}} [(action: [(command: [V=[$x] N=23])])])]"},
|
||||
{"variable with fields", "{{$.I}}", noError,
|
||||
"[(action: [(command: [V=[$ I]])])]"},
|
||||
{"multi-word command", "{{printf `%d` 23}}", noError,
|
||||
"[(action: [(command: [I=printf S=`%d` N=23])])]"},
|
||||
{"pipeline", "{{.X|.Y}}", noError,
|
||||
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
||||
{"pipeline with decl", "{{$x := .X|.Y}}", noError,
|
||||
`[(action: $x := [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
||||
`[(action: [$x] := [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
||||
{"declaration", "{{.X|.Y}}", noError,
|
||||
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
||||
{"simple if", "{{if .X}}hello{{end}}", noError,
|
||||
@ -217,6 +219,7 @@ var parseTests = []parseTest{
|
||||
{"undefined function", "hello{{undefined}}", hasError, ""},
|
||||
{"undefined variable", "{{$x}}", hasError, ""},
|
||||
{"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
|
||||
{"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user