summaryrefslogtreecommitdiff
path: root/src/pkg/exp/template/parse.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/exp/template/parse.go')
-rw-r--r--src/pkg/exp/template/parse.go783
1 files changed, 783 insertions, 0 deletions
diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go
new file mode 100644
index 000000000..8b2d60207
--- /dev/null
+++ b/src/pkg/exp/template/parse.go
@@ -0,0 +1,783 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "os"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "unicode"
+)
+
+// Template is the representation of a parsed template.
+type Template struct {
+ name string
+ root *listNode
+ funcs map[string]reflect.Value
+ // Parsing only; cleared after parse.
+ set *Set
+ lex *lexer
+ token item // token lookahead for parser
+ havePeek bool
+}
+
+// next returns the next token.
+func (t *Template) next() item {
+ if t.havePeek {
+ t.havePeek = false
+ } else {
+ t.token = t.lex.nextItem()
+ }
+ return t.token
+}
+
+// backup backs the input stream up one token.
+func (t *Template) backup() {
+ t.havePeek = true
+}
+
+// peek returns but does not consume the next token.
+func (t *Template) peek() item {
+ if t.havePeek {
+ return t.token
+ }
+ t.token = t.lex.nextItem()
+ t.havePeek = true
+ return t.token
+}
+
+// A node is an element in the parse tree. The interface is trivial.
+type node interface {
+ typ() nodeType
+ String() string
+}
+
+type nodeType int
+
+func (t nodeType) typ() nodeType {
+ return t
+}
+
+const (
+ nodeText nodeType = iota
+ nodeAction
+ nodeCommand
+ nodeDot
+ nodeElse
+ nodeEnd
+ nodeField
+ nodeIdentifier
+ nodeIf
+ nodeList
+ nodeNumber
+ nodeRange
+ nodeString
+ nodeTemplate
+ nodeWith
+)
+
+// Nodes.
+
+// listNode holds a sequence of nodes.
+type listNode struct {
+ nodeType
+ nodes []node
+}
+
+func newList() *listNode {
+ return &listNode{nodeType: nodeList}
+}
+
+func (l *listNode) append(n node) {
+ l.nodes = append(l.nodes, n)
+}
+
+func (l *listNode) String() string {
+ b := new(bytes.Buffer)
+ fmt.Fprint(b, "[")
+ for _, n := range l.nodes {
+ fmt.Fprint(b, n)
+ }
+ fmt.Fprint(b, "]")
+ return b.String()
+}
+
+// textNode holds plain text.
+type textNode struct {
+ nodeType
+ text []byte
+}
+
+func newText(text string) *textNode {
+ return &textNode{nodeType: nodeText, text: []byte(text)}
+}
+
+func (t *textNode) String() string {
+ return fmt.Sprintf("(text: %q)", t.text)
+}
+
+// actionNode holds an action (something bounded by delimiters).
+type actionNode struct {
+ nodeType
+ line int
+ pipeline []*commandNode
+}
+
+func newAction(line int, pipeline []*commandNode) *actionNode {
+ return &actionNode{nodeType: nodeAction, line: line, pipeline: pipeline}
+}
+
+func (a *actionNode) append(command *commandNode) {
+ a.pipeline = append(a.pipeline, command)
+}
+
+func (a *actionNode) String() string {
+ return fmt.Sprintf("(action: %v)", a.pipeline)
+}
+
+// commandNode holds a command (a pipeline inside an evaluating action).
+type commandNode struct {
+ nodeType
+ args []node // identifier, string, or number
+}
+
+func newCommand() *commandNode {
+ return &commandNode{nodeType: nodeCommand}
+}
+
+func (c *commandNode) append(arg node) {
+ c.args = append(c.args, arg)
+}
+
+func (c *commandNode) String() string {
+ return fmt.Sprintf("(command: %v)", c.args)
+}
+
+// identifierNode holds an identifier.
+type identifierNode struct {
+ nodeType
+ ident string
+}
+
+func newIdentifier(ident string) *identifierNode {
+ return &identifierNode{nodeType: nodeIdentifier, ident: ident}
+}
+
+func (i *identifierNode) String() string {
+ return fmt.Sprintf("I=%s", i.ident)
+}
+
+// dotNode holds the special identifier '.'. It is represented by a nil pointer.
+type dotNode bool
+
+func newDot() *dotNode {
+ return nil
+}
+
+func (d *dotNode) typ() nodeType {
+ return nodeDot
+}
+
+func (d *dotNode) String() string {
+ return "{{<.>}}"
+}
+
+// fieldNode holds a field (identifier starting with '.').
+// The names may be chained ('.x.y').
+// The period is dropped from each ident.
+type fieldNode struct {
+ nodeType
+ ident []string
+}
+
+func newField(ident string) *fieldNode {
+ return &fieldNode{nodeType: nodeField, ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
+}
+
+func (f *fieldNode) String() string {
+ return fmt.Sprintf("F=%s", f.ident)
+}
+
+// boolNode holds a boolean constant.
+type boolNode struct {
+ nodeType
+ true bool
+}
+
+func newBool(true bool) *boolNode {
+ return &boolNode{nodeType: nodeString, true: true}
+}
+
+func (b *boolNode) String() string {
+ if b.true {
+ return fmt.Sprintf("B=true")
+ }
+ return fmt.Sprintf("B=false")
+}
+
+// numberNode holds a number, signed or unsigned integer, floating, or complex.
+// The value is parsed and stored under all the types that can represent the value.
+// This simulates in a small amount of code the behavior of Go's ideal constants.
+type numberNode struct {
+ nodeType
+ isInt bool // number has an integral value
+ isUint bool // number has an unsigned integral value
+ isFloat bool // number has a floating-point value
+ isComplex bool // number is complex
+ int64 // the signed integer value
+ uint64 // the unsigned integer value
+ float64 // the floating-point value
+ complex128 // the complex value
+ text string
+}
+
+func newNumber(text string, isComplex bool) (*numberNode, os.Error) {
+ n := &numberNode{nodeType: nodeNumber, text: text}
+ if isComplex {
+ // fmt.Sscan can parse the pair, so let it do the work.
+ if _, err := fmt.Sscan(text, &n.complex128); err != nil {
+ return nil, err
+ }
+ n.isComplex = true
+ n.simplifyComplex()
+ return n, nil
+ }
+ // Imaginary constants can only be complex unless they are zero.
+ if len(text) > 0 && text[len(text)-1] == 'i' {
+ f, err := strconv.Atof64(text[:len(text)-1])
+ if err == nil {
+ n.isComplex = true
+ n.complex128 = complex(0, f)
+ n.simplifyComplex()
+ return n, nil
+ }
+ }
+ // Do integer test first so we get 0x123 etc.
+ u, err := strconv.Btoui64(text, 0) // will fail for -0; fixed below.
+ if err == nil {
+ n.isUint = true
+ n.uint64 = u
+ }
+ i, err := strconv.Btoi64(text, 0)
+ if err == nil {
+ n.isInt = true
+ n.int64 = i
+ if i == 0 {
+ n.isUint = true // in case of -0.
+ n.uint64 = u
+ }
+ }
+ // If an integer extraction succeeded, promote the float.
+ if n.isInt {
+ n.isFloat = true
+ n.float64 = float64(n.int64)
+ } else if n.isUint {
+ n.isFloat = true
+ n.float64 = float64(n.uint64)
+ } else {
+ f, err := strconv.Atof64(text)
+ if err == nil {
+ n.isFloat = true
+ n.float64 = f
+ // If a floating-point extraction succeeded, extract the int if needed.
+ if !n.isInt && float64(int64(f)) == f {
+ n.isInt = true
+ n.int64 = int64(f)
+ }
+ if !n.isUint && float64(uint64(f)) == f {
+ n.isUint = true
+ n.uint64 = uint64(f)
+ }
+ }
+ }
+ if !n.isInt && !n.isUint && !n.isFloat {
+ return nil, fmt.Errorf("illegal number syntax: %q", text)
+ }
+ return n, nil
+}
+
+// simplifyComplex pulls out any other types that are represented by the complex number.
+// These all require that the imaginary part be zero.
+func (n *numberNode) simplifyComplex() {
+ n.isFloat = imag(n.complex128) == 0
+ if n.isFloat {
+ n.float64 = real(n.complex128)
+ n.isInt = float64(int64(n.float64)) == n.float64
+ if n.isInt {
+ n.int64 = int64(n.float64)
+ }
+ n.isUint = float64(uint64(n.float64)) == n.float64
+ if n.isUint {
+ n.uint64 = uint64(n.float64)
+ }
+ }
+}
+
+func (n *numberNode) String() string {
+ return fmt.Sprintf("N=%s", n.text)
+}
+
+// stringNode holds a quoted string.
+type stringNode struct {
+ nodeType
+ text string
+}
+
+func newString(text string) *stringNode {
+ return &stringNode{nodeType: nodeString, text: text}
+}
+
+func (s *stringNode) String() string {
+ return fmt.Sprintf("S=%#q", s.text)
+}
+
+// endNode represents an {{end}} action. It is represented by a nil pointer.
+type endNode bool
+
+func newEnd() *endNode {
+ return nil
+}
+
+func (e *endNode) typ() nodeType {
+ return nodeEnd
+}
+
+func (e *endNode) String() string {
+ return "{{end}}"
+}
+
+// elseNode represents an {{else}} action.
+type elseNode struct {
+ nodeType
+ line int
+}
+
+func newElse(line int) *elseNode {
+ return &elseNode{nodeType: nodeElse, line: line}
+}
+
+func (e *elseNode) typ() nodeType {
+ return nodeElse
+}
+
+func (e *elseNode) String() string {
+ return "{{else}}"
+}
+// ifNode represents an {{if}} action and its commands.
+// TODO: what should evaluation look like? is a pipeline enough?
+type ifNode struct {
+ nodeType
+ line int
+ pipeline []*commandNode
+ list *listNode
+ elseList *listNode
+}
+
+func newIf(line int, pipeline []*commandNode, list, elseList *listNode) *ifNode {
+ return &ifNode{nodeType: nodeIf, line: line, pipeline: pipeline, list: list, elseList: elseList}
+}
+
+func (i *ifNode) String() string {
+ if i.elseList != nil {
+ return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.pipeline, i.list, i.elseList)
+ }
+ return fmt.Sprintf("({{if %s}} %s)", i.pipeline, i.list)
+}
+
+// rangeNode represents a {{range}} action and its commands.
+type rangeNode struct {
+ nodeType
+ line int
+ pipeline []*commandNode
+ list *listNode
+ elseList *listNode
+}
+
+func newRange(line int, pipeline []*commandNode, list, elseList *listNode) *rangeNode {
+ return &rangeNode{nodeType: nodeRange, line: line, pipeline: pipeline, list: list, elseList: elseList}
+}
+
+func (r *rangeNode) String() string {
+ if r.elseList != nil {
+ return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipeline, r.list, r.elseList)
+ }
+ return fmt.Sprintf("({{range %s}} %s)", r.pipeline, r.list)
+}
+
+// templateNode represents a {{template}} action.
+type templateNode struct {
+ nodeType
+ line int
+ name node
+ pipeline []*commandNode
+}
+
+func newTemplate(line int, name node, pipeline []*commandNode) *templateNode {
+ return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipeline: pipeline}
+}
+
+func (t *templateNode) String() string {
+ return fmt.Sprintf("{{template %s %s}}", t.name, t.pipeline)
+}
+
+// withNode represents a {{with}} action and its commands.
+type withNode struct {
+ nodeType
+ line int
+ pipeline []*commandNode
+ list *listNode
+ elseList *listNode
+}
+
+func newWith(line int, pipeline []*commandNode, list, elseList *listNode) *withNode {
+ return &withNode{nodeType: nodeWith, line: line, pipeline: pipeline, list: list, elseList: elseList}
+}
+
+func (w *withNode) String() string {
+ if w.elseList != nil {
+ return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.pipeline, w.list, w.elseList)
+ }
+ return fmt.Sprintf("({{with %s}} %s)", w.pipeline, w.list)
+}
+
+
+// Parsing.
+
+// New allocates a new template with the given name.
+func New(name string) *Template {
+ return &Template{
+ 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...))
+}
+
+// error terminates processing.
+func (t *Template) error(err os.Error) {
+ t.errorf("%s", err)
+}
+
+// expect consumes the next token and guarantees it has the required type.
+func (t *Template) expect(expected itemType, context string) item {
+ token := t.next()
+ if token.typ != expected {
+ t.errorf("expected %s in %s; got %s", expected, context, token)
+ }
+ return token
+}
+
+// unexpected complains about the token and terminates processing.
+func (t *Template) unexpected(token item, context string) {
+ t.errorf("unexpected %s in %s", token, context)
+}
+
+// recover is the handler that turns panics into returns from the top
+// level of Parse or Execute.
+func (t *Template) recover(errp *os.Error) {
+ e := recover()
+ if e != nil {
+ if _, ok := e.(runtime.Error); ok {
+ panic(e)
+ }
+ t.stopParse()
+ *errp = e.(os.Error)
+ }
+ return
+}
+
+// startParse starts the template parsing from the lexer.
+func (t *Template) startParse(set *Set, lex *lexer) {
+ t.root = nil
+ t.set = set
+ t.lex = lex
+}
+
+// stopParse terminates parsing.
+func (t *Template) stopParse() {
+ t.set, t.lex = nil, nil
+}
+
+// atEOF returns true if, possibly after spaces, we're at EOF.
+func (t *Template) atEOF() bool {
+ for {
+ token := t.peek()
+ switch token.typ {
+ case itemEOF:
+ return true
+ case itemText:
+ for _, r := range token.val {
+ if !unicode.IsSpace(r) {
+ return false
+ }
+ }
+ t.next() // skip spaces.
+ continue
+ }
+ break
+ }
+ return false
+}
+
+// Parse parses the template definition string to construct an internal representation
+// of the template for execution.
+func (t *Template) Parse(s string) (err os.Error) {
+ t.startParse(nil, lex(t.name, s))
+ defer t.recover(&err)
+ t.parse(true)
+ t.stopParse()
+ return
+}
+
+// ParseInSet parses the template definition string to construct an internal representation
+// of the template for execution. Function bindings are checked against those in the set.
+func (t *Template) ParseInSet(s string, set *Set) (err os.Error) {
+ t.startParse(set, lex(t.name, s))
+ defer t.recover(&err)
+ t.parse(true)
+ t.stopParse()
+ return
+}
+
+// parse is the helper for Parse. It triggers an error if we expect EOF but don't reach it.
+func (t *Template) parse(toEOF bool) (next node) {
+ t.root, next = t.itemList(true)
+ if toEOF && next != nil {
+ t.errorf("unexpected %s", next)
+ }
+ return next
+}
+
+// itemList:
+// textOrAction*
+// Terminates at EOF and at {{end}} or {{else}}, which is returned separately.
+// The toEOF flag tells whether we expect to reach EOF.
+func (t *Template) itemList(toEOF bool) (list *listNode, next node) {
+ list = newList()
+ for t.peek().typ != itemEOF {
+ n := t.textOrAction()
+ switch n.typ() {
+ case nodeEnd, nodeElse:
+ return list, n
+ }
+ list.append(n)
+ }
+ if !toEOF {
+ t.unexpected(t.next(), "input")
+ }
+ return list, nil
+}
+
+// textOrAction:
+// text | action
+func (t *Template) textOrAction() node {
+ switch token := t.next(); token.typ {
+ case itemText:
+ return newText(token.val)
+ case itemLeftDelim:
+ return t.action()
+ default:
+ t.unexpected(token, "input")
+ }
+ return nil
+}
+
+// Action:
+// control
+// command ("|" command)*
+// Left delim is past. Now get actions.
+// First word could be a keyword such as range.
+func (t *Template) action() (n node) {
+ switch token := t.next(); token.typ {
+ case itemElse:
+ return t.elseControl()
+ case itemEnd:
+ return t.endControl()
+ case itemIf:
+ return t.ifControl()
+ case itemRange:
+ return t.rangeControl()
+ case itemTemplate:
+ return t.templateControl()
+ case itemWith:
+ return t.withControl()
+ }
+ t.backup()
+ return newAction(t.lex.lineNumber(), t.pipeline("command"))
+}
+
+// Pipeline:
+// field or command
+// pipeline "|" pipeline
+func (t *Template) pipeline(context string) (pipe []*commandNode) {
+ for {
+ switch token := t.next(); token.typ {
+ case itemRightDelim:
+ if len(pipe) == 0 {
+ t.errorf("missing value for %s", context)
+ }
+ return
+ case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemNumber, itemRawString, itemString:
+ t.backup()
+ pipe = append(pipe, t.command())
+ default:
+ t.unexpected(token, context)
+ }
+ }
+ return
+}
+
+func (t *Template) parseControl(context string) (lineNum int, pipe []*commandNode, list, elseList *listNode) {
+ lineNum = t.lex.lineNumber()
+ pipe = t.pipeline(context)
+ var next node
+ list, next = t.itemList(false)
+ switch next.typ() {
+ case nodeEnd: //done
+ case nodeElse:
+ elseList, next = t.itemList(false)
+ if next.typ() != nodeEnd {
+ t.errorf("expected end; found %s", next)
+ }
+ elseList = elseList
+ }
+ return lineNum, pipe, list, elseList
+}
+
+// If:
+// {{if pipeline}} itemList {{end}}
+// {{if pipeline}} itemList {{else}} itemList {{end}}
+// If keyword is past.
+func (t *Template) ifControl() node {
+ return newIf(t.parseControl("if"))
+}
+
+// Range:
+// {{range pipeline}} itemList {{end}}
+// {{range pipeline}} itemList {{else}} itemList {{end}}
+// Range keyword is past.
+func (t *Template) rangeControl() node {
+ return newRange(t.parseControl("range"))
+}
+
+// With:
+// {{with pipeline}} itemList {{end}}
+// {{with pipeline}} itemList {{else}} itemList {{end}}
+// If keyword is past.
+func (t *Template) withControl() node {
+ return newWith(t.parseControl("with"))
+}
+
+
+// End:
+// {{end}}
+// End keyword is past.
+func (t *Template) endControl() node {
+ t.expect(itemRightDelim, "end")
+ return newEnd()
+}
+
+// Else:
+// {{else}}
+// Else keyword is past.
+func (t *Template) elseControl() node {
+ t.expect(itemRightDelim, "else")
+ return newElse(t.lex.lineNumber())
+}
+
+// Template:
+// {{template stringValue pipeline}}
+// Template keyword is past. The name must be something that can evaluate
+// to a string.
+func (t *Template) templateControl() node {
+ var name node
+ switch token := t.next(); token.typ {
+ case itemIdentifier:
+ if _, ok := findFunction(token.val, t, t.set); !ok {
+ t.errorf("function %q not defined", token.val)
+ }
+ name = newIdentifier(token.val)
+ case itemDot:
+ name = newDot()
+ case itemField:
+ name = newField(token.val)
+ case itemString, itemRawString:
+ s, err := strconv.Unquote(token.val)
+ if err != nil {
+ t.error(err)
+ }
+ name = newString(s)
+ default:
+ t.unexpected(token, "template invocation")
+ }
+ pipeline := t.pipeline("template")
+ return newTemplate(t.lex.lineNumber(), name, pipeline)
+}
+
+// command:
+// space-separated arguments up to a pipeline character or right delimiter.
+// we consume the pipe character but leave the right delim to terminate the action.
+func (t *Template) command() *commandNode {
+ cmd := newCommand()
+Loop:
+ for {
+ switch token := t.next(); token.typ {
+ case itemRightDelim:
+ t.backup()
+ break Loop
+ case itemPipe:
+ break Loop
+ case itemError:
+ t.errorf("%s", token.val)
+ case itemIdentifier:
+ if _, ok := findFunction(token.val, t, t.set); !ok {
+ t.errorf("function %q not defined", token.val)
+ }
+ cmd.append(newIdentifier(token.val))
+ case itemDot:
+ cmd.append(newDot())
+ case itemField:
+ cmd.append(newField(token.val))
+ case itemBool:
+ cmd.append(newBool(token.val == "true"))
+ case itemComplex, itemNumber:
+ number, err := newNumber(token.val, token.typ == itemComplex)
+ if err != nil {
+ t.error(err)
+ }
+ cmd.append(number)
+ case itemString, itemRawString:
+ s, err := strconv.Unquote(token.val)
+ if err != nil {
+ t.error(err)
+ }
+ cmd.append(newString(s))
+ default:
+ t.unexpected(token, "command")
+ }
+ }
+ if len(cmd.args) == 0 {
+ t.errorf("empty command")
+ }
+ return cmd
+}