| // 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" |
| "runtime" |
| "strconv" |
| ) |
| |
| // Template is the representation of a parsed template. |
| type Template struct { |
| // TODO: At the moment, these are all internal to parsing. |
| name string |
| root *listNode |
| lex *lexer |
| tokens chan item |
| 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.tokens |
| } |
| 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.tokens |
| 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 |
| nodeElse |
| nodeEnd |
| nodeField |
| nodeIdentifier |
| nodeList |
| nodeNumber |
| nodeRange |
| nodeString |
| ) |
| |
| // 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 string |
| } |
| |
| func newText(text string) *textNode { |
| return &textNode{nodeType: nodeText, text: text} |
| } |
| |
| func (t *textNode) String() string { |
| return fmt.Sprintf("(text: %q)", t.text) |
| } |
| |
| // actionNode holds an action (something bounded by metacharacters). |
| type actionNode struct { |
| nodeType |
| pipeline []*commandNode |
| } |
| |
| func newAction() *actionNode { |
| return &actionNode{nodeType: nodeAction} |
| } |
| |
| 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) |
| } |
| |
| // fieldNode holds a field (identifier starting with '.'). The period is dropped from the ident. |
| type fieldNode struct { |
| nodeType |
| ident string |
| } |
| |
| func newField(ident string) *fieldNode { |
| return &fieldNode{nodeType: nodeField, ident: ident[1:]} //drop period |
| } |
| |
| func (f *fieldNode) String() string { |
| return fmt.Sprintf("F=.%s", f.ident) |
| } |
| |
| // numberNode holds a number, signed or unsigned, integer, floating, or imaginary. |
| // 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. |
| // TODO: booleans, complex numbers. |
| 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 |
| imaginary bool // number is imaginary |
| int64 // the signed integer value |
| uint64 // the unsigned integer value |
| float64 // the positive floating-point value |
| text string |
| } |
| |
| func newNumber(text string) (*numberNode, os.Error) { |
| n := &numberNode{nodeType: nodeNumber, text: text} |
| // Imaginary constants can only be floating-point. |
| if len(text) > 0 && text[len(text)-1] == 'i' { |
| f, err := strconv.Atof64(text[:len(text)-1]) |
| if err == nil { |
| n.imaginary = true |
| n.isFloat = true |
| n.float64 = f |
| 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 |
| } |
| |
| 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. It is represented by a nil pointer. |
| type elseNode bool |
| |
| func newElse() *elseNode { |
| return nil |
| } |
| |
| func (e *elseNode) typ() nodeType { |
| return nodeElse |
| } |
| |
| func (e *elseNode) String() string { |
| return "{{else}}" |
| } |
| |
| // rangeNode represents an {{range}} action and its commands. |
| type rangeNode struct { |
| nodeType |
| field node |
| list *listNode |
| elseList *listNode |
| } |
| |
| func newRange(field node, list *listNode) *rangeNode { |
| return &rangeNode{nodeType: nodeRange, field: field, list: list} |
| } |
| |
| func (r *rangeNode) String() string { |
| if r.elseList != nil { |
| return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.field, r.list, r.elseList) |
| } |
| return fmt.Sprintf("({{range %s}} %s)", r.field, r.list) |
| } |
| |
| // Parsing. |
| |
| // New allocates a new template with the given name. |
| func New(name string) *Template { |
| return &Template{ |
| name: name, |
| } |
| } |
| |
| // errorf formats the error and terminates processing. |
| func (t *Template) errorf(format string, args ...interface{}) { |
| 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) |
| } |
| |
| // Parse parses the template definition string and constructs an efficient representation of the template. |
| func (t *Template) Parse(s string) (err os.Error) { |
| t.lex, t.tokens = lex(t.name, s) |
| defer func() { |
| e := recover() |
| if e != nil { |
| if _, ok := e.(runtime.Error); ok { |
| panic(e) |
| } |
| err = e.(os.Error) |
| } |
| return |
| }() |
| var next node |
| t.root, next = t.itemList(true) |
| if next != nil { |
| t.errorf("unexpected %s", next) |
| } |
| return nil |
| } |
| |
| // 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 itemLeftMeta: |
| return t.action() |
| default: |
| t.unexpected(token, "input") |
| } |
| return nil |
| } |
| |
| // Action: |
| // control |
| // command ("|" command)* |
| // Left meta is past. Now get actions. |
| func (t *Template) action() (n node) { |
| action := newAction() |
| switch token := t.next(); token.typ { |
| case itemRange: |
| return t.rangeControl() |
| case itemElse: |
| return t.elseControl() |
| case itemEnd: |
| return t.endControl() |
| } |
| t.backup() |
| Loop: |
| for { |
| switch token := t.next(); token.typ { |
| case itemRightMeta: |
| break Loop |
| case itemIdentifier, itemField: |
| t.backup() |
| cmd, err := t.command() |
| if err != nil { |
| t.error(err) |
| } |
| action.append(cmd) |
| default: |
| t.unexpected(token, "command") |
| } |
| } |
| return action |
| } |
| |
| // Range: |
| // {{range field}} itemList {{end}} |
| // {{range field}} itemList {{else}} itemList {{end}} |
| // Range keyword is past. |
| func (t *Template) rangeControl() node { |
| field := t.expect(itemField, "range") |
| t.expect(itemRightMeta, "range") |
| list, next := t.itemList(false) |
| r := newRange(newField(field.val), list) |
| switch next.typ() { |
| case nodeEnd: //done |
| case nodeElse: |
| elseList, next := t.itemList(false) |
| if next.typ() != nodeEnd { |
| t.errorf("expected end; found %s", next) |
| } |
| r.elseList = elseList |
| } |
| return r |
| } |
| |
| // End: |
| // {{end}} |
| // End keyword is past. |
| func (t *Template) endControl() node { |
| t.expect(itemRightMeta, "end") |
| return newEnd() |
| } |
| |
| // Else: |
| // {{else}} |
| // Else keyword is past. |
| func (t *Template) elseControl() node { |
| t.expect(itemRightMeta, "else") |
| return newElse() |
| } |
| |
| // command: |
| // space-separated arguments up to a pipeline character or right metacharacter. |
| // we consume the pipe character but leave the right meta to terminate the action. |
| func (t *Template) command() (*commandNode, os.Error) { |
| cmd := newCommand() |
| Loop: |
| for { |
| switch token := t.next(); token.typ { |
| case itemRightMeta: |
| t.backup() |
| break Loop |
| case itemPipe: |
| break Loop |
| case itemError: |
| return nil, os.NewError(token.val) |
| case itemIdentifier: |
| cmd.append(newIdentifier(token.val)) |
| case itemField: |
| cmd.append(newField(token.val)) |
| case itemNumber: |
| if len(cmd.args) == 0 { |
| t.errorf("command cannot be %q", token.val) |
| } |
| number, err := newNumber(token.val) |
| if err != nil { |
| t.error(err) |
| } |
| cmd.append(number) |
| case itemString, itemRawString: |
| if len(cmd.args) == 0 { |
| t.errorf("command cannot be %q", token.val) |
| } |
| s, err := strconv.Unquote(token.val) |
| if err != nil { |
| return nil, err |
| } |
| cmd.append(newString(s)) |
| default: |
| t.unexpected(token, "command") |
| } |
| } |
| return cmd, nil |
| } |