1
0
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:
Rob Pike 2011-07-08 17:54:16 +10:00
parent ee14989e43
commit b8c664297b
3 changed files with 120 additions and 60 deletions

View File

@ -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 {

View File

@ -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:

View File

@ -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,