mirror of
https://github.com/golang/go
synced 2024-11-22 09:54:40 -07:00
exp/template: parse variables and declarations
R=golang-dev, adg CC=golang-dev https://golang.org/cl/4631099
This commit is contained in:
parent
ee14989e43
commit
b8c664297b
@ -82,14 +82,14 @@ func (s *state) walk(data reflect.Value, n node) {
|
|||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *actionNode:
|
case *actionNode:
|
||||||
s.line = n.line
|
s.line = n.line
|
||||||
s.printValue(n, s.evalPipeline(data, n.pipeline))
|
s.printValue(n, s.evalPipeline(data, n.pipe))
|
||||||
case *listNode:
|
case *listNode:
|
||||||
for _, node := range n.nodes {
|
for _, node := range n.nodes {
|
||||||
s.walk(data, node)
|
s.walk(data, node)
|
||||||
}
|
}
|
||||||
case *ifNode:
|
case *ifNode:
|
||||||
s.line = n.line
|
s.line = n.line
|
||||||
s.walkIfOrWith(nodeIf, data, n.pipeline, n.list, n.elseList)
|
s.walkIfOrWith(nodeIf, data, n.pipe, n.list, n.elseList)
|
||||||
case *rangeNode:
|
case *rangeNode:
|
||||||
s.line = n.line
|
s.line = n.line
|
||||||
s.walkRange(data, n)
|
s.walkRange(data, n)
|
||||||
@ -102,7 +102,7 @@ func (s *state) walk(data reflect.Value, n node) {
|
|||||||
s.walkTemplate(data, n)
|
s.walkTemplate(data, n)
|
||||||
case *withNode:
|
case *withNode:
|
||||||
s.line = n.line
|
s.line = n.line
|
||||||
s.walkIfOrWith(nodeWith, data, n.pipeline, n.list, n.elseList)
|
s.walkIfOrWith(nodeWith, data, n.pipe, n.list, n.elseList)
|
||||||
default:
|
default:
|
||||||
s.errorf("unknown node: %s", n)
|
s.errorf("unknown node: %s", n)
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ func (s *state) walk(data 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, data reflect.Value, pipe []*commandNode, list, elseList *listNode) {
|
func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe *pipeNode, list, elseList *listNode) {
|
||||||
val := s.evalPipeline(data, pipe)
|
val := s.evalPipeline(data, pipe)
|
||||||
truth, ok := isTrue(val)
|
truth, ok := isTrue(val)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -152,7 +152,7 @@ func isTrue(val reflect.Value) (truth, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) walkRange(data reflect.Value, r *rangeNode) {
|
func (s *state) walkRange(data reflect.Value, r *rangeNode) {
|
||||||
val := s.evalPipeline(data, r.pipeline)
|
val := s.evalPipeline(data, r.pipe)
|
||||||
down := s.down(data)
|
down := s.down(data)
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
@ -188,7 +188,7 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
|
|||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
s.errorf("template %q not in set", name)
|
s.errorf("template %q not in set", name)
|
||||||
}
|
}
|
||||||
data = s.evalPipeline(data, t.pipeline)
|
data = s.evalPipeline(data, t.pipe)
|
||||||
newState := *s
|
newState := *s
|
||||||
newState.tmpl = tmpl
|
newState.tmpl = tmpl
|
||||||
newState.walk(data, tmpl.root)
|
newState.walk(data, tmpl.root)
|
||||||
@ -198,9 +198,9 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
|
|||||||
// values from the data structure by examining fields, calling methods, and so on.
|
// values from the data structure by examining fields, calling methods, and so on.
|
||||||
// The printing of those values happens only through walk functions.
|
// The printing of those values happens only through walk functions.
|
||||||
|
|
||||||
func (s *state) evalPipeline(data reflect.Value, pipe []*commandNode) reflect.Value {
|
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value {
|
||||||
value := zero
|
value := zero
|
||||||
for _, cmd := range pipe {
|
for _, cmd := range pipe.cmds {
|
||||||
value = s.evalCommand(data, cmd, value) // previous value is this one's final arg.
|
value = s.evalCommand(data, 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 {
|
||||||
|
@ -23,33 +23,39 @@ type Template struct {
|
|||||||
// Parsing only; cleared after parse.
|
// Parsing only; cleared after parse.
|
||||||
set *Set
|
set *Set
|
||||||
lex *lexer
|
lex *lexer
|
||||||
token item // token lookahead for parser
|
token [2]item // two-token lookahead for parser
|
||||||
havePeek bool
|
peekCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
// next returns the next token.
|
// next returns the next token.
|
||||||
func (t *Template) next() item {
|
func (t *Template) next() item {
|
||||||
if t.havePeek {
|
if t.peekCount > 0 {
|
||||||
t.havePeek = false
|
t.peekCount--
|
||||||
} else {
|
} else {
|
||||||
t.token = t.lex.nextItem()
|
t.token[0] = t.lex.nextItem()
|
||||||
}
|
}
|
||||||
return t.token
|
return t.token[t.peekCount]
|
||||||
}
|
}
|
||||||
|
|
||||||
// backup backs the input stream up one token.
|
// backup backs the input stream up one token.
|
||||||
func (t *Template) backup() {
|
func (t *Template) backup() {
|
||||||
t.havePeek = true
|
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.
|
// peek returns but does not consume the next token.
|
||||||
func (t *Template) peek() item {
|
func (t *Template) peek() item {
|
||||||
if t.havePeek {
|
if t.peekCount > 0 {
|
||||||
return t.token
|
return t.token[t.peekCount-1]
|
||||||
}
|
}
|
||||||
t.token = t.lex.nextItem()
|
t.peekCount = 1
|
||||||
t.havePeek = true
|
t.token[0] = t.lex.nextItem()
|
||||||
return t.token
|
return t.token[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// A node is an element in the parse tree. The interface is trivial.
|
// A node is an element in the parse tree. The interface is trivial.
|
||||||
@ -76,9 +82,11 @@ const (
|
|||||||
nodeIf
|
nodeIf
|
||||||
nodeList
|
nodeList
|
||||||
nodeNumber
|
nodeNumber
|
||||||
|
nodePipe
|
||||||
nodeRange
|
nodeRange
|
||||||
nodeString
|
nodeString
|
||||||
nodeTemplate
|
nodeTemplate
|
||||||
|
nodeVariable
|
||||||
nodeWith
|
nodeWith
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,23 +130,42 @@ func (t *textNode) String() string {
|
|||||||
return fmt.Sprintf("(text: %q)", t.text)
|
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("%s := %v", p.decl.ident, p.cmds)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", p.cmds)
|
||||||
|
}
|
||||||
|
|
||||||
// actionNode holds an action (something bounded by delimiters).
|
// actionNode holds an action (something bounded by delimiters).
|
||||||
type actionNode struct {
|
type actionNode struct {
|
||||||
nodeType
|
nodeType
|
||||||
line int
|
line int
|
||||||
pipeline []*commandNode
|
pipe *pipeNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAction(line int, pipeline []*commandNode) *actionNode {
|
func newAction(line int, pipe *pipeNode) *actionNode {
|
||||||
return &actionNode{nodeType: nodeAction, line: line, pipeline: pipeline}
|
return &actionNode{nodeType: nodeAction, line: line, pipe: pipe}
|
||||||
}
|
|
||||||
|
|
||||||
func (a *actionNode) append(command *commandNode) {
|
|
||||||
a.pipeline = append(a.pipeline, command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *actionNode) String() string {
|
func (a *actionNode) String() string {
|
||||||
return fmt.Sprintf("(action: %v)", a.pipeline)
|
return fmt.Sprintf("(action: %v)", a.pipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// commandNode holds a command (a pipeline inside an evaluating action).
|
// commandNode holds a command (a pipeline inside an evaluating action).
|
||||||
@ -173,6 +200,20 @@ func (i *identifierNode) String() string {
|
|||||||
return fmt.Sprintf("I=%s", i.ident)
|
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: 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.
|
// dotNode holds the special identifier '.'. It is represented by a nil pointer.
|
||||||
type dotNode bool
|
type dotNode bool
|
||||||
|
|
||||||
@ -370,44 +411,44 @@ func (e *elseNode) String() string {
|
|||||||
return "{{else}}"
|
return "{{else}}"
|
||||||
}
|
}
|
||||||
// ifNode represents an {{if}} action and its commands.
|
// ifNode represents an {{if}} action and its commands.
|
||||||
// TODO: what should evaluation look like? is a pipeline enough?
|
// TODO: what should evaluation look like? is a pipe enough?
|
||||||
type ifNode struct {
|
type ifNode struct {
|
||||||
nodeType
|
nodeType
|
||||||
line int
|
line int
|
||||||
pipeline []*commandNode
|
pipe *pipeNode
|
||||||
list *listNode
|
list *listNode
|
||||||
elseList *listNode
|
elseList *listNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIf(line int, pipeline []*commandNode, list, elseList *listNode) *ifNode {
|
func newIf(line int, pipe *pipeNode, list, elseList *listNode) *ifNode {
|
||||||
return &ifNode{nodeType: nodeIf, line: line, pipeline: pipeline, list: list, elseList: elseList}
|
return &ifNode{nodeType: nodeIf, line: line, pipe: pipe, list: list, elseList: elseList}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ifNode) String() string {
|
func (i *ifNode) String() string {
|
||||||
if i.elseList != nil {
|
if i.elseList != nil {
|
||||||
return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.pipeline, i.list, i.elseList)
|
return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.pipe, i.list, i.elseList)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("({{if %s}} %s)", i.pipeline, i.list)
|
return fmt.Sprintf("({{if %s}} %s)", i.pipe, i.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rangeNode represents a {{range}} action and its commands.
|
// rangeNode represents a {{range}} action and its commands.
|
||||||
type rangeNode struct {
|
type rangeNode struct {
|
||||||
nodeType
|
nodeType
|
||||||
line int
|
line int
|
||||||
pipeline []*commandNode
|
pipe *pipeNode
|
||||||
list *listNode
|
list *listNode
|
||||||
elseList *listNode
|
elseList *listNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRange(line int, pipeline []*commandNode, list, elseList *listNode) *rangeNode {
|
func newRange(line int, pipe *pipeNode, list, elseList *listNode) *rangeNode {
|
||||||
return &rangeNode{nodeType: nodeRange, line: line, pipeline: pipeline, list: list, elseList: elseList}
|
return &rangeNode{nodeType: nodeRange, line: line, pipe: pipe, list: list, elseList: elseList}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rangeNode) String() string {
|
func (r *rangeNode) String() string {
|
||||||
if r.elseList != nil {
|
if r.elseList != nil {
|
||||||
return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipeline, r.list, r.elseList)
|
return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipe, r.list, r.elseList)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("({{range %s}} %s)", r.pipeline, r.list)
|
return fmt.Sprintf("({{range %s}} %s)", r.pipe, r.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// templateNode represents a {{template}} action.
|
// templateNode represents a {{template}} action.
|
||||||
@ -415,35 +456,35 @@ type templateNode struct {
|
|||||||
nodeType
|
nodeType
|
||||||
line int
|
line int
|
||||||
name node
|
name node
|
||||||
pipeline []*commandNode
|
pipe *pipeNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplate(line int, name node, pipeline []*commandNode) *templateNode {
|
func newTemplate(line int, name node, pipe *pipeNode) *templateNode {
|
||||||
return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipeline: pipeline}
|
return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipe: pipe}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templateNode) String() string {
|
func (t *templateNode) String() string {
|
||||||
return fmt.Sprintf("{{template %s %s}}", t.name, t.pipeline)
|
return fmt.Sprintf("{{template %s %s}}", t.name, t.pipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// withNode represents a {{with}} action and its commands.
|
// withNode represents a {{with}} action and its commands.
|
||||||
type withNode struct {
|
type withNode struct {
|
||||||
nodeType
|
nodeType
|
||||||
line int
|
line int
|
||||||
pipeline []*commandNode
|
pipe *pipeNode
|
||||||
list *listNode
|
list *listNode
|
||||||
elseList *listNode
|
elseList *listNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWith(line int, pipeline []*commandNode, list, elseList *listNode) *withNode {
|
func newWith(line int, pipe *pipeNode, list, elseList *listNode) *withNode {
|
||||||
return &withNode{nodeType: nodeWith, line: line, pipeline: pipeline, list: list, elseList: elseList}
|
return &withNode{nodeType: nodeWith, line: line, pipe: pipe, list: list, elseList: elseList}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *withNode) String() string {
|
func (w *withNode) String() string {
|
||||||
if w.elseList != nil {
|
if w.elseList != nil {
|
||||||
return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.pipeline, w.list, w.elseList)
|
return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.pipe, w.list, w.elseList)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("({{with %s}} %s)", w.pipeline, w.list)
|
return fmt.Sprintf("({{with %s}} %s)", w.pipe, w.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -631,17 +672,29 @@ func (t *Template) action() (n node) {
|
|||||||
// Pipeline:
|
// Pipeline:
|
||||||
// field or command
|
// field or command
|
||||||
// pipeline "|" pipeline
|
// pipeline "|" pipeline
|
||||||
func (t *Template) pipeline(context string) (pipe []*commandNode) {
|
func (t *Template) pipeline(context string) (pipe *pipeNode) {
|
||||||
|
var decl *variableNode
|
||||||
|
// Is there a declaration?
|
||||||
|
if v := t.peek(); v.typ == itemVariable {
|
||||||
|
t.next()
|
||||||
|
if ce := t.peek(); ce.typ == itemColonEquals {
|
||||||
|
t.next()
|
||||||
|
decl = newVariable(v.val)
|
||||||
|
} else {
|
||||||
|
t.backup2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pipe = newPipeline(t.lex.lineNumber(), decl)
|
||||||
for {
|
for {
|
||||||
switch token := t.next(); token.typ {
|
switch token := t.next(); token.typ {
|
||||||
case itemRightDelim:
|
case itemRightDelim:
|
||||||
if len(pipe) == 0 {
|
if len(pipe.cmds) == 0 {
|
||||||
t.errorf("missing value for %s", context)
|
t.errorf("missing value for %s", context)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemNumber, itemRawString, itemString:
|
case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemVariable, itemNumber, itemRawString, itemString:
|
||||||
t.backup()
|
t.backup()
|
||||||
pipe = append(pipe, t.command())
|
pipe.append(t.command())
|
||||||
default:
|
default:
|
||||||
t.unexpected(token, context)
|
t.unexpected(token, context)
|
||||||
}
|
}
|
||||||
@ -649,7 +702,7 @@ func (t *Template) pipeline(context string) (pipe []*commandNode) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Template) parseControl(context string) (lineNum int, pipe []*commandNode, list, elseList *listNode) {
|
func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) {
|
||||||
lineNum = t.lex.lineNumber()
|
lineNum = t.lex.lineNumber()
|
||||||
pipe = t.pipeline(context)
|
pipe = t.pipeline(context)
|
||||||
var next node
|
var next node
|
||||||
@ -732,8 +785,7 @@ func (t *Template) templateControl() node {
|
|||||||
default:
|
default:
|
||||||
t.unexpected(token, "template invocation")
|
t.unexpected(token, "template invocation")
|
||||||
}
|
}
|
||||||
pipeline := t.pipeline("template")
|
return newTemplate(t.lex.lineNumber(), name, t.pipeline("template"))
|
||||||
return newTemplate(t.lex.lineNumber(), name, pipeline)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// command:
|
// command:
|
||||||
@ -758,6 +810,8 @@ Loop:
|
|||||||
cmd.append(newIdentifier(token.val))
|
cmd.append(newIdentifier(token.val))
|
||||||
case itemDot:
|
case itemDot:
|
||||||
cmd.append(newDot())
|
cmd.append(newDot())
|
||||||
|
case itemVariable:
|
||||||
|
cmd.append(newVariable(token.val))
|
||||||
case itemField:
|
case itemField:
|
||||||
cmd.append(newField(token.val))
|
cmd.append(newField(token.val))
|
||||||
case itemBool:
|
case itemBool:
|
||||||
|
@ -146,10 +146,16 @@ var parseTests = []parseTest{
|
|||||||
`[(action: [(command: [F=[X]])])]`},
|
`[(action: [(command: [F=[X]])])]`},
|
||||||
{"simple command", "{{printf}}", noError,
|
{"simple command", "{{printf}}", noError,
|
||||||
`[(action: [(command: [I=printf])])]`},
|
`[(action: [(command: [I=printf])])]`},
|
||||||
|
{"variable invocation", "{{$x 23}}", noError,
|
||||||
|
"[(action: [(command: [V=$x N=23])])]"},
|
||||||
{"multi-word command", "{{printf `%d` 23}}", noError,
|
{"multi-word command", "{{printf `%d` 23}}", noError,
|
||||||
"[(action: [(command: [I=printf S=`%d` N=23])])]"},
|
"[(action: [(command: [I=printf S=`%d` N=23])])]"},
|
||||||
{"pipeline", "{{.X|.Y}}", noError,
|
{"pipeline", "{{.X|.Y}}", noError,
|
||||||
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
||||||
|
{"pipeline with decl", "{{$x := .X|.Y}}", noError,
|
||||||
|
`[(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,
|
{"simple if", "{{if .X}}hello{{end}}", noError,
|
||||||
`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
|
`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
|
||||||
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
||||||
|
Loading…
Reference in New Issue
Block a user