diff options
Diffstat (limited to 'src/pkg/exp/template/parse.go')
| -rw-r--r-- | src/pkg/exp/template/parse.go | 467 |
1 files changed, 364 insertions, 103 deletions
diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go index 57ddb0084..8b2d60207 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -8,17 +8,21 @@ import ( "bytes" "fmt" "os" + "reflect" "runtime" "strconv" + "strings" + "unicode" ) // 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 + name string + root *listNode + funcs map[string]reflect.Value + // Parsing only; cleared after parse. + set *Set lex *lexer - tokens chan item token item // token lookahead for parser havePeek bool } @@ -28,7 +32,7 @@ func (t *Template) next() item { if t.havePeek { t.havePeek = false } else { - t.token = <-t.tokens + t.token = t.lex.nextItem() } return t.token } @@ -43,7 +47,7 @@ func (t *Template) peek() item { if t.havePeek { return t.token } - t.token = <-t.tokens + t.token = t.lex.nextItem() t.havePeek = true return t.token } @@ -64,14 +68,18 @@ const ( nodeText nodeType = iota nodeAction nodeCommand + nodeDot nodeElse nodeEnd nodeField nodeIdentifier + nodeIf nodeList nodeNumber nodeRange nodeString + nodeTemplate + nodeWith ) // Nodes. @@ -103,25 +111,26 @@ func (l *listNode) String() string { // textNode holds plain text. type textNode struct { nodeType - text string + text []byte } func newText(text string) *textNode { - return &textNode{nodeType: nodeText, text: text} + 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 metacharacters). +// actionNode holds an action (something bounded by delimiters). type actionNode struct { nodeType + line int pipeline []*commandNode } -func newAction() *actionNode { - return &actionNode{nodeType: nodeAction} +func newAction(line int, pipeline []*commandNode) *actionNode { + return &actionNode{nodeType: nodeAction, line: line, pipeline: pipeline} } func (a *actionNode) append(command *commandNode) { @@ -164,45 +173,88 @@ 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. +// 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 + ident []string } func newField(ident string) *fieldNode { - return &fieldNode{nodeType: nodeField, ident: ident[1:]} //drop period + 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) + 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 imaginary. +// 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. -// 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) { + 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} - // Imaginary constants can only be floating-point. + 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.imaginary = true - n.isFloat = true - n.float64 = f + n.isComplex = true + n.complex128 = complex(0, f) + n.simplifyComplex() return n, nil } } @@ -250,6 +302,23 @@ func newNumber(text string) (*numberNode, os.Error) { 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) } @@ -283,11 +352,14 @@ func (e *endNode) String() string { return "{{end}}" } -// elseNode represents an {{else}} action. It is represented by a nil pointer. -type elseNode bool +// elseNode represents an {{else}} action. +type elseNode struct { + nodeType + line int +} -func newElse() *elseNode { - return nil +func newElse(line int) *elseNode { + return &elseNode{nodeType: nodeElse, line: line} } func (e *elseNode) typ() nodeType { @@ -297,37 +369,106 @@ func (e *elseNode) typ() nodeType { 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 an {{range}} action and its commands. +// rangeNode represents a {{range}} action and its commands. type rangeNode struct { nodeType - field node + line int + pipeline []*commandNode list *listNode elseList *listNode } -func newRange(field node, list *listNode) *rangeNode { - return &rangeNode{nodeType: nodeRange, field: field, list: list} +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.field, r.list, r.elseList) + return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipeline, r.list, r.elseList) } - return fmt.Sprintf("({{range %s}} %s)", r.field, r.list) + 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, + 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...)) } @@ -351,25 +492,80 @@ 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) +// 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 + } } - err = e.(os.Error) + t.next() // skip spaces. + continue } - return - }() - var next node + 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 next != nil { + if toEOF && next != nil { t.errorf("unexpected %s", next) } - return nil + return next } // itemList: @@ -398,7 +594,7 @@ func (t *Template) textOrAction() node { switch token := t.next(); token.typ { case itemText: return newText(token.val) - case itemLeftMeta: + case itemLeftDelim: return t.action() default: t.unexpected(token, "input") @@ -409,63 +605,95 @@ func (t *Template) textOrAction() node { // Action: // control // command ("|" command)* -// Left meta is past. Now get actions. +// Left delim is past. Now get actions. +// First word could be a keyword such as range. 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() + case itemIf: + return t.ifControl() + case itemRange: + return t.rangeControl() + case itemTemplate: + return t.templateControl() + case itemWith: + return t.withControl() } t.backup() -Loop: + 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 itemRightMeta: - break Loop - case itemIdentifier, itemField: - t.backup() - cmd, err := t.command() - if err != nil { - t.error(err) + case itemRightDelim: + if len(pipe) == 0 { + t.errorf("missing value for %s", context) } - action.append(cmd) + return + case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemNumber, itemRawString, itemString: + t.backup() + pipe = append(pipe, t.command()) default: - t.unexpected(token, "command") + t.unexpected(token, context) } } - return action + return } -// 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) +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) + elseList, next = t.itemList(false) if next.typ() != nodeEnd { t.errorf("expected end; found %s", next) } - r.elseList = elseList + elseList = elseList } - return r + 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(itemRightMeta, "end") + t.expect(itemRightDelim, "end") return newEnd() } @@ -473,50 +701,83 @@ func (t *Template) endControl() node { // {{else}} // Else keyword is past. func (t *Template) elseControl() node { - t.expect(itemRightMeta, "else") - return newElse() + 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 metacharacter. -// we consume the pipe character but leave the right meta to terminate the action. -func (t *Template) command() (*commandNode, os.Error) { +// 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 itemRightMeta: + case itemRightDelim: t.backup() break Loop case itemPipe: break Loop case itemError: - return nil, os.NewError(token.val) + 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 itemNumber: - if len(cmd.args) == 0 { - t.errorf("command cannot be %q", token.val) - } - number, err := newNumber(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: - 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 + t.error(err) } cmd.append(newString(s)) default: t.unexpected(token, "command") } } - return cmd, nil + if len(cmd.args) == 0 { + t.errorf("empty command") + } + return cmd } |
