Commit 7f4b4c0c authored by Rob Pike's avatar Rob Pike

text/template: better error messages during execution,

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
parent 1659aef3
...@@ -242,10 +242,11 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) { ...@@ -242,10 +242,11 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
copy(newCmds, p.Cmds) copy(newCmds, p.Cmds)
// Merge existing identifier commands with the sanitizers needed. // Merge existing identifier commands with the sanitizers needed.
for _, id := range idents { for _, id := range idents {
pos := id.Args[0].Position()
i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq) i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq)
if i != -1 { if i != -1 {
for _, name := range s[:i] { for _, name := range s[:i] {
newCmds = appendCmd(newCmds, newIdentCmd(name)) newCmds = appendCmd(newCmds, newIdentCmd(name, pos))
} }
s = s[i+1:] s = s[i+1:]
} }
...@@ -253,7 +254,7 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) { ...@@ -253,7 +254,7 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
} }
// Create any remaining sanitizers. // Create any remaining sanitizers.
for _, name := range s { for _, name := range s {
newCmds = appendCmd(newCmds, newIdentCmd(name)) newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
} }
p.Cmds = newCmds p.Cmds = newCmds
} }
...@@ -315,10 +316,10 @@ func escFnsEq(a, b string) bool { ...@@ -315,10 +316,10 @@ func escFnsEq(a, b string) bool {
} }
// newIdentCmd produces a command containing a single identifier node. // 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{ return &parse.CommandNode{
NodeType: parse.NodeCommand, NodeType: parse.NodeCommand,
Args: []parse.Node{parse.NewIdentifier(identifier)}, Args: []parse.Node{parse.NewIdentifier(identifier).SetPos(pos)},
} }
} }
......
This diff is collapsed.
...@@ -675,6 +675,32 @@ func TestExecuteError(t *testing.T) { ...@@ -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) { func TestJSEscaping(t *testing.T) {
testCases := []struct { testCases := []struct {
in, exp string in, exp string
......
...@@ -54,7 +54,7 @@ func addValueFuncs(out map[string]reflect.Value, in FuncMap) { ...@@ -54,7 +54,7 @@ func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
panic("value for " + name + " not a function") panic("value for " + name + " not a function")
} }
if !goodFunc(v.Type()) { 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 out[name] = v
} }
...@@ -107,7 +107,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) { ...@@ -107,7 +107,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
return nil, fmt.Errorf("index of nil pointer") return nil, fmt.Errorf("index of nil pointer")
} }
switch v.Kind() { switch v.Kind() {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice, reflect.String:
var x int64 var x int64
switch index.Kind() { switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
...@@ -134,7 +134,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) { ...@@ -134,7 +134,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
v = reflect.Zero(v.Type().Elem()) v = reflect.Zero(v.Type().Elem())
} }
default: 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 return v.Interface(), nil
......
...@@ -14,7 +14,7 @@ import ( ...@@ -14,7 +14,7 @@ import (
// item represents a token or text string returned from the scanner. // item represents a token or text string returned from the scanner.
type item struct { type item struct {
typ itemType // The type of this item. 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. val string // The value of this item.
} }
...@@ -38,7 +38,7 @@ type itemType int ...@@ -38,7 +38,7 @@ type itemType int
const ( const (
itemError itemType = iota // error occurred; value is text of error itemError itemType = iota // error occurred; value is text of error
itemBool // boolean constant itemBool // boolean constant
itemChar // printable ASCII character; grab bag for comma etc itemChar // printable ASCII character; grab bag for comma etc.
itemCharConstant // character constant itemCharConstant // character constant
itemComplex // complex constant (1+2i); imaginary is just a number itemComplex // complex constant (1+2i); imaginary is just a number
itemColonEquals // colon-equals (':=') introducing a declaration itemColonEquals // colon-equals (':=') introducing a declaration
...@@ -93,21 +93,22 @@ type lexer struct { ...@@ -93,21 +93,22 @@ type lexer struct {
leftDelim string // start of action leftDelim string // start of action
rightDelim string // end of action rightDelim string // end of action
state stateFn // the next lexing function to enter state stateFn // the next lexing function to enter
pos int // current position in the input pos Pos // current position in the input
start int // start position of this item start Pos // start position of this item
width int // width of last rune read from input width Pos // width of last rune read from input
lastPos int // position of most recent item returned by nextItem lastPos Pos // position of most recent item returned by nextItem
items chan item // channel of scanned items items chan item // channel of scanned items
parenDepth int // nesting depth of ( ) exprs parenDepth int // nesting depth of ( ) exprs
} }
// next returns the next rune in the input. // next returns the next rune in the input.
func (l *lexer) next() (r rune) { func (l *lexer) next() rune {
if l.pos >= len(l.input) { if int(l.pos) >= len(l.input) {
l.width = 0 l.width = 0
return eof 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 l.pos += l.width
return r return r
} }
...@@ -230,7 +231,7 @@ func lexText(l *lexer) stateFn { ...@@ -230,7 +231,7 @@ func lexText(l *lexer) stateFn {
// lexLeftDelim scans the left delimiter, which is known to be present. // lexLeftDelim scans the left delimiter, which is known to be present.
func lexLeftDelim(l *lexer) stateFn { func lexLeftDelim(l *lexer) stateFn {
l.pos += len(l.leftDelim) l.pos += Pos(len(l.leftDelim))
if strings.HasPrefix(l.input[l.pos:], leftComment) { if strings.HasPrefix(l.input[l.pos:], leftComment) {
return lexComment return lexComment
} }
...@@ -241,19 +242,19 @@ func lexLeftDelim(l *lexer) stateFn { ...@@ -241,19 +242,19 @@ func lexLeftDelim(l *lexer) stateFn {
// lexComment scans a comment. The left comment marker is known to be present. // lexComment scans a comment. The left comment marker is known to be present.
func lexComment(l *lexer) stateFn { func lexComment(l *lexer) stateFn {
l.pos += len(leftComment) l.pos += Pos(len(leftComment))
i := strings.Index(l.input[l.pos:], rightComment+l.rightDelim) i := strings.Index(l.input[l.pos:], rightComment+l.rightDelim)
if i < 0 { if i < 0 {
return l.errorf("unclosed comment") return l.errorf("unclosed comment")
} }
l.pos += i + len(rightComment) + len(l.rightDelim) l.pos += Pos(i + len(rightComment) + len(l.rightDelim))
l.ignore() l.ignore()
return lexText return lexText
} }
// lexRightDelim scans the right delimiter, which is known to be present. // lexRightDelim scans the right delimiter, which is known to be present.
func lexRightDelim(l *lexer) stateFn { func lexRightDelim(l *lexer) stateFn {
l.pos += len(l.rightDelim) l.pos += Pos(len(l.rightDelim))
l.emit(itemRightDelim) l.emit(itemRightDelim)
return lexText return lexText
} }
...@@ -291,7 +292,7 @@ func lexInsideAction(l *lexer) stateFn { ...@@ -291,7 +292,7 @@ func lexInsideAction(l *lexer) stateFn {
return lexChar return lexChar
case r == '.': case r == '.':
// special look-ahead for ".field" so we don't break l.backup(). // 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] r := l.input[l.pos]
if r < '0' || '9' < r { if r < '0' || '9' < r {
return lexField return lexField
......
This diff is collapsed.
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"fmt" "fmt"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"unicode" "unicode"
) )
...@@ -21,6 +22,7 @@ type Tree struct { ...@@ -21,6 +22,7 @@ type Tree struct {
Name string // name of the template represented by the tree. Name string // name of the template represented by the tree.
ParseName string // name of the top-level template during parsing, for error messages. ParseName string // name of the top-level template during parsing, for error messages.
Root *ListNode // top-level root of the tree. Root *ListNode // top-level root of the tree.
text string // text parsed to create the template (or its parent)
// Parsing only; cleared after parse. // Parsing only; cleared after parse.
funcs []map[string]interface{} funcs []map[string]interface{}
lex *lexer lex *lexer
...@@ -35,7 +37,9 @@ type Tree struct { ...@@ -35,7 +37,9 @@ type Tree struct {
// empty map is returned with the error. // empty map is returned with the error.
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) { func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
treeSet = make(map[string]*Tree) 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 return
} }
...@@ -112,6 +116,25 @@ func New(name string, funcs ...map[string]interface{}) *Tree { ...@@ -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. // errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) { func (t *Tree) errorf(format string, args ...interface{}) {
t.Root = nil t.Root = nil
...@@ -202,10 +225,11 @@ func (t *Tree) atEOF() bool { ...@@ -202,10 +225,11 @@ func (t *Tree) atEOF() bool {
// the template for execution. If either action delimiter string is empty, the // the template for execution. If either action delimiter string is empty, the
// default ("{{" or "}}") is used. Embedded template definitions are added to // default ("{{" or "}}") is used. Embedded template definitions are added to
// the treeSet map. // 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) defer t.recover(&err)
t.ParseName = t.Name 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.parse(treeSet)
t.add(treeSet) t.add(treeSet)
t.stopParse() t.stopParse()
...@@ -253,12 +277,13 @@ func IsEmptyTree(n Node) bool { ...@@ -253,12 +277,13 @@ func IsEmptyTree(n Node) bool {
// as itemList except it also parses {{define}} actions. // as itemList except it also parses {{define}} actions.
// It runs to EOF. // It runs to EOF.
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) { func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
t.Root = newList() t.Root = newList(t.peek().pos)
for t.peek().typ != itemEOF { for t.peek().typ != itemEOF {
if t.peek().typ == itemLeftDelim { if t.peek().typ == itemLeftDelim {
delim := t.next() delim := t.next()
if t.nextNonSpace().typ == itemDefine { if t.nextNonSpace().typ == itemDefine {
newT := New("definition") // name will be updated once we know it. newT := New("definition") // name will be updated once we know it.
newT.text = t.text
newT.ParseName = t.ParseName newT.ParseName = t.ParseName
newT.startParse(t.funcs, t.lex) newT.startParse(t.funcs, t.lex)
newT.parseDefinition(treeSet) newT.parseDefinition(treeSet)
...@@ -300,7 +325,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) { ...@@ -300,7 +325,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
// textOrAction* // textOrAction*
// Terminates at {{end}} or {{else}}, returned separately. // Terminates at {{end}} or {{else}}, returned separately.
func (t *Tree) itemList() (list *ListNode, next Node) { func (t *Tree) itemList() (list *ListNode, next Node) {
list = newList() list = newList(t.peekNonSpace().pos)
for t.peekNonSpace().typ != itemEOF { for t.peekNonSpace().typ != itemEOF {
n := t.textOrAction() n := t.textOrAction()
switch n.Type() { switch n.Type() {
...@@ -318,7 +343,7 @@ func (t *Tree) itemList() (list *ListNode, next Node) { ...@@ -318,7 +343,7 @@ func (t *Tree) itemList() (list *ListNode, next Node) {
func (t *Tree) textOrAction() Node { func (t *Tree) textOrAction() Node {
switch token := t.nextNonSpace(); token.typ { switch token := t.nextNonSpace(); token.typ {
case itemText: case itemText:
return newText(token.val) return newText(token.pos, token.val)
case itemLeftDelim: case itemLeftDelim:
return t.action() return t.action()
default: default:
...@@ -349,13 +374,14 @@ func (t *Tree) action() (n Node) { ...@@ -349,13 +374,14 @@ func (t *Tree) action() (n Node) {
} }
t.backup() t.backup()
// Do not pop variables; they persist until "end". // 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: // Pipeline:
// declarations? command ('|' command)* // declarations? command ('|' command)*
func (t *Tree) pipeline(context string) (pipe *PipeNode) { func (t *Tree) pipeline(context string) (pipe *PipeNode) {
var decl []*VariableNode var decl []*VariableNode
pos := t.peekNonSpace().pos
// Are there declarations? // Are there declarations?
for { for {
if v := t.peekNonSpace(); v.typ == itemVariable { if v := t.peekNonSpace(); v.typ == itemVariable {
...@@ -367,7 +393,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) { ...@@ -367,7 +393,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
tokenAfterVariable := t.peek() tokenAfterVariable := t.peek()
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") { if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
t.nextNonSpace() t.nextNonSpace()
variable := newVariable(v.val) variable := newVariable(v.pos, v.val)
decl = append(decl, variable) decl = append(decl, variable)
t.vars = append(t.vars, v.val) t.vars = append(t.vars, v.val)
if next.typ == itemChar && next.val == "," { if next.typ == itemChar && next.val == "," {
...@@ -384,7 +410,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) { ...@@ -384,7 +410,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
} }
break break
} }
pipe = newPipeline(t.lex.lineNumber(), decl) pipe = newPipeline(pos, t.lex.lineNumber(), decl)
for { for {
switch token := t.nextNonSpace(); token.typ { switch token := t.nextNonSpace(); token.typ {
case itemRightDelim, itemRightParen: case itemRightDelim, itemRightParen:
...@@ -406,9 +432,9 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) { ...@@ -406,9 +432,9 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
return return
} }
func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, elseList *ListNode) { func (t *Tree) parseControl(context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
lineNum = t.lex.lineNumber()
defer t.popVars(len(t.vars)) defer t.popVars(len(t.vars))
line = t.lex.lineNumber()
pipe = t.pipeline(context) pipe = t.pipeline(context)
var next Node var next Node
list, next = t.itemList() list, next = t.itemList()
...@@ -421,7 +447,7 @@ func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, ...@@ -421,7 +447,7 @@ func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list,
} }
elseList = elseList elseList = elseList
} }
return lineNum, pipe, list, elseList return pipe.Position(), line, pipe, list, elseList
} }
// If: // If:
...@@ -452,16 +478,14 @@ func (t *Tree) withControl() Node { ...@@ -452,16 +478,14 @@ func (t *Tree) withControl() Node {
// {{end}} // {{end}}
// End keyword is past. // End keyword is past.
func (t *Tree) endControl() Node { func (t *Tree) endControl() Node {
t.expect(itemRightDelim, "end") return newEnd(t.expect(itemRightDelim, "end").pos)
return newEnd()
} }
// Else: // Else:
// {{else}} // {{else}}
// Else keyword is past. // Else keyword is past.
func (t *Tree) elseControl() Node { func (t *Tree) elseControl() Node {
t.expect(itemRightDelim, "else") return newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
return newElse(t.lex.lineNumber())
} }
// Template: // Template:
...@@ -470,7 +494,8 @@ func (t *Tree) elseControl() Node { ...@@ -470,7 +494,8 @@ func (t *Tree) elseControl() Node {
// to a string. // to a string.
func (t *Tree) templateControl() Node { func (t *Tree) templateControl() Node {
var name string var name string
switch token := t.nextNonSpace(); token.typ { token := t.nextNonSpace()
switch token.typ {
case itemString, itemRawString: case itemString, itemRawString:
s, err := strconv.Unquote(token.val) s, err := strconv.Unquote(token.val)
if err != nil { if err != nil {
...@@ -486,7 +511,7 @@ func (t *Tree) templateControl() Node { ...@@ -486,7 +511,7 @@ func (t *Tree) templateControl() Node {
// Do not pop variables; they persist until "end". // Do not pop variables; they persist until "end".
pipe = t.pipeline("template") pipe = t.pipeline("template")
} }
return newTemplate(t.lex.lineNumber(), name, pipe) return newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
} }
// command: // command:
...@@ -494,7 +519,7 @@ func (t *Tree) templateControl() Node { ...@@ -494,7 +519,7 @@ func (t *Tree) templateControl() Node {
// space-separated arguments up to a pipeline character or right delimiter. // 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. // we consume the pipe character but leave the right delim to terminate the action.
func (t *Tree) command() *CommandNode { func (t *Tree) command() *CommandNode {
cmd := newCommand() cmd := newCommand(t.peekNonSpace().pos)
for { for {
t.peekNonSpace() // skip leading spaces. t.peekNonSpace() // skip leading spaces.
operand := t.operand() operand := t.operand()
...@@ -531,7 +556,7 @@ func (t *Tree) operand() Node { ...@@ -531,7 +556,7 @@ func (t *Tree) operand() Node {
return nil return nil
} }
if t.peek().typ == itemField { if t.peek().typ == itemField {
chain := newChain(node) chain := newChain(t.peek().pos, node)
for t.peek().typ == itemField { for t.peek().typ == itemField {
chain.Add(t.next().val) chain.Add(t.next().val)
} }
...@@ -541,9 +566,9 @@ func (t *Tree) operand() Node { ...@@ -541,9 +566,9 @@ func (t *Tree) operand() Node {
// TODO: Switch to Chains always when we can. // TODO: Switch to Chains always when we can.
switch node.Type() { switch node.Type() {
case NodeField: case NodeField:
node = newField(chain.String()) node = newField(chain.Position(), chain.String())
case NodeVariable: case NodeVariable:
node = newVariable(chain.String()) node = newVariable(chain.Position(), chain.String())
default: default:
node = chain node = chain
} }
...@@ -568,19 +593,19 @@ func (t *Tree) term() Node { ...@@ -568,19 +593,19 @@ func (t *Tree) term() Node {
if !t.hasFunction(token.val) { if !t.hasFunction(token.val) {
t.errorf("function %q not defined", token.val) t.errorf("function %q not defined", token.val)
} }
return NewIdentifier(token.val) return NewIdentifier(token.val).SetPos(token.pos)
case itemDot: case itemDot:
return newDot() return newDot(token.pos)
case itemNil: case itemNil:
return newNil() return newNil(token.pos)
case itemVariable: case itemVariable:
return t.useVar(token.val) return t.useVar(token.pos, token.val)
case itemField: case itemField:
return newField(token.val) return newField(token.pos, token.val)
case itemBool: case itemBool:
return newBool(token.val == "true") return newBool(token.pos, token.val == "true")
case itemCharConstant, itemComplex, itemNumber: case itemCharConstant, itemComplex, itemNumber:
number, err := newNumber(token.val, token.typ) number, err := newNumber(token.pos, token.val, token.typ)
if err != nil { if err != nil {
t.error(err) t.error(err)
} }
...@@ -596,7 +621,7 @@ func (t *Tree) term() Node { ...@@ -596,7 +621,7 @@ func (t *Tree) term() Node {
if err != nil { if err != nil {
t.error(err) t.error(err)
} }
return newString(token.val, s) return newString(token.pos, token.val, s)
} }
t.backup() t.backup()
return nil return nil
...@@ -622,8 +647,8 @@ func (t *Tree) popVars(n int) { ...@@ -622,8 +647,8 @@ func (t *Tree) popVars(n int) {
// useVar returns a node for a variable reference. It errors if the // useVar returns a node for a variable reference. It errors if the
// variable is not defined. // variable is not defined.
func (t *Tree) useVar(name string) Node { func (t *Tree) useVar(pos Pos, name string) Node {
v := newVariable(name) v := newVariable(pos, name)
for _, varName := range t.vars { for _, varName := range t.vars {
if varName == v.Ident[0] { if varName == v.Ident[0] {
return v return v
......
...@@ -85,7 +85,7 @@ func TestNumberParse(t *testing.T) { ...@@ -85,7 +85,7 @@ func TestNumberParse(t *testing.T) {
typ = itemComplex typ = itemComplex
} }
} }
n, err := newNumber(test.text, typ) n, err := newNumber(0, test.text, typ)
ok := test.isInt || test.isUint || test.isFloat || test.isComplex ok := test.isInt || test.isUint || test.isFloat || test.isComplex
if ok && err != nil { if ok && err != nil {
t.Errorf("unexpected error for %q: %s", test.text, err) t.Errorf("unexpected error for %q: %s", test.text, err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment