]> Cypherpunks repositories - gostls13.git/commitdiff
text/template: better error messages during execution,
authorRob Pike <r@golang.org>
Wed, 3 Oct 2012 02:02:13 +0000 (12:02 +1000)
committerRob Pike <r@golang.org>
Wed, 3 Oct 2012 02:02:13 +0000 (12:02 +1000)
They now show the correct name, the byte offset on the line, and context for the failed evaluation.
Before:
        template: three:7: error calling index: index out of range: 5
After:
        template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5
Here top is the template that was parsed to create the set, and the error appears with the action
starting at byte 20 of line 7 of "top", inside the template called "three", evaluating the expression
<index "hi" $>.

Also fix a bug in index: it didn't work on strings. Ouch.

Also fix bug in error for index: was showing type of index not slice.
The real previous error was:
        template: three:7: error calling index: can't index item of type int
The html/template package's errors can be improved by building on this;
I'll do that in a separate pass.

Extends the API for text/template/parse but only by addition of a field and method. The
old API still works.

Fixes #3188.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6576058

src/pkg/html/template/escape.go
src/pkg/text/template/exec.go
src/pkg/text/template/exec_test.go
src/pkg/text/template/funcs.go
src/pkg/text/template/parse/lex.go
src/pkg/text/template/parse/node.go
src/pkg/text/template/parse/parse.go
src/pkg/text/template/parse/parse_test.go

index 5f0e28e8c1ce0191e9c1e39442e4e15fef1e734f..ce11dedf6b9d0c5c6ba42e2b588f5275fbed7224 100644 (file)
@@ -242,10 +242,11 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
        copy(newCmds, p.Cmds)
        // Merge existing identifier commands with the sanitizers needed.
        for _, id := range idents {
+               pos := id.Args[0].Position()
                i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq)
                if i != -1 {
                        for _, name := range s[:i] {
-                               newCmds = appendCmd(newCmds, newIdentCmd(name))
+                               newCmds = appendCmd(newCmds, newIdentCmd(name, pos))
                        }
                        s = s[i+1:]
                }
@@ -253,7 +254,7 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
        }
        // Create any remaining sanitizers.
        for _, name := range s {
-               newCmds = appendCmd(newCmds, newIdentCmd(name))
+               newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
        }
        p.Cmds = newCmds
 }
@@ -315,10 +316,10 @@ func escFnsEq(a, b string) bool {
 }
 
 // newIdentCmd produces a command containing a single identifier node.
-func newIdentCmd(identifier string) *parse.CommandNode {
+func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode {
        return &parse.CommandNode{
                NodeType: parse.NodeCommand,
-               Args:     []parse.Node{parse.NewIdentifier(identifier)},
+               Args:     []parse.Node{parse.NewIdentifier(identifier).SetPos(pos)},
        }
 }
 
index 5e127d7db4c6d9799a43afe3b15db9da1440c28b..b9c03d8f0b8b3a77632f6928f3347e8c46cc0c51 100644 (file)
@@ -20,7 +20,7 @@ import (
 type state struct {
        tmpl *Template
        wr   io.Writer
-       line int        // line number for errors
+       node parse.Node // current node, for errors
        vars []variable // push-down stack of variable values.
 }
 
@@ -63,17 +63,32 @@ func (s *state) varValue(name string) reflect.Value {
 
 var zero reflect.Value
 
+// at marks the state to be on node n, for error reporting.
+func (s *state) at(node parse.Node) {
+       s.node = node
+}
+
+// doublePercent returns the string with %'s replaced by %%, if necessary,
+// so it can be used safely inside a Printf format string.
+func doublePercent(str string) string {
+       if strings.Contains(str, "%") {
+               str = strings.Replace(str, "%", "%%", -1)
+       }
+       return str
+}
+
 // errorf formats the error and terminates processing.
 func (s *state) errorf(format string, args ...interface{}) {
-       format = fmt.Sprintf("template: %s:%d: %s", s.tmpl.Name(), s.line, format)
+       name := doublePercent(s.tmpl.Name())
+       if s.node == nil {
+               format = fmt.Sprintf("template: %s: %s", name, format)
+       } else {
+               location, context := s.tmpl.ErrorContext(s.node)
+               format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
+       }
        panic(fmt.Errorf(format, args...))
 }
 
-// error terminates processing.
-func (s *state) error(err error) {
-       s.errorf("%s", err)
-}
-
 // errRecover is the handler that turns panics into returns from the top
 // level of Parse.
 func errRecover(errp *error) {
@@ -108,7 +123,6 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
        state := &state{
                tmpl: t,
                wr:   wr,
-               line: 1,
                vars: []variable{{"$", value}},
        }
        if t.Tree == nil || t.Root == nil {
@@ -120,38 +134,34 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
 
 // Walk functions step through the major pieces of the template structure,
 // generating output as they go.
-func (s *state) walk(dot reflect.Value, n parse.Node) {
-       switch n := n.(type) {
+func (s *state) walk(dot reflect.Value, node parse.Node) {
+       s.at(node)
+       switch node := node.(type) {
        case *parse.ActionNode:
-               s.line = n.Line
                // Do not pop variables so they persist until next end.
                // Also, if the action declares variables, don't print the result.
-               val := s.evalPipeline(dot, n.Pipe)
-               if len(n.Pipe.Decl) == 0 {
-                       s.printValue(n, val)
+               val := s.evalPipeline(dot, node.Pipe)
+               if len(node.Pipe.Decl) == 0 {
+                       s.printValue(node, val)
                }
        case *parse.IfNode:
-               s.line = n.Line
-               s.walkIfOrWith(parse.NodeIf, dot, n.Pipe, n.List, n.ElseList)
+               s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
        case *parse.ListNode:
-               for _, node := range n.Nodes {
+               for _, node := range node.Nodes {
                        s.walk(dot, node)
                }
        case *parse.RangeNode:
-               s.line = n.Line
-               s.walkRange(dot, n)
+               s.walkRange(dot, node)
        case *parse.TemplateNode:
-               s.line = n.Line
-               s.walkTemplate(dot, n)
+               s.walkTemplate(dot, node)
        case *parse.TextNode:
-               if _, err := s.wr.Write(n.Text); err != nil {
-                       s.error(err)
+               if _, err := s.wr.Write(node.Text); err != nil {
+                       s.errorf("%s", err)
                }
        case *parse.WithNode:
-               s.line = n.Line
-               s.walkIfOrWith(parse.NodeWith, dot, n.Pipe, n.List, n.ElseList)
+               s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
        default:
-               s.errorf("unknown node: %s", n)
+               s.errorf("unknown node: %s", node)
        }
 }
 
@@ -206,6 +216,7 @@ func isTrue(val reflect.Value) (truth, ok bool) {
 }
 
 func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
+       s.at(r)
        defer s.pop(s.mark())
        val, _ := indirect(s.evalPipeline(dot, r.Pipe))
        // mark top of stack before any variables in the body are pushed.
@@ -266,6 +277,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
 }
 
 func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
+       s.at(t)
        tmpl := s.tmpl.tmpl[t.Name]
        if tmpl == nil {
                s.errorf("template %q not defined", t.Name)
@@ -291,6 +303,7 @@ func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value ref
        if pipe == nil {
                return
        }
+       s.at(pipe)
        for _, cmd := range pipe.Cmds {
                value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
                // If the object has type interface{}, dig down one level to the thing inside.
@@ -319,14 +332,14 @@ func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final ref
                return s.evalChainNode(dot, n, cmd.Args, final)
        case *parse.IdentifierNode:
                // Must be a function.
-               return s.evalFunction(dot, n.Ident, cmd.Args, final)
+               return s.evalFunction(dot, n, cmd, cmd.Args, final)
        case *parse.PipeNode:
                // Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
-               // TODO: is this right?
                return s.evalPipeline(dot, n)
        case *parse.VariableNode:
                return s.evalVariableNode(dot, n, cmd.Args, final)
        }
+       s.at(firstWord)
        s.notAFunction(cmd.Args, final)
        switch word := firstWord.(type) {
        case *parse.BoolNode:
@@ -352,6 +365,7 @@ func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
        // These are ideal constants but we don't know the type
        // and we have no context.  (If it was a method argument,
        // we'd know what we need.) The syntax guides us to some extent.
+       s.at(constant)
        switch {
        case constant.IsComplex:
                return reflect.ValueOf(constant.Complex128) // incontrovertible.
@@ -370,52 +384,57 @@ func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
 }
 
 func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
-       return s.evalFieldChain(dot, dot, field.Ident, args, final)
+       s.at(field)
+       return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
 }
 
 func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
+       s.at(chain)
        // (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
        pipe := s.evalArg(dot, nil, chain.Node)
        if len(chain.Field) == 0 {
                s.errorf("internal error: no fields in evalChainNode")
        }
-       return s.evalFieldChain(dot, pipe, chain.Field, args, final)
+       return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
 }
 
-func (s *state) evalVariableNode(dot reflect.Value, v *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
        // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
-       value := s.varValue(v.Ident[0])
-       if len(v.Ident) == 1 {
+       s.at(variable)
+       value := s.varValue(variable.Ident[0])
+       if len(variable.Ident) == 1 {
                s.notAFunction(args, final)
                return value
        }
-       return s.evalFieldChain(dot, value, v.Ident[1:], args, final)
+       return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
 }
 
 // evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
 // dot is the environment in which to evaluate arguments, while
 // receiver is the value being walked along the chain.
-func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
        n := len(ident)
        for i := 0; i < n-1; i++ {
-               receiver = s.evalField(dot, ident[i], nil, zero, receiver)
+               receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
        }
        // Now if it's a method, it gets the arguments.
-       return s.evalField(dot, ident[n-1], args, final, receiver)
+       return s.evalField(dot, ident[n-1], node, args, final, receiver)
 }
 
-func (s *state) evalFunction(dot reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
+       s.at(node)
+       name := node.Ident
        function, ok := findFunction(name, s.tmpl)
        if !ok {
                s.errorf("%q is not a defined function", name)
        }
-       return s.evalCall(dot, function, name, args, final)
+       return s.evalCall(dot, function, cmd, name, args, final)
 }
 
 // evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
 // The 'final' argument represents the return value from the preceding
 // value of the pipeline, if any.
-func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node, final, receiver reflect.Value) reflect.Value {
+func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
        if !receiver.IsValid() {
                return zero
        }
@@ -428,7 +447,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node
                ptr = ptr.Addr()
        }
        if method := ptr.MethodByName(fieldName); method.IsValid() {
-               return s.evalCall(dot, method, fieldName, args, final)
+               return s.evalCall(dot, method, node, fieldName, args, final)
        }
        hasArgs := len(args) > 1 || final.IsValid()
        // It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
@@ -473,7 +492,7 @@ var (
 // evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
 // it looks just like a function call.  The arg list, if non-nil, includes (in the manner of the shell), arg[0]
 // as the function itself.
-func (s *state) evalCall(dot, fun reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
        if args != nil {
                args = args[1:] // Zeroth arg is function name/node; not passed to function.
        }
@@ -492,7 +511,8 @@ func (s *state) evalCall(dot, fun reflect.Value, name string, args []parse.Node,
                s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
        }
        if !goodFunc(typ) {
-               s.errorf("can't handle multiple results from method/function %q", name)
+               // TODO: This could still be a confusing error; maybe goodFunc should provide info.
+               s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
        }
        // Build the arg list.
        argv := make([]reflect.Value, numIn)
@@ -519,6 +539,7 @@ func (s *state) evalCall(dot, fun reflect.Value, name string, args []parse.Node,
        result := fun.Call(argv)
        // If we have an error that is not nil, stop execution and return that error to the caller.
        if len(result) == 2 && !result[1].IsNil() {
+               s.at(node)
                s.errorf("error calling %s: %s", name, result[1].Interface().(error))
        }
        return result[0]
@@ -567,6 +588,7 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
 }
 
 func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
        switch arg := n.(type) {
        case *parse.DotNode:
                return s.validateType(dot, typ)
@@ -605,6 +627,7 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) refle
 }
 
 func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
        if n, ok := n.(*parse.BoolNode); ok {
                value := reflect.New(typ).Elem()
                value.SetBool(n.True)
@@ -615,6 +638,7 @@ func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
 }
 
 func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
        if n, ok := n.(*parse.StringNode); ok {
                value := reflect.New(typ).Elem()
                value.SetString(n.Text)
@@ -625,6 +649,7 @@ func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
 }
 
 func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
        if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
                value := reflect.New(typ).Elem()
                value.SetInt(n.Int64)
@@ -635,6 +660,7 @@ func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
 }
 
 func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
        if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
                value := reflect.New(typ).Elem()
                value.SetUint(n.Uint64)
@@ -645,6 +671,7 @@ func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Valu
 }
 
 func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
        if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
                value := reflect.New(typ).Elem()
                value.SetFloat(n.Float64)
@@ -665,6 +692,7 @@ func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
 }
 
 func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
+       s.at(n)
        switch n := n.(type) {
        case *parse.BoolNode:
                return reflect.ValueOf(n.True)
@@ -673,7 +701,7 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu
        case *parse.FieldNode:
                return s.evalFieldNode(dot, n, nil, zero)
        case *parse.IdentifierNode:
-               return s.evalFunction(dot, n.Ident, nil, zero)
+               return s.evalFunction(dot, n, n, nil, zero)
        case *parse.NilNode:
                // NilNode is handled in evalArg, the only place that calls here.
                s.errorf("evalEmptyInterface: nil (can't happen)")
@@ -708,6 +736,7 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
 // printValue writes the textual representation of the value to the output of
 // the template.
 func (s *state) printValue(n parse.Node, v reflect.Value) {
+       s.at(n)
        if v.Kind() == reflect.Ptr {
                v, _ = indirect(v) // fmt.Fprint handles nil.
        }
index 0835d31f79da26adf4b232aa75085b7f280fb5e3..d79365107d31d7d011801accffce6cdc9da2992d 100644 (file)
@@ -675,6 +675,32 @@ func TestExecuteError(t *testing.T) {
        }
 }
 
+const execErrorText = `line 1
+line 2
+line 3
+{{template "one" .}}
+{{define "one"}}{{template "two" .}}{{end}}
+{{define "two"}}{{template "three" .}}{{end}}
+{{define "three"}}{{index "hi" $}}{{end}}`
+
+// Check that an error from a nested template contains all the relevant information.
+func TestExecError(t *testing.T) {
+       tmpl, err := New("top").Parse(execErrorText)
+       if err != nil {
+               t.Fatal("parse error:", err)
+       }
+       var b bytes.Buffer
+       err = tmpl.Execute(&b, 5) // 5 is out of range indexing "hi"
+       if err == nil {
+               t.Fatal("expected error")
+       }
+       const want = `template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5`
+       got := err.Error()
+       if got != want {
+               t.Errorf("expected\n%q\ngot\n%q", want, got)
+       }
+}
+
 func TestJSEscaping(t *testing.T) {
        testCases := []struct {
                in, exp string
index d3ecb51e37aa62da930b0a4d7ae7f4a68b7603c3..31549dc45b840de7d9c019f074d2c92a5909dd52 100644 (file)
@@ -54,7 +54,7 @@ func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
                        panic("value for " + name + " not a function")
                }
                if !goodFunc(v.Type()) {
-                       panic(fmt.Errorf("can't handle multiple results from method/function %q", name))
+                       panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
                }
                out[name] = v
        }
@@ -107,7 +107,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
                        return nil, fmt.Errorf("index of nil pointer")
                }
                switch v.Kind() {
-               case reflect.Array, reflect.Slice:
+               case reflect.Array, reflect.Slice, reflect.String:
                        var x int64
                        switch index.Kind() {
                        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -134,7 +134,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
                                v = reflect.Zero(v.Type().Elem())
                        }
                default:
-                       return nil, fmt.Errorf("can't index item of type %s", index.Type())
+                       return nil, fmt.Errorf("can't index item of type %s", v.Type())
                }
        }
        return v.Interface(), nil
index be7a4fb65776c64add3dc7ff57420ca69ada8610..23c0cf0793c7d367bcc3f4758aa39b334633fecf 100644 (file)
@@ -14,7 +14,7 @@ import (
 // item represents a token or text string returned from the scanner.
 type item struct {
        typ itemType // The type of this item.
-       pos int      // The starting position, in bytes, of this item in the input string.
+       pos Pos      // The starting position, in bytes, of this item in the input string.
        val string   // The value of this item.
 }
 
@@ -38,7 +38,7 @@ type itemType int
 const (
        itemError        itemType = iota // error occurred; value is text of error
        itemBool                         // boolean constant
-       itemChar                         // printable ASCII character; grab bag for comma etc
+       itemChar                         // printable ASCII character; grab bag for comma etc.
        itemCharConstant                 // character constant
        itemComplex                      // complex constant (1+2i); imaginary is just a number
        itemColonEquals                  // colon-equals (':=') introducing a declaration
@@ -93,21 +93,22 @@ type lexer struct {
        leftDelim  string    // start of action
        rightDelim string    // end of action
        state      stateFn   // the next lexing function to enter
-       pos        int       // current position in the input
-       start      int       // start position of this item
-       width      int       // width of last rune read from input
-       lastPos    int       // position of most recent item returned by nextItem
+       pos        Pos       // current position in the input
+       start      Pos       // start position of this item
+       width      Pos       // width of last rune read from input
+       lastPos    Pos       // position of most recent item returned by nextItem
        items      chan item // channel of scanned items
        parenDepth int       // nesting depth of ( ) exprs
 }
 
 // next returns the next rune in the input.
-func (l *lexer) next() (r rune) {
-       if l.pos >= len(l.input) {
+func (l *lexer) next() rune {
+       if int(l.pos) >= len(l.input) {
                l.width = 0
                return eof
        }
-       r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
+       r, w := utf8.DecodeRuneInString(l.input[l.pos:])
+       l.width = Pos(w)
        l.pos += l.width
        return r
 }
@@ -230,7 +231,7 @@ func lexText(l *lexer) stateFn {
 
 // lexLeftDelim scans the left delimiter, which is known to be present.
 func lexLeftDelim(l *lexer) stateFn {
-       l.pos += len(l.leftDelim)
+       l.pos += Pos(len(l.leftDelim))
        if strings.HasPrefix(l.input[l.pos:], leftComment) {
                return lexComment
        }
@@ -241,19 +242,19 @@ func lexLeftDelim(l *lexer) stateFn {
 
 // lexComment scans a comment. The left comment marker is known to be present.
 func lexComment(l *lexer) stateFn {
-       l.pos += len(leftComment)
+       l.pos += Pos(len(leftComment))
        i := strings.Index(l.input[l.pos:], rightComment+l.rightDelim)
        if i < 0 {
                return l.errorf("unclosed comment")
        }
-       l.pos += i + len(rightComment) + len(l.rightDelim)
+       l.pos += Pos(i + len(rightComment) + len(l.rightDelim))
        l.ignore()
        return lexText
 }
 
 // lexRightDelim scans the right delimiter, which is known to be present.
 func lexRightDelim(l *lexer) stateFn {
-       l.pos += len(l.rightDelim)
+       l.pos += Pos(len(l.rightDelim))
        l.emit(itemRightDelim)
        return lexText
 }
@@ -291,7 +292,7 @@ func lexInsideAction(l *lexer) stateFn {
                return lexChar
        case r == '.':
                // special look-ahead for ".field" so we don't break l.backup().
-               if l.pos < len(l.input) {
+               if l.pos < Pos(len(l.input)) {
                        r := l.input[l.pos]
                        if r < '0' || '9' < r {
                                return lexField
index 39e3b320bcb56d260b0a0bf756fb1529a4ec1462..0cb7ceff4dc8bd6c303c4abe3c9d57a2c6b9eda7 100644 (file)
@@ -21,11 +21,20 @@ type Node interface {
        // To avoid type assertions, some XxxNodes also have specialized
        // CopyXxx methods that return *XxxNode.
        Copy() Node
+       Position() Pos // byte position of start of node in full original input string
 }
 
 // NodeType identifies the type of a parse tree node.
 type NodeType int
 
+// Pos represents a byte position in the original input text from which
+// this template was parsed.
+type Pos int
+
+func (p Pos) Position() Pos {
+       return p
+}
+
 // Type returns itself and provides an easy default implementation
 // for embedding in a Node. Embedded in all non-trivial Nodes.
 func (t NodeType) Type() NodeType {
@@ -60,11 +69,12 @@ const (
 // ListNode holds a sequence of nodes.
 type ListNode struct {
        NodeType
+       Pos
        Nodes []Node // The element nodes in lexical order.
 }
 
-func newList() *ListNode {
-       return &ListNode{NodeType: NodeList}
+func newList(pos Pos) *ListNode {
+       return &ListNode{NodeType: NodeList, Pos: pos}
 }
 
 func (l *ListNode) append(n Node) {
@@ -83,7 +93,7 @@ func (l *ListNode) CopyList() *ListNode {
        if l == nil {
                return l
        }
-       n := newList()
+       n := newList(l.Pos)
        for _, elem := range l.Nodes {
                n.append(elem.Copy())
        }
@@ -97,11 +107,12 @@ func (l *ListNode) Copy() Node {
 // TextNode holds plain text.
 type TextNode struct {
        NodeType
+       Pos
        Text []byte // The text; may span newlines.
 }
 
-func newText(text string) *TextNode {
-       return &TextNode{NodeType: NodeText, Text: []byte(text)}
+func newText(pos Pos, text string) *TextNode {
+       return &TextNode{NodeType: NodeText, Pos: pos, Text: []byte(text)}
 }
 
 func (t *TextNode) String() string {
@@ -115,13 +126,14 @@ func (t *TextNode) Copy() Node {
 // PipeNode holds a pipeline with optional declaration
 type PipeNode struct {
        NodeType
-       Line int             // The line number in the input.
+       Pos
+       Line int             // The line number in the input (deprecated; kept for compatibility)
        Decl []*VariableNode // Variable declarations in lexical order.
        Cmds []*CommandNode  // The commands in lexical order.
 }
 
-func newPipeline(line int, decl []*VariableNode) *PipeNode {
-       return &PipeNode{NodeType: NodePipe, Line: line, Decl: decl}
+func newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
+       return &PipeNode{NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
 }
 
 func (p *PipeNode) append(command *CommandNode) {
@@ -156,7 +168,7 @@ func (p *PipeNode) CopyPipe() *PipeNode {
        for _, d := range p.Decl {
                decl = append(decl, d.Copy().(*VariableNode))
        }
-       n := newPipeline(p.Line, decl)
+       n := newPipeline(p.Pos, p.Line, decl)
        for _, c := range p.Cmds {
                n.append(c.Copy().(*CommandNode))
        }
@@ -172,12 +184,13 @@ func (p *PipeNode) Copy() Node {
 // ones such as field evaluations and parenthesized pipelines.
 type ActionNode struct {
        NodeType
-       Line int       // The line number in the input.
+       Pos
+       Line int       // The line number in the input (deprecated; kept for compatibility)
        Pipe *PipeNode // The pipeline in the action.
 }
 
-func newAction(line int, pipe *PipeNode) *ActionNode {
-       return &ActionNode{NodeType: NodeAction, Line: line, Pipe: pipe}
+func newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
+       return &ActionNode{NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
 }
 
 func (a *ActionNode) String() string {
@@ -186,18 +199,19 @@ func (a *ActionNode) String() string {
 }
 
 func (a *ActionNode) Copy() Node {
-       return newAction(a.Line, a.Pipe.CopyPipe())
+       return newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
 
 }
 
 // CommandNode holds a command (a pipeline inside an evaluating action).
 type CommandNode struct {
        NodeType
+       Pos
        Args []Node // Arguments in lexical order: Identifier, field, or constant.
 }
 
-func newCommand() *CommandNode {
-       return &CommandNode{NodeType: NodeCommand}
+func newCommand(pos Pos) *CommandNode {
+       return &CommandNode{NodeType: NodeCommand, Pos: pos}
 }
 
 func (c *CommandNode) append(arg Node) {
@@ -223,7 +237,7 @@ func (c *CommandNode) Copy() Node {
        if c == nil {
                return c
        }
-       n := newCommand()
+       n := newCommand(c.Pos)
        for _, c := range c.Args {
                n.append(c.Copy())
        }
@@ -233,6 +247,7 @@ func (c *CommandNode) Copy() Node {
 // IdentifierNode holds an identifier.
 type IdentifierNode struct {
        NodeType
+       Pos
        Ident string // The identifier's name.
 }
 
@@ -241,23 +256,32 @@ func NewIdentifier(ident string) *IdentifierNode {
        return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
 }
 
+// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
+// Chained for convenience.
+// TODO: fix one day?
+func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
+       i.Pos = pos
+       return i
+}
+
 func (i *IdentifierNode) String() string {
        return i.Ident
 }
 
 func (i *IdentifierNode) Copy() Node {
-       return NewIdentifier(i.Ident)
+       return NewIdentifier(i.Ident).SetPos(i.Pos)
 }
 
 // VariableNode holds a list of variable names, possibly with chained field
 // accesses. The dollar sign is part of the (first) name.
 type VariableNode struct {
        NodeType
+       Pos
        Ident []string // Variable name and fields in lexical order.
 }
 
-func newVariable(ident string) *VariableNode {
-       return &VariableNode{NodeType: NodeVariable, Ident: strings.Split(ident, ".")}
+func newVariable(pos Pos, ident string) *VariableNode {
+       return &VariableNode{NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
 }
 
 func (v *VariableNode) String() string {
@@ -272,14 +296,16 @@ func (v *VariableNode) String() string {
 }
 
 func (v *VariableNode) Copy() Node {
-       return &VariableNode{NodeType: NodeVariable, Ident: append([]string{}, v.Ident...)}
+       return &VariableNode{NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
 }
 
-// DotNode holds the special identifier '.'. It is represented by a nil pointer.
-type DotNode bool
+// DotNode holds the special identifier '.'.
+type DotNode struct {
+       Pos
+}
 
-func newDot() *DotNode {
-       return nil
+func newDot(pos Pos) *DotNode {
+       return &DotNode{Pos: pos}
 }
 
 func (d *DotNode) Type() NodeType {
@@ -291,27 +317,28 @@ func (d *DotNode) String() string {
 }
 
 func (d *DotNode) Copy() Node {
-       return newDot()
+       return newDot(d.Pos)
 }
 
 // NilNode holds the special identifier 'nil' representing an untyped nil constant.
-// It is represented by a nil pointer.
-type NilNode bool
+type NilNode struct {
+       Pos
+}
 
-func newNil() *NilNode {
-       return nil
+func newNil(pos Pos) *NilNode {
+       return &NilNode{Pos: pos}
 }
 
-func (d *NilNode) Type() NodeType {
+func (n *NilNode) Type() NodeType {
        return NodeNil
 }
 
-func (d *NilNode) String() string {
+func (n *NilNode) String() string {
        return "nil"
 }
 
-func (d *NilNode) Copy() Node {
-       return newNil()
+func (n *NilNode) Copy() Node {
+       return newNil(n.Pos)
 }
 
 // FieldNode holds a field (identifier starting with '.').
@@ -319,11 +346,12 @@ func (d *NilNode) Copy() Node {
 // The period is dropped from each ident.
 type FieldNode struct {
        NodeType
+       Pos
        Ident []string // The identifiers in lexical order.
 }
 
-func newField(ident string) *FieldNode {
-       return &FieldNode{NodeType: NodeField, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
+func newField(pos Pos, ident string) *FieldNode {
+       return &FieldNode{NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
 }
 
 func (f *FieldNode) String() string {
@@ -335,7 +363,7 @@ func (f *FieldNode) String() string {
 }
 
 func (f *FieldNode) Copy() Node {
-       return &FieldNode{NodeType: NodeField, Ident: append([]string{}, f.Ident...)}
+       return &FieldNode{NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
 }
 
 // ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
@@ -343,12 +371,13 @@ func (f *FieldNode) Copy() Node {
 // The periods are dropped from each ident.
 type ChainNode struct {
        NodeType
+       Pos
        Node  Node
        Field []string // The identifiers in lexical order.
 }
 
-func newChain(node Node) *ChainNode {
-       return &ChainNode{NodeType: NodeChain, Node: node}
+func newChain(pos Pos, node Node) *ChainNode {
+       return &ChainNode{NodeType: NodeChain, Pos: pos, Node: node}
 }
 
 // Add adds the named field (which should start with a period) to the end of the chain.
@@ -375,17 +404,18 @@ func (c *ChainNode) String() string {
 }
 
 func (c *ChainNode) Copy() Node {
-       return &ChainNode{NodeType: NodeChain, Node: c.Node, Field: append([]string{}, c.Field...)}
+       return &ChainNode{NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
 }
 
 // BoolNode holds a boolean constant.
 type BoolNode struct {
        NodeType
+       Pos
        True bool // The value of the boolean constant.
 }
 
-func newBool(true bool) *BoolNode {
-       return &BoolNode{NodeType: NodeBool, True: true}
+func newBool(pos Pos, true bool) *BoolNode {
+       return &BoolNode{NodeType: NodeBool, Pos: pos, True: true}
 }
 
 func (b *BoolNode) String() string {
@@ -396,7 +426,7 @@ func (b *BoolNode) String() string {
 }
 
 func (b *BoolNode) Copy() Node {
-       return newBool(b.True)
+       return newBool(b.Pos, b.True)
 }
 
 // NumberNode holds a number: signed or unsigned integer, float, or complex.
@@ -404,6 +434,7 @@ func (b *BoolNode) Copy() Node {
 // This simulates in a small amount of code the behavior of Go's ideal constants.
 type NumberNode struct {
        NodeType
+       Pos
        IsInt      bool       // Number has an integral value.
        IsUint     bool       // Number has an unsigned integral value.
        IsFloat    bool       // Number has a floating-point value.
@@ -415,8 +446,8 @@ type NumberNode struct {
        Text       string     // The original textual representation from the input.
 }
 
-func newNumber(text string, typ itemType) (*NumberNode, error) {
-       n := &NumberNode{NodeType: NodeNumber, Text: text}
+func newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
+       n := &NumberNode{NodeType: NodeNumber, Pos: pos, Text: text}
        switch typ {
        case itemCharConstant:
                rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
@@ -526,12 +557,13 @@ func (n *NumberNode) Copy() Node {
 // StringNode holds a string constant. The value has been "unquoted".
 type StringNode struct {
        NodeType
+       Pos
        Quoted string // The original text of the string, with quotes.
        Text   string // The string, after quote processing.
 }
 
-func newString(orig, text string) *StringNode {
-       return &StringNode{NodeType: NodeString, Quoted: orig, Text: text}
+func newString(pos Pos, orig, text string) *StringNode {
+       return &StringNode{NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
 }
 
 func (s *StringNode) String() string {
@@ -539,15 +571,17 @@ func (s *StringNode) String() string {
 }
 
 func (s *StringNode) Copy() Node {
-       return newString(s.Quoted, s.Text)
+       return newString(s.Pos, s.Quoted, s.Text)
 }
 
-// endNode represents an {{end}} action. It is represented by a nil pointer.
+// endNode represents an {{end}} action.
 // It does not appear in the final parse tree.
-type endNode bool
+type endNode struct {
+       Pos
+}
 
-func newEnd() *endNode {
-       return nil
+func newEnd(pos Pos) *endNode {
+       return &endNode{Pos: pos}
 }
 
 func (e *endNode) Type() NodeType {
@@ -559,17 +593,18 @@ func (e *endNode) String() string {
 }
 
 func (e *endNode) Copy() Node {
-       return newEnd()
+       return newEnd(e.Pos)
 }
 
 // elseNode represents an {{else}} action. Does not appear in the final tree.
 type elseNode struct {
        NodeType
-       Line int // The line number in the input.
+       Pos
+       Line int // The line number in the input (deprecated; kept for compatibility)
 }
 
-func newElse(line int) *elseNode {
-       return &elseNode{NodeType: nodeElse, Line: line}
+func newElse(pos Pos, line int) *elseNode {
+       return &elseNode{NodeType: nodeElse, Pos: pos, Line: line}
 }
 
 func (e *elseNode) Type() NodeType {
@@ -581,13 +616,14 @@ func (e *elseNode) String() string {
 }
 
 func (e *elseNode) Copy() Node {
-       return newElse(e.Line)
+       return newElse(e.Pos, e.Line)
 }
 
 // BranchNode is the common representation of if, range, and with.
 type BranchNode struct {
        NodeType
-       Line     int       // The line number in the input.
+       Pos
+       Line     int       // The line number in the input (deprecated; kept for compatibility)
        Pipe     *PipeNode // The pipeline to be evaluated.
        List     *ListNode // What to execute if the value is non-empty.
        ElseList *ListNode // What to execute if the value is empty (nil if absent).
@@ -616,12 +652,12 @@ type IfNode struct {
        BranchNode
 }
 
-func newIf(line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
-       return &IfNode{BranchNode{NodeType: NodeIf, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
+func newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
+       return &IfNode{BranchNode{NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
 }
 
 func (i *IfNode) Copy() Node {
-       return newIf(i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
+       return newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
 }
 
 // RangeNode represents a {{range}} action and its commands.
@@ -629,12 +665,12 @@ type RangeNode struct {
        BranchNode
 }
 
-func newRange(line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
-       return &RangeNode{BranchNode{NodeType: NodeRange, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
+func newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
+       return &RangeNode{BranchNode{NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
 }
 
 func (r *RangeNode) Copy() Node {
-       return newRange(r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
+       return newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
 }
 
 // WithNode represents a {{with}} action and its commands.
@@ -642,24 +678,25 @@ type WithNode struct {
        BranchNode
 }
 
-func newWith(line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
-       return &WithNode{BranchNode{NodeType: NodeWith, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
+func newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
+       return &WithNode{BranchNode{NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
 }
 
 func (w *WithNode) Copy() Node {
-       return newWith(w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
+       return newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
 }
 
 // TemplateNode represents a {{template}} action.
 type TemplateNode struct {
        NodeType
-       Line int       // The line number in the input.
+       Pos
+       Line int       // The line number in the input (deprecated; kept for compatibility)
        Name string    // The name of the template (unquoted).
        Pipe *PipeNode // The command to evaluate as dot for the template.
 }
 
-func newTemplate(line int, name string, pipe *PipeNode) *TemplateNode {
-       return &TemplateNode{NodeType: NodeTemplate, Line: line, Name: name, Pipe: pipe}
+func newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
+       return &TemplateNode{NodeType: NodeTemplate, Line: line, Pos: pos, Name: name, Pipe: pipe}
 }
 
 func (t *TemplateNode) String() string {
@@ -670,5 +707,5 @@ func (t *TemplateNode) String() string {
 }
 
 func (t *TemplateNode) Copy() Node {
-       return newTemplate(t.Line, t.Name, t.Pipe.CopyPipe())
+       return newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
 }
index 9e2af12ad7fef6ee354b5bab9b0079528fc8269a..250cad5f353be122e46b9477c5db47cef42cfddf 100644 (file)
@@ -13,6 +13,7 @@ import (
        "fmt"
        "runtime"
        "strconv"
+       "strings"
        "unicode"
 )
 
@@ -21,6 +22,7 @@ type Tree struct {
        Name      string    // name of the template represented by the tree.
        ParseName string    // name of the top-level template during parsing, for error messages.
        Root      *ListNode // top-level root of the tree.
+       text      string    // text parsed to create the template (or its parent)
        // Parsing only; cleared after parse.
        funcs     []map[string]interface{}
        lex       *lexer
@@ -35,7 +37,9 @@ type Tree struct {
 // empty map is returned with the error.
 func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
        treeSet = make(map[string]*Tree)
-       _, err = New(name).Parse(text, leftDelim, rightDelim, treeSet, funcs...)
+       t := New(name)
+       t.text = text
+       _, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
        return
 }
 
@@ -112,6 +116,25 @@ func New(name string, funcs ...map[string]interface{}) *Tree {
        }
 }
 
+// ErrorContext returns a textual representation of the location of the node in the input text.
+func (t *Tree) ErrorContext(n Node) (location, context string) {
+       pos := int(n.Position())
+       text := t.text[:pos]
+       byteNum := strings.LastIndex(text, "\n")
+       if byteNum == -1 {
+               byteNum = pos // On first line.
+       } else {
+               byteNum++ // After the newline.
+               byteNum = pos - byteNum
+       }
+       lineNum := 1 + strings.Count(text, "\n")
+       context = n.String()
+       if len(context) > 20 {
+               context = fmt.Sprintf("%.20s...", context)
+       }
+       return fmt.Sprintf("%s:%d:%d", t.ParseName, lineNum, byteNum), context
+}
+
 // errorf formats the error and terminates processing.
 func (t *Tree) errorf(format string, args ...interface{}) {
        t.Root = nil
@@ -202,10 +225,11 @@ func (t *Tree) atEOF() bool {
 // the template for execution. If either action delimiter string is empty, the
 // default ("{{" or "}}") is used. Embedded template definitions are added to
 // the treeSet map.
-func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
+func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
        defer t.recover(&err)
        t.ParseName = t.Name
-       t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
+       t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
+       t.text = text
        t.parse(treeSet)
        t.add(treeSet)
        t.stopParse()
@@ -253,12 +277,13 @@ func IsEmptyTree(n Node) bool {
 // as itemList except it also parses {{define}} actions.
 // It runs to EOF.
 func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
-       t.Root = newList()
+       t.Root = newList(t.peek().pos)
        for t.peek().typ != itemEOF {
                if t.peek().typ == itemLeftDelim {
                        delim := t.next()
                        if t.nextNonSpace().typ == itemDefine {
                                newT := New("definition") // name will be updated once we know it.
+                               newT.text = t.text
                                newT.ParseName = t.ParseName
                                newT.startParse(t.funcs, t.lex)
                                newT.parseDefinition(treeSet)
@@ -300,7 +325,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
 //     textOrAction*
 // Terminates at {{end}} or {{else}}, returned separately.
 func (t *Tree) itemList() (list *ListNode, next Node) {
-       list = newList()
+       list = newList(t.peekNonSpace().pos)
        for t.peekNonSpace().typ != itemEOF {
                n := t.textOrAction()
                switch n.Type() {
@@ -318,7 +343,7 @@ func (t *Tree) itemList() (list *ListNode, next Node) {
 func (t *Tree) textOrAction() Node {
        switch token := t.nextNonSpace(); token.typ {
        case itemText:
-               return newText(token.val)
+               return newText(token.pos, token.val)
        case itemLeftDelim:
                return t.action()
        default:
@@ -349,13 +374,14 @@ func (t *Tree) action() (n Node) {
        }
        t.backup()
        // Do not pop variables; they persist until "end".
-       return newAction(t.lex.lineNumber(), t.pipeline("command"))
+       return newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
 }
 
 // Pipeline:
 //     declarations? command ('|' command)*
 func (t *Tree) pipeline(context string) (pipe *PipeNode) {
        var decl []*VariableNode
+       pos := t.peekNonSpace().pos
        // Are there declarations?
        for {
                if v := t.peekNonSpace(); v.typ == itemVariable {
@@ -367,7 +393,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
                        tokenAfterVariable := t.peek()
                        if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
                                t.nextNonSpace()
-                               variable := newVariable(v.val)
+                               variable := newVariable(v.pos, v.val)
                                decl = append(decl, variable)
                                t.vars = append(t.vars, v.val)
                                if next.typ == itemChar && next.val == "," {
@@ -384,7 +410,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
                }
                break
        }
-       pipe = newPipeline(t.lex.lineNumber(), decl)
+       pipe = newPipeline(pos, t.lex.lineNumber(), decl)
        for {
                switch token := t.nextNonSpace(); token.typ {
                case itemRightDelim, itemRightParen:
@@ -406,9 +432,9 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
        return
 }
 
-func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, elseList *ListNode) {
-       lineNum = t.lex.lineNumber()
+func (t *Tree) parseControl(context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
        defer t.popVars(len(t.vars))
+       line = t.lex.lineNumber()
        pipe = t.pipeline(context)
        var next Node
        list, next = t.itemList()
@@ -421,7 +447,7 @@ func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list,
                }
                elseList = elseList
        }
-       return lineNum, pipe, list, elseList
+       return pipe.Position(), line, pipe, list, elseList
 }
 
 // If:
@@ -452,16 +478,14 @@ func (t *Tree) withControl() Node {
 //     {{end}}
 // End keyword is past.
 func (t *Tree) endControl() Node {
-       t.expect(itemRightDelim, "end")
-       return newEnd()
+       return newEnd(t.expect(itemRightDelim, "end").pos)
 }
 
 // Else:
 //     {{else}}
 // Else keyword is past.
 func (t *Tree) elseControl() Node {
-       t.expect(itemRightDelim, "else")
-       return newElse(t.lex.lineNumber())
+       return newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
 }
 
 // Template:
@@ -470,7 +494,8 @@ func (t *Tree) elseControl() Node {
 // to a string.
 func (t *Tree) templateControl() Node {
        var name string
-       switch token := t.nextNonSpace(); token.typ {
+       token := t.nextNonSpace()
+       switch token.typ {
        case itemString, itemRawString:
                s, err := strconv.Unquote(token.val)
                if err != nil {
@@ -486,7 +511,7 @@ func (t *Tree) templateControl() Node {
                // Do not pop variables; they persist until "end".
                pipe = t.pipeline("template")
        }
-       return newTemplate(t.lex.lineNumber(), name, pipe)
+       return newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
 }
 
 // command:
@@ -494,7 +519,7 @@ func (t *Tree) templateControl() Node {
 // 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 *Tree) command() *CommandNode {
-       cmd := newCommand()
+       cmd := newCommand(t.peekNonSpace().pos)
        for {
                t.peekNonSpace() // skip leading spaces.
                operand := t.operand()
@@ -531,7 +556,7 @@ func (t *Tree) operand() Node {
                return nil
        }
        if t.peek().typ == itemField {
-               chain := newChain(node)
+               chain := newChain(t.peek().pos, node)
                for t.peek().typ == itemField {
                        chain.Add(t.next().val)
                }
@@ -541,9 +566,9 @@ func (t *Tree) operand() Node {
                // TODO: Switch to Chains always when we can.
                switch node.Type() {
                case NodeField:
-                       node = newField(chain.String())
+                       node = newField(chain.Position(), chain.String())
                case NodeVariable:
-                       node = newVariable(chain.String())
+                       node = newVariable(chain.Position(), chain.String())
                default:
                        node = chain
                }
@@ -568,19 +593,19 @@ func (t *Tree) term() Node {
                if !t.hasFunction(token.val) {
                        t.errorf("function %q not defined", token.val)
                }
-               return NewIdentifier(token.val)
+               return NewIdentifier(token.val).SetPos(token.pos)
        case itemDot:
-               return newDot()
+               return newDot(token.pos)
        case itemNil:
-               return newNil()
+               return newNil(token.pos)
        case itemVariable:
-               return t.useVar(token.val)
+               return t.useVar(token.pos, token.val)
        case itemField:
-               return newField(token.val)
+               return newField(token.pos, token.val)
        case itemBool:
-               return newBool(token.val == "true")
+               return newBool(token.pos, token.val == "true")
        case itemCharConstant, itemComplex, itemNumber:
-               number, err := newNumber(token.val, token.typ)
+               number, err := newNumber(token.pos, token.val, token.typ)
                if err != nil {
                        t.error(err)
                }
@@ -596,7 +621,7 @@ func (t *Tree) term() Node {
                if err != nil {
                        t.error(err)
                }
-               return newString(token.val, s)
+               return newString(token.pos, token.val, s)
        }
        t.backup()
        return nil
@@ -622,8 +647,8 @@ func (t *Tree) popVars(n int) {
 
 // useVar returns a node for a variable reference. It errors if the
 // variable is not defined.
-func (t *Tree) useVar(name string) Node {
-       v := newVariable(name)
+func (t *Tree) useVar(pos Pos, name string) Node {
+       v := newVariable(pos, name)
        for _, varName := range t.vars {
                if varName == v.Ident[0] {
                        return v
index 0f75c33e7779b23adfcee1e26e7dd66a6403ebcb..695c76ebfe3649f43888558b80cca63b2012c889 100644 (file)
@@ -85,7 +85,7 @@ func TestNumberParse(t *testing.T) {
                                typ = itemComplex
                        }
                }
-               n, err := newNumber(test.text, typ)
+               n, err := newNumber(0, test.text, typ)
                ok := test.isInt || test.isUint || test.isFloat || test.isComplex
                if ok && err != nil {
                        t.Errorf("unexpected error for %q: %s", test.text, err)