diff options
Diffstat (limited to 'src/pkg/template/parse')
| -rw-r--r-- | src/pkg/template/parse/Makefile | 14 | ||||
| -rw-r--r-- | src/pkg/template/parse/lex.go | 474 | ||||
| -rw-r--r-- | src/pkg/template/parse/lex_test.go | 209 | ||||
| -rw-r--r-- | src/pkg/template/parse/node.go | 469 | ||||
| -rw-r--r-- | src/pkg/template/parse/parse.go | 436 | ||||
| -rw-r--r-- | src/pkg/template/parse/parse_test.go | 259 | ||||
| -rw-r--r-- | src/pkg/template/parse/set.go | 50 | 
7 files changed, 1911 insertions, 0 deletions
| diff --git a/src/pkg/template/parse/Makefile b/src/pkg/template/parse/Makefile new file mode 100644 index 000000000..fe6585809 --- /dev/null +++ b/src/pkg/template/parse/Makefile @@ -0,0 +1,14 @@ +# Copyright 2011 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include ../../../Make.inc + +TARG=template/parse +GOFILES=\ +	lex.go\ +	node.go\ +	parse.go\ +	set.go\ + +include ../../../Make.pkg diff --git a/src/pkg/template/parse/lex.go b/src/pkg/template/parse/lex.go new file mode 100644 index 000000000..7ec4e920b --- /dev/null +++ b/src/pkg/template/parse/lex.go @@ -0,0 +1,474 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parse + +import ( +	"fmt" +	"strings" +	"unicode" +	"utf8" +) + +// item represents a token or text string returned from the scanner. +type item struct { +	typ itemType +	val string +} + +func (i item) String() string { +	switch { +	case i.typ == itemEOF: +		return "EOF" +	case i.typ == itemError: +		return i.val +	case i.typ > itemKeyword: +		return fmt.Sprintf("<%s>", i.val) +	case len(i.val) > 10: +		return fmt.Sprintf("%.10q...", i.val) +	} +	return fmt.Sprintf("%q", i.val) +} + +// itemType identifies the type of lex items. +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. +	itemCharConstant                 // character constant +	itemComplex                      // complex constant (1+2i); imaginary is just a number +	itemColonEquals                  // colon-equals (':=') introducing a declaration +	itemEOF +	itemField      // alphanumeric identifier, starting with '.', possibly chained ('.x.y') +	itemIdentifier // alphanumeric identifier +	itemLeftDelim  // left action delimiter +	itemNumber     // simple number, including imaginary +	itemPipe       // pipe symbol +	itemRawString  // raw quoted string (includes quotes) +	itemRightDelim // right action delimiter +	itemString     // quoted string (includes quotes) +	itemText       // plain text +	itemVariable   // variable starting with '$', such as '$' or  '$1' or '$hello'. +	// Keywords appear after all the rest. +	itemKeyword  // used only to delimit the keywords +	itemDot      // the cursor, spelled '.'. +	itemDefine   // define keyword +	itemElse     // else keyword +	itemEnd      // end keyword +	itemIf       // if keyword +	itemRange    // range keyword +	itemTemplate // template keyword +	itemWith     // with keyword +) + +// Make the types prettyprint. +var itemName = map[itemType]string{ +	itemError:        "error", +	itemBool:         "bool", +	itemChar:         "char", +	itemCharConstant: "charconst", +	itemComplex:      "complex", +	itemColonEquals:  ":=", +	itemEOF:          "EOF", +	itemField:        "field", +	itemIdentifier:   "identifier", +	itemLeftDelim:    "left delim", +	itemNumber:       "number", +	itemPipe:         "pipe", +	itemRawString:    "raw string", +	itemRightDelim:   "right delim", +	itemString:       "string", +	itemVariable:     "variable", +	// keywords +	itemDot:      ".", +	itemDefine:   "define", +	itemElse:     "else", +	itemIf:       "if", +	itemEnd:      "end", +	itemRange:    "range", +	itemTemplate: "template", +	itemWith:     "with", +} + +func (i itemType) String() string { +	s := itemName[i] +	if s == "" { +		return fmt.Sprintf("item%d", int(i)) +	} +	return s +} + +var key = map[string]itemType{ +	".":        itemDot, +	"define":   itemDefine, +	"else":     itemElse, +	"end":      itemEnd, +	"if":       itemIf, +	"range":    itemRange, +	"template": itemTemplate, +	"with":     itemWith, +} + +const eof = -1 + +// stateFn represents the state of the scanner as a function that returns the next state. +type stateFn func(*lexer) stateFn + +// lexer holds the state of the scanner. +type lexer struct { +	name  string    // the name of the input; used only for error reports. +	input string    // the string being scanned. +	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. +	items chan item // channel of scanned items. +} + +// next returns the next rune in the input. +func (l *lexer) next() (rune int) { +	if l.pos >= len(l.input) { +		l.width = 0 +		return eof +	} +	rune, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) +	l.pos += l.width +	return rune +} + +// peek returns but does not consume the next rune in the input. +func (l *lexer) peek() int { +	rune := l.next() +	l.backup() +	return rune +} + +// backup steps back one rune. Can only be called once per call of next. +func (l *lexer) backup() { +	l.pos -= l.width +} + +// emit passes an item back to the client. +func (l *lexer) emit(t itemType) { +	l.items <- item{t, l.input[l.start:l.pos]} +	l.start = l.pos +} + +// ignore skips over the pending input before this point. +func (l *lexer) ignore() { +	l.start = l.pos +} + +// accept consumes the next rune if it's from the valid set. +func (l *lexer) accept(valid string) bool { +	if strings.IndexRune(valid, l.next()) >= 0 { +		return true +	} +	l.backup() +	return false +} + +// acceptRun consumes a run of runes from the valid set. +func (l *lexer) acceptRun(valid string) { +	for strings.IndexRune(valid, l.next()) >= 0 { +	} +	l.backup() +} + +// lineNumber reports which line we're on. Doing it this way +// means we don't have to worry about peek double counting. +func (l *lexer) lineNumber() int { +	return 1 + strings.Count(l.input[:l.pos], "\n") +} + +// error returns an error token and terminates the scan by passing +// back a nil pointer that will be the next state, terminating l.run. +func (l *lexer) errorf(format string, args ...interface{}) stateFn { +	l.items <- item{itemError, fmt.Sprintf(format, args...)} +	return nil +} + +// nextItem returns the next item from the input. +func (l *lexer) nextItem() item { +	for { +		select { +		case item := <-l.items: +			return item +		default: +			l.state = l.state(l) +		} +	} +	panic("not reached") +} + +// lex creates a new scanner for the input string. +func lex(name, input string) *lexer { +	l := &lexer{ +		name:  name, +		input: input, +		state: lexText, +		items: make(chan item, 2), // Two items of buffering is sufficient for all state functions +	} +	return l +} + +// state functions + +const ( +	leftDelim    = "{{" +	rightDelim   = "}}" +	leftComment  = "{{/*" +	rightComment = "*/}}" +) + +// lexText scans until an opening action delimiter, "{{". +func lexText(l *lexer) stateFn { +	for { +		if strings.HasPrefix(l.input[l.pos:], leftDelim) { +			if l.pos > l.start { +				l.emit(itemText) +			} +			return lexLeftDelim +		} +		if l.next() == eof { +			break +		} +	} +	// Correctly reached EOF. +	if l.pos > l.start { +		l.emit(itemText) +	} +	l.emit(itemEOF) +	return nil +} + +// lexLeftDelim scans the left delimiter, which is known to be present. +func lexLeftDelim(l *lexer) stateFn { +	if strings.HasPrefix(l.input[l.pos:], leftComment) { +		return lexComment +	} +	l.pos += len(leftDelim) +	l.emit(itemLeftDelim) +	return lexInsideAction +} + +// lexComment scans a comment. The left comment marker is known to be present. +func lexComment(l *lexer) stateFn { +	i := strings.Index(l.input[l.pos:], rightComment) +	if i < 0 { +		return l.errorf("unclosed comment") +	} +	l.pos += i + len(rightComment) +	l.ignore() +	return lexText +} + +// lexRightDelim scans the right delimiter, which is known to be present. +func lexRightDelim(l *lexer) stateFn { +	l.pos += len(rightDelim) +	l.emit(itemRightDelim) +	return lexText +} + +// lexInsideAction scans the elements inside action delimiters. +func lexInsideAction(l *lexer) stateFn { +	// Either number, quoted string, or identifier. +	// Spaces separate and are ignored. +	// Pipe symbols separate and are emitted. +	for { +		if strings.HasPrefix(l.input[l.pos:], rightDelim) { +			return lexRightDelim +		} +		switch r := l.next(); { +		case r == eof || r == '\n': +			return l.errorf("unclosed action") +		case isSpace(r): +			l.ignore() +		case r == ':': +			if l.next() != '=' { +				return l.errorf("expected :=") +			} +			l.emit(itemColonEquals) +		case r == '|': +			l.emit(itemPipe) +		case r == '"': +			return lexQuote +		case r == '`': +			return lexRawQuote +		case r == '$': +			return lexIdentifier +		case r == '\'': +			return lexChar +		case r == '.': +			// special look-ahead for ".field" so we don't break l.backup(). +			if l.pos < len(l.input) { +				r := l.input[l.pos] +				if r < '0' || '9' < r { +					return lexIdentifier // itemDot comes from the keyword table. +				} +			} +			fallthrough // '.' can start a number. +		case r == '+' || r == '-' || ('0' <= r && r <= '9'): +			l.backup() +			return lexNumber +		case isAlphaNumeric(r): +			l.backup() +			return lexIdentifier +		case r <= unicode.MaxASCII && unicode.IsPrint(r): +			l.emit(itemChar) +			return lexInsideAction +		default: +			return l.errorf("unrecognized character in action: %#U", r) +		} +	} +	return nil +} + +// lexIdentifier scans an alphanumeric or field. +func lexIdentifier(l *lexer) stateFn { +Loop: +	for { +		switch r := l.next(); { +		case isAlphaNumeric(r): +			// absorb. +		case r == '.' && (l.input[l.start] == '.' || l.input[l.start] == '$'): +			// field chaining; absorb into one token. +		default: +			l.backup() +			word := l.input[l.start:l.pos] +			switch { +			case key[word] > itemKeyword: +				l.emit(key[word]) +			case word[0] == '.': +				l.emit(itemField) +			case word[0] == '$': +				l.emit(itemVariable) +			case word == "true", word == "false": +				l.emit(itemBool) +			default: +				l.emit(itemIdentifier) +			} +			break Loop +		} +	} +	return lexInsideAction +} + +// lexChar scans a character constant. The initial quote is already +// scanned.  Syntax checking is done by the parse. +func lexChar(l *lexer) stateFn { +Loop: +	for { +		switch l.next() { +		case '\\': +			if r := l.next(); r != eof && r != '\n' { +				break +			} +			fallthrough +		case eof, '\n': +			return l.errorf("unterminated character constant") +		case '\'': +			break Loop +		} +	} +	l.emit(itemCharConstant) +	return lexInsideAction +} + +// lexNumber scans a number: decimal, octal, hex, float, or imaginary.  This +// isn't a perfect number scanner - for instance it accepts "." and "0x0.2" +// and "089" - but when it's wrong the input is invalid and the parser (via +// strconv) will notice. +func lexNumber(l *lexer) stateFn { +	if !l.scanNumber() { +		return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) +	} +	if sign := l.peek(); sign == '+' || sign == '-' { +		// Complex: 1+2i.  No spaces, must end in 'i'. +		if !l.scanNumber() || l.input[l.pos-1] != 'i' { +			return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) +		} +		l.emit(itemComplex) +	} else { +		l.emit(itemNumber) +	} +	return lexInsideAction +} + +func (l *lexer) scanNumber() bool { +	// Optional leading sign. +	l.accept("+-") +	// Is it hex? +	digits := "0123456789" +	if l.accept("0") && l.accept("xX") { +		digits = "0123456789abcdefABCDEF" +	} +	l.acceptRun(digits) +	if l.accept(".") { +		l.acceptRun(digits) +	} +	if l.accept("eE") { +		l.accept("+-") +		l.acceptRun("0123456789") +	} +	// Is it imaginary? +	l.accept("i") +	// Next thing mustn't be alphanumeric. +	if isAlphaNumeric(l.peek()) { +		l.next() +		return false +	} +	return true +} + +// lexQuote scans a quoted string. +func lexQuote(l *lexer) stateFn { +Loop: +	for { +		switch l.next() { +		case '\\': +			if r := l.next(); r != eof && r != '\n' { +				break +			} +			fallthrough +		case eof, '\n': +			return l.errorf("unterminated quoted string") +		case '"': +			break Loop +		} +	} +	l.emit(itemString) +	return lexInsideAction +} + +// lexRawQuote scans a raw quoted string. +func lexRawQuote(l *lexer) stateFn { +Loop: +	for { +		switch l.next() { +		case eof, '\n': +			return l.errorf("unterminated raw quoted string") +		case '`': +			break Loop +		} +	} +	l.emit(itemRawString) +	return lexInsideAction +} + +// isSpace reports whether r is a space character. +func isSpace(r int) bool { +	switch r { +	case ' ', '\t', '\n', '\r': +		return true +	} +	return false +} + +// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. +func isAlphaNumeric(r int) bool { +	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) +} diff --git a/src/pkg/template/parse/lex_test.go b/src/pkg/template/parse/lex_test.go new file mode 100644 index 000000000..2ad91d5fa --- /dev/null +++ b/src/pkg/template/parse/lex_test.go @@ -0,0 +1,209 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parse + +import ( +	"reflect" +	"testing" +) + +type lexTest struct { +	name  string +	input string +	items []item +} + +var ( +	tEOF      = item{itemEOF, ""} +	tLeft     = item{itemLeftDelim, "{{"} +	tRight    = item{itemRightDelim, "}}"} +	tRange    = item{itemRange, "range"} +	tPipe     = item{itemPipe, "|"} +	tFor      = item{itemIdentifier, "for"} +	tQuote    = item{itemString, `"abc \n\t\" "`} +	raw       = "`" + `abc\n\t\" ` + "`" +	tRawQuote = item{itemRawString, raw} +) + +var lexTests = []lexTest{ +	{"empty", "", []item{tEOF}}, +	{"spaces", " \t\n", []item{{itemText, " \t\n"}, tEOF}}, +	{"text", `now is the time`, []item{{itemText, "now is the time"}, tEOF}}, +	{"text with comment", "hello-{{/* this is a comment */}}-world", []item{ +		{itemText, "hello-"}, +		{itemText, "-world"}, +		tEOF, +	}}, +	{"punctuation", "{{,@%}}", []item{ +		tLeft, +		{itemChar, ","}, +		{itemChar, "@"}, +		{itemChar, "%"}, +		tRight, +		tEOF, +	}}, +	{"empty action", `{{}}`, []item{tLeft, tRight, tEOF}}, +	{"for", `{{for }}`, []item{tLeft, tFor, tRight, tEOF}}, +	{"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}}, +	{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}}, +	{"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{ +		tLeft, +		{itemNumber, "1"}, +		{itemNumber, "02"}, +		{itemNumber, "0x14"}, +		{itemNumber, "-7.2i"}, +		{itemNumber, "1e3"}, +		{itemNumber, "+1.2e-4"}, +		{itemNumber, "4.2i"}, +		{itemComplex, "1+2i"}, +		tRight, +		tEOF, +	}}, +	{"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{ +		tLeft, +		{itemCharConstant, `'a'`}, +		{itemCharConstant, `'\n'`}, +		{itemCharConstant, `'\''`}, +		{itemCharConstant, `'\\'`}, +		{itemCharConstant, `'\u00FF'`}, +		{itemCharConstant, `'\xFF'`}, +		{itemCharConstant, `'本'`}, +		tRight, +		tEOF, +	}}, +	{"bools", "{{true false}}", []item{ +		tLeft, +		{itemBool, "true"}, +		{itemBool, "false"}, +		tRight, +		tEOF, +	}}, +	{"dot", "{{.}}", []item{ +		tLeft, +		{itemDot, "."}, +		tRight, +		tEOF, +	}}, +	{"dots", "{{.x . .2 .x.y}}", []item{ +		tLeft, +		{itemField, ".x"}, +		{itemDot, "."}, +		{itemNumber, ".2"}, +		{itemField, ".x.y"}, +		tRight, +		tEOF, +	}}, +	{"keywords", "{{range if else end with}}", []item{ +		tLeft, +		{itemRange, "range"}, +		{itemIf, "if"}, +		{itemElse, "else"}, +		{itemEnd, "end"}, +		{itemWith, "with"}, +		tRight, +		tEOF, +	}}, +	{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{ +		tLeft, +		{itemVariable, "$c"}, +		{itemColonEquals, ":="}, +		{itemIdentifier, "printf"}, +		{itemVariable, "$"}, +		{itemVariable, "$hello"}, +		{itemVariable, "$23"}, +		{itemVariable, "$"}, +		{itemVariable, "$var.Field"}, +		{itemField, ".Method"}, +		tRight, +		tEOF, +	}}, +	{"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{ +		{itemText, "intro "}, +		tLeft, +		{itemIdentifier, "echo"}, +		{itemIdentifier, "hi"}, +		{itemNumber, "1.2"}, +		tPipe, +		{itemIdentifier, "noargs"}, +		tPipe, +		{itemIdentifier, "args"}, +		{itemNumber, "1"}, +		{itemString, `"hi"`}, +		tRight, +		{itemText, " outro"}, +		tEOF, +	}}, +	{"declaration", "{{$v := 3}}", []item{ +		tLeft, +		{itemVariable, "$v"}, +		{itemColonEquals, ":="}, +		{itemNumber, "3"}, +		tRight, +		tEOF, +	}}, +	{"2 declarations", "{{$v , $w := 3}}", []item{ +		tLeft, +		{itemVariable, "$v"}, +		{itemChar, ","}, +		{itemVariable, "$w"}, +		{itemColonEquals, ":="}, +		{itemNumber, "3"}, +		tRight, +		tEOF, +	}}, +	// errors +	{"badchar", "#{{\x01}}", []item{ +		{itemText, "#"}, +		tLeft, +		{itemError, "unrecognized character in action: U+0001"}, +	}}, +	{"unclosed action", "{{\n}}", []item{ +		tLeft, +		{itemError, "unclosed action"}, +	}}, +	{"EOF in action", "{{range", []item{ +		tLeft, +		tRange, +		{itemError, "unclosed action"}, +	}}, +	{"unclosed quote", "{{\"\n\"}}", []item{ +		tLeft, +		{itemError, "unterminated quoted string"}, +	}}, +	{"unclosed raw quote", "{{`xx\n`}}", []item{ +		tLeft, +		{itemError, "unterminated raw quoted string"}, +	}}, +	{"unclosed char constant", "{{'\n}}", []item{ +		tLeft, +		{itemError, "unterminated character constant"}, +	}}, +	{"bad number", "{{3k}}", []item{ +		tLeft, +		{itemError, `bad number syntax: "3k"`}, +	}}, +} + +// collect gathers the emitted items into a slice. +func collect(t *lexTest) (items []item) { +	l := lex(t.name, t.input) +	for { +		item := l.nextItem() +		items = append(items, item) +		if item.typ == itemEOF || item.typ == itemError { +			break +		} +	} +	return +} + +func TestLex(t *testing.T) { +	for _, test := range lexTests { +		items := collect(&test) +		if !reflect.DeepEqual(items, test.items) { +			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) +		} +	} +} diff --git a/src/pkg/template/parse/node.go b/src/pkg/template/parse/node.go new file mode 100644 index 000000000..a917418dc --- /dev/null +++ b/src/pkg/template/parse/node.go @@ -0,0 +1,469 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Parse nodes. + +package parse + +import ( +	"bytes" +	"fmt" +	"os" +	"strconv" +	"strings" +) + +// A node is an element in the parse tree. The interface is trivial. +type Node interface { +	Type() NodeType +	String() string +} + +// NodeType identifies the type of a parse tree node. +type NodeType int + +// 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 { +	return t +} + +const ( +	NodeText       NodeType = iota // Plain text. +	NodeAction                     // A simple action such as field evaluation. +	NodeBool                       // A boolean constant. +	NodeCommand                    // An element of a pipeline. +	NodeDot                        // The cursor, dot. +	NodeElse                       // An else action. +	NodeEnd                        // An end action. +	NodeField                      // A field or method name. +	NodeIdentifier                 // An identifier; always a function name. +	NodeIf                         // An if action. +	NodeList                       // A list of Nodes. +	NodeNumber                     // A numerical constant. +	NodePipe                       // A pipeline of commands. +	NodeRange                      // A range action. +	NodeString                     // A string constant. +	NodeTemplate                   // A template invocation action. +	NodeVariable                   // A $ variable. +	NodeWith                       // A with action. +) + +// Nodes. + +// ListNode holds a sequence of nodes. +type ListNode struct { +	NodeType +	Nodes []Node // The element nodes in lexical order. +} + +func newList() *ListNode { +	return &ListNode{NodeType: NodeList} +} + +func (l *ListNode) append(n Node) { +	l.Nodes = append(l.Nodes, n) +} + +func (l *ListNode) String() string { +	b := new(bytes.Buffer) +	fmt.Fprint(b, "[") +	for _, n := range l.Nodes { +		fmt.Fprint(b, n) +	} +	fmt.Fprint(b, "]") +	return b.String() +} + +// TextNode holds plain text. +type TextNode struct { +	NodeType +	Text []byte // The text; may span newlines. +} + +func newText(text string) *TextNode { +	return &TextNode{NodeType: NodeText, Text: []byte(text)} +} + +func (t *TextNode) String() string { +	return fmt.Sprintf("(text: %q)", t.Text) +} + +// PipeNode holds a pipeline with optional declaration +type PipeNode struct { +	NodeType +	Line int             // The line number in the input. +	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 (p *PipeNode) append(command *CommandNode) { +	p.Cmds = append(p.Cmds, command) +} + +func (p *PipeNode) String() string { +	if p.Decl != nil { +		return fmt.Sprintf("%v := %v", p.Decl, p.Cmds) +	} +	return fmt.Sprintf("%v", p.Cmds) +} + +// ActionNode holds an action (something bounded by delimiters). +// Control actions have their own nodes; ActionNode represents simple +// ones such as field evaluations. +type ActionNode struct { +	NodeType +	Line int       // The line number in the input. +	Pipe *PipeNode // The pipeline in the action. +} + +func newAction(line int, pipe *PipeNode) *ActionNode { +	return &ActionNode{NodeType: NodeAction, Line: line, Pipe: pipe} +} + +func (a *ActionNode) String() string { +	return fmt.Sprintf("(action: %v)", a.Pipe) +} + +// CommandNode holds a command (a pipeline inside an evaluating action). +type CommandNode struct { +	NodeType +	Args []Node // Arguments in lexical order: Identifier, field, or constant. +} + +func newCommand() *CommandNode { +	return &CommandNode{NodeType: NodeCommand} +} + +func (c *CommandNode) append(arg Node) { +	c.Args = append(c.Args, arg) +} + +func (c *CommandNode) String() string { +	return fmt.Sprintf("(command: %v)", c.Args) +} + +// IdentifierNode holds an identifier. +type IdentifierNode struct { +	NodeType +	Ident string // The identifier's name. +} + +// NewIdentifier returns a new IdentifierNode with the given identifier name. +func NewIdentifier(ident string) *IdentifierNode { +	return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident} +} + +func (i *IdentifierNode) String() string { +	return fmt.Sprintf("I=%s", i.Ident) +} + +// VariableNode holds a list of variable names. The dollar sign is +// part of the name. +type VariableNode struct { +	NodeType +	Ident []string // Variable names in lexical order. +} + +func newVariable(ident string) *VariableNode { +	return &VariableNode{NodeType: NodeVariable, Ident: strings.Split(ident, ".")} +} + +func (v *VariableNode) String() string { +	return fmt.Sprintf("V=%s", v.Ident) +} + +// DotNode holds the special identifier '.'. It is represented by a nil pointer. +type DotNode bool + +func newDot() *DotNode { +	return nil +} + +func (d *DotNode) Type() 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 // 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 (f *FieldNode) String() string { +	return fmt.Sprintf("F=%s", f.Ident) +} + +// BoolNode holds a boolean constant. +type BoolNode struct { +	NodeType +	True bool // The value of the boolean constant. +} + +func newBool(true bool) *BoolNode { +	return &BoolNode{NodeType: NodeBool, True: true} +} + +func (b *BoolNode) String() string { +	return fmt.Sprintf("B=%t", b.True) +} + +// NumberNode holds a number: signed or unsigned integer, float, or complex. +// The value is parsed and stored under all the types that can represent the value. +// This simulates in a small amount of code the behavior of Go's ideal constants. +type NumberNode struct { +	NodeType +	IsInt      bool       // Number has an integral value. +	IsUint     bool       // Number has an unsigned integral value. +	IsFloat    bool       // Number has a floating-point value. +	IsComplex  bool       // Number is complex. +	Int64      int64      // The signed integer value. +	Uint64     uint64     // The unsigned integer value. +	Float64    float64    // The floating-point value. +	Complex128 complex128 // The complex value. +	Text       string     // The original textual representation from the input. +} + +func newNumber(text string, typ itemType) (*NumberNode, os.Error) { +	n := &NumberNode{NodeType: NodeNumber, Text: text} +	switch typ { +	case itemCharConstant: +		rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0]) +		if err != nil { +			return nil, err +		} +		if tail != "'" { +			return nil, fmt.Errorf("malformed character constant: %s", text) +		} +		n.Int64 = int64(rune) +		n.IsInt = true +		n.Uint64 = uint64(rune) +		n.IsUint = true +		n.Float64 = float64(rune) // odd but those are the rules. +		n.IsFloat = true +		return n, nil +	case itemComplex: +		// fmt.Sscan can parse the pair, so let it do the work. +		if _, err := fmt.Sscan(text, &n.Complex128); err != nil { +			return nil, err +		} +		n.IsComplex = true +		n.simplifyComplex() +		return n, nil +	} +	// Imaginary constants can only be complex unless they are zero. +	if len(text) > 0 && text[len(text)-1] == 'i' { +		f, err := strconv.Atof64(text[:len(text)-1]) +		if err == nil { +			n.IsComplex = true +			n.Complex128 = complex(0, f) +			n.simplifyComplex() +			return n, nil +		} +	} +	// Do integer test first so we get 0x123 etc. +	u, err := strconv.Btoui64(text, 0) // will fail for -0; fixed below. +	if err == nil { +		n.IsUint = true +		n.Uint64 = u +	} +	i, err := strconv.Btoi64(text, 0) +	if err == nil { +		n.IsInt = true +		n.Int64 = i +		if i == 0 { +			n.IsUint = true // in case of -0. +			n.Uint64 = u +		} +	} +	// If an integer extraction succeeded, promote the float. +	if n.IsInt { +		n.IsFloat = true +		n.Float64 = float64(n.Int64) +	} else if n.IsUint { +		n.IsFloat = true +		n.Float64 = float64(n.Uint64) +	} else { +		f, err := strconv.Atof64(text) +		if err == nil { +			n.IsFloat = true +			n.Float64 = f +			// If a floating-point extraction succeeded, extract the int if needed. +			if !n.IsInt && float64(int64(f)) == f { +				n.IsInt = true +				n.Int64 = int64(f) +			} +			if !n.IsUint && float64(uint64(f)) == f { +				n.IsUint = true +				n.Uint64 = uint64(f) +			} +		} +	} +	if !n.IsInt && !n.IsUint && !n.IsFloat { +		return nil, fmt.Errorf("illegal number syntax: %q", text) +	} +	return n, nil +} + +// simplifyComplex pulls out any other types that are represented by the complex number. +// These all require that the imaginary part be zero. +func (n *NumberNode) simplifyComplex() { +	n.IsFloat = imag(n.Complex128) == 0 +	if n.IsFloat { +		n.Float64 = real(n.Complex128) +		n.IsInt = float64(int64(n.Float64)) == n.Float64 +		if n.IsInt { +			n.Int64 = int64(n.Float64) +		} +		n.IsUint = float64(uint64(n.Float64)) == n.Float64 +		if n.IsUint { +			n.Uint64 = uint64(n.Float64) +		} +	} +} + +func (n *NumberNode) String() string { +	return fmt.Sprintf("N=%s", n.Text) +} + +// StringNode holds a string constant. The value has been "unquoted". +type StringNode struct { +	NodeType +	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 (s *StringNode) String() string { +	return fmt.Sprintf("S=%#q", s.Text) +} + +// EndNode represents an {{end}} action. It is represented by a nil pointer. +type EndNode bool + +func newEnd() *EndNode { +	return nil +} + +func (e *EndNode) Type() NodeType { +	return NodeEnd +} + +func (e *EndNode) String() string { +	return "{{end}}" +} + +// ElseNode represents an {{else}} action. +type ElseNode struct { +	NodeType +	Line int // The line number in the input. +} + +func newElse(line int) *ElseNode { +	return &ElseNode{NodeType: NodeElse, Line: line} +} + +func (e *ElseNode) Type() NodeType { +	return NodeElse +} + +func (e *ElseNode) String() string { +	return "{{else}}" +} + +// IfNode represents an {{if}} action and its commands. +type IfNode struct { +	NodeType +	Line     int       // The line number in the input. +	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). +} + +func newIf(line int, pipe *PipeNode, list, elseList *ListNode) *IfNode { +	return &IfNode{NodeType: NodeIf, Line: line, Pipe: pipe, List: list, ElseList: elseList} +} + +func (i *IfNode) String() string { +	if i.ElseList != nil { +		return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.Pipe, i.List, i.ElseList) +	} +	return fmt.Sprintf("({{if %s}} %s)", i.Pipe, i.List) +} + +// RangeNode represents a {{range}} action and its commands. +type RangeNode struct { +	NodeType +	Line     int       // The line number in the input. +	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). +} + +func newRange(line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode { +	return &RangeNode{NodeType: NodeRange, Line: line, Pipe: pipe, List: list, ElseList: elseList} +} + +func (r *RangeNode) String() string { +	if r.ElseList != nil { +		return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.Pipe, r.List, r.ElseList) +	} +	return fmt.Sprintf("({{range %s}} %s)", r.Pipe, r.List) +} + +// TemplateNode represents a {{template}} action. +type TemplateNode struct { +	NodeType +	Line int       // The line number in the input. +	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 (t *TemplateNode) String() string { +	if t.Pipe == nil { +		return fmt.Sprintf("{{template %q}}", t.Name) +	} +	return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe) +} + +// WithNode represents a {{with}} action and its commands. +type WithNode struct { +	NodeType +	Line     int       // The line number in the input. +	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). +} + +func newWith(line int, pipe *PipeNode, list, elseList *ListNode) *WithNode { +	return &WithNode{NodeType: NodeWith, Line: line, Pipe: pipe, List: list, ElseList: elseList} +} + +func (w *WithNode) String() string { +	if w.ElseList != nil { +		return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.Pipe, w.List, w.ElseList) +	} +	return fmt.Sprintf("({{with %s}} %s)", w.Pipe, w.List) +} diff --git a/src/pkg/template/parse/parse.go b/src/pkg/template/parse/parse.go new file mode 100644 index 000000000..9a411a3f3 --- /dev/null +++ b/src/pkg/template/parse/parse.go @@ -0,0 +1,436 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package parse builds parse trees for templates.  The grammar is defined +// in the documents for the template package. +package parse + +import ( +	"fmt" +	"os" +	"runtime" +	"strconv" +	"unicode" +) + +// Tree is the representation of a parsed template. +type Tree struct { +	Name string    // Name is the name of the template. +	Root *ListNode // Root is the top-level root of the parse tree. +	// Parsing only; cleared after parse. +	funcs     []map[string]interface{} +	lex       *lexer +	token     [2]item // two-token lookahead for parser. +	peekCount int +	vars      []string // variables defined at the moment. +} + +// next returns the next token. +func (t *Tree) next() item { +	if t.peekCount > 0 { +		t.peekCount-- +	} else { +		t.token[0] = t.lex.nextItem() +	} +	return t.token[t.peekCount] +} + +// backup backs the input stream up one token. +func (t *Tree) backup() { +	t.peekCount++ +} + +// backup2 backs the input stream up two tokens +func (t *Tree) backup2(t1 item) { +	t.token[1] = t1 +	t.peekCount = 2 +} + +// peek returns but does not consume the next token. +func (t *Tree) peek() item { +	if t.peekCount > 0 { +		return t.token[t.peekCount-1] +	} +	t.peekCount = 1 +	t.token[0] = t.lex.nextItem() +	return t.token[0] +} + +// Parsing. + +// New allocates a new template with the given name. +func New(name string, funcs ...map[string]interface{}) *Tree { +	return &Tree{ +		Name:  name, +		funcs: funcs, +	} +} + +// errorf formats the error and terminates processing. +func (t *Tree) errorf(format string, args ...interface{}) { +	t.Root = nil +	format = fmt.Sprintf("template: %s:%d: %s", t.Name, t.lex.lineNumber(), format) +	panic(fmt.Errorf(format, args...)) +} + +// error terminates processing. +func (t *Tree) error(err os.Error) { +	t.errorf("%s", err) +} + +// expect consumes the next token and guarantees it has the required type. +func (t *Tree) expect(expected itemType, context string) item { +	token := t.next() +	if token.typ != expected { +		t.errorf("expected %s in %s; got %s", expected, context, token) +	} +	return token +} + +// unexpected complains about the token and terminates processing. +func (t *Tree) unexpected(token item, context string) { +	t.errorf("unexpected %s in %s", token, context) +} + +// recover is the handler that turns panics into returns from the top level of Parse. +func (t *Tree) recover(errp *os.Error) { +	e := recover() +	if e != nil { +		if _, ok := e.(runtime.Error); ok { +			panic(e) +		} +		if t != nil { +			t.stopParse() +		} +		*errp = e.(os.Error) +	} +	return +} + +// startParse starts the template parsing from the lexer. +func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) { +	t.Root = nil +	t.lex = lex +	t.vars = []string{"$"} +	t.funcs = funcs +} + +// stopParse terminates parsing. +func (t *Tree) stopParse() { +	t.lex = nil +	t.vars = nil +	t.funcs = nil +} + +// atEOF returns true if, possibly after spaces, we're at EOF. +func (t *Tree) atEOF() bool { +	for { +		token := t.peek() +		switch token.typ { +		case itemEOF: +			return true +		case itemText: +			for _, r := range token.val { +				if !unicode.IsSpace(r) { +					return false +				} +			} +			t.next() // skip spaces. +			continue +		} +		break +	} +	return false +} + +// Parse parses the template definition string to construct an internal +// representation of the template for execution. +func (t *Tree) Parse(s string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) { +	defer t.recover(&err) +	t.startParse(funcs, lex(t.Name, s)) +	t.parse(true) +	t.stopParse() +	return t, nil +} + +// parse is the helper for Parse. +// It triggers an error if we expect EOF but don't reach it. +func (t *Tree) parse(toEOF bool) (next Node) { +	t.Root, next = t.itemList(true) +	if toEOF && next != nil { +		t.errorf("unexpected %s", next) +	} +	return next +} + +// itemList: +//	textOrAction* +// Terminates at EOF and at {{end}} or {{else}}, which is returned separately. +// The toEOF flag tells whether we expect to reach EOF. +func (t *Tree) itemList(toEOF bool) (list *ListNode, next Node) { +	list = newList() +	for t.peek().typ != itemEOF { +		n := t.textOrAction() +		switch n.Type() { +		case NodeEnd, NodeElse: +			return list, n +		} +		list.append(n) +	} +	if !toEOF { +		t.unexpected(t.next(), "input") +	} +	return list, nil +} + +// textOrAction: +//	text | action +func (t *Tree) textOrAction() Node { +	switch token := t.next(); token.typ { +	case itemText: +		return newText(token.val) +	case itemLeftDelim: +		return t.action() +	default: +		t.unexpected(token, "input") +	} +	return nil +} + +// Action: +//	control +//	command ("|" command)* +// Left delim is past. Now get actions. +// First word could be a keyword such as range. +func (t *Tree) action() (n Node) { +	switch token := t.next(); token.typ { +	case itemElse: +		return t.elseControl() +	case itemEnd: +		return t.endControl() +	case itemIf: +		return t.ifControl() +	case itemRange: +		return t.rangeControl() +	case itemTemplate: +		return t.templateControl() +	case itemWith: +		return t.withControl() +	} +	t.backup() +	// Do not pop variables; they persist until "end". +	return newAction(t.lex.lineNumber(), t.pipeline("command")) +} + +// Pipeline: +//	field or command +//	pipeline "|" pipeline +func (t *Tree) pipeline(context string) (pipe *PipeNode) { +	var decl []*VariableNode +	// Are there declarations? +	for { +		if v := t.peek(); v.typ == itemVariable { +			t.next() +			if next := t.peek(); next.typ == itemColonEquals || next.typ == itemChar { +				t.next() +				variable := newVariable(v.val) +				if len(variable.Ident) != 1 { +					t.errorf("illegal variable in declaration: %s", v.val) +				} +				decl = append(decl, variable) +				t.vars = append(t.vars, v.val) +				if next.typ == itemChar && next.val == "," { +					if context == "range" && len(decl) < 2 { +						continue +					} +					t.errorf("too many declarations in %s", context) +				} +			} else { +				t.backup2(v) +			} +		} +		break +	} +	pipe = newPipeline(t.lex.lineNumber(), decl) +	for { +		switch token := t.next(); token.typ { +		case itemRightDelim: +			if len(pipe.Cmds) == 0 { +				t.errorf("missing value for %s", context) +			} +			return +		case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, +			itemVariable, itemNumber, itemRawString, itemString: +			t.backup() +			pipe.append(t.command()) +		default: +			t.unexpected(token, context) +		} +	} +	return +} + +func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, elseList *ListNode) { +	lineNum = t.lex.lineNumber() +	defer t.popVars(len(t.vars)) +	pipe = t.pipeline(context) +	var next Node +	list, next = t.itemList(false) +	switch next.Type() { +	case NodeEnd: //done +	case NodeElse: +		elseList, next = t.itemList(false) +		if next.Type() != NodeEnd { +			t.errorf("expected end; found %s", next) +		} +		elseList = elseList +	} +	return lineNum, pipe, list, elseList +} + +// If: +//	{{if pipeline}} itemList {{end}} +//	{{if pipeline}} itemList {{else}} itemList {{end}} +// If keyword is past. +func (t *Tree) ifControl() Node { +	return newIf(t.parseControl("if")) +} + +// Range: +//	{{range pipeline}} itemList {{end}} +//	{{range pipeline}} itemList {{else}} itemList {{end}} +// Range keyword is past. +func (t *Tree) rangeControl() Node { +	return newRange(t.parseControl("range")) +} + +// With: +//	{{with pipeline}} itemList {{end}} +//	{{with pipeline}} itemList {{else}} itemList {{end}} +// If keyword is past. +func (t *Tree) withControl() Node { +	return newWith(t.parseControl("with")) +} + +// End: +//	{{end}} +// End keyword is past. +func (t *Tree) endControl() Node { +	t.expect(itemRightDelim, "end") +	return newEnd() +} + +// Else: +//	{{else}} +// Else keyword is past. +func (t *Tree) elseControl() Node { +	t.expect(itemRightDelim, "else") +	return newElse(t.lex.lineNumber()) +} + +// Template: +//	{{template stringValue pipeline}} +// Template keyword is past.  The name must be something that can evaluate +// to a string. +func (t *Tree) templateControl() Node { +	var name string +	switch token := t.next(); token.typ { +	case itemString, itemRawString: +		s, err := strconv.Unquote(token.val) +		if err != nil { +			t.error(err) +		} +		name = s +	default: +		t.unexpected(token, "template invocation") +	} +	var pipe *PipeNode +	if t.next().typ != itemRightDelim { +		t.backup() +		// Do not pop variables; they persist until "end". +		pipe = t.pipeline("template") +	} +	return newTemplate(t.lex.lineNumber(), name, pipe) +} + +// command: +// space-separated arguments up to a pipeline character or right delimiter. +// we consume the pipe character but leave the right delim to terminate the action. +func (t *Tree) command() *CommandNode { +	cmd := newCommand() +Loop: +	for { +		switch token := t.next(); token.typ { +		case itemRightDelim: +			t.backup() +			break Loop +		case itemPipe: +			break Loop +		case itemError: +			t.errorf("%s", token.val) +		case itemIdentifier: +			if !t.hasFunction(token.val) { +				t.errorf("function %q not defined", token.val) +			} +			cmd.append(NewIdentifier(token.val)) +		case itemDot: +			cmd.append(newDot()) +		case itemVariable: +			cmd.append(t.useVar(token.val)) +		case itemField: +			cmd.append(newField(token.val)) +		case itemBool: +			cmd.append(newBool(token.val == "true")) +		case itemCharConstant, itemComplex, itemNumber: +			number, err := newNumber(token.val, token.typ) +			if err != nil { +				t.error(err) +			} +			cmd.append(number) +		case itemString, itemRawString: +			s, err := strconv.Unquote(token.val) +			if err != nil { +				t.error(err) +			} +			cmd.append(newString(token.val, s)) +		default: +			t.unexpected(token, "command") +		} +	} +	if len(cmd.Args) == 0 { +		t.errorf("empty command") +	} +	return cmd +} + +// hasFunction reports if a function name exists in the Tree's maps. +func (t *Tree) hasFunction(name string) bool { +	for _, funcMap := range t.funcs { +		if funcMap == nil { +			continue +		} +		if funcMap[name] != nil { +			return true +		} +	} +	return false +} + +// popVars trims the variable list to the specified length +func (t *Tree) popVars(n int) { +	t.vars = t.vars[:n] +} + +// 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) +	for _, varName := range t.vars { +		if varName == v.Ident[0] { +			return v +		} +	} +	t.errorf("undefined variable %q", v.Ident[0]) +	return nil +} diff --git a/src/pkg/template/parse/parse_test.go b/src/pkg/template/parse/parse_test.go new file mode 100644 index 000000000..1928c319d --- /dev/null +++ b/src/pkg/template/parse/parse_test.go @@ -0,0 +1,259 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parse + +import ( +	"flag" +	"fmt" +	"testing" +) + +var debug = flag.Bool("debug", false, "show the errors produced by the tests") + +type numberTest struct { +	text      string +	isInt     bool +	isUint    bool +	isFloat   bool +	isComplex bool +	int64 +	uint64 +	float64 +	complex128 +} + +var numberTests = []numberTest{ +	// basics +	{"0", true, true, true, false, 0, 0, 0, 0}, +	{"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint. +	{"73", true, true, true, false, 73, 73, 73, 0}, +	{"073", true, true, true, false, 073, 073, 073, 0}, +	{"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0}, +	{"-73", true, false, true, false, -73, 0, -73, 0}, +	{"+73", true, false, true, false, 73, 0, 73, 0}, +	{"100", true, true, true, false, 100, 100, 100, 0}, +	{"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0}, +	{"-1e9", true, false, true, false, -1e9, 0, -1e9, 0}, +	{"-1.2", false, false, true, false, 0, 0, -1.2, 0}, +	{"1e19", false, true, true, false, 0, 1e19, 1e19, 0}, +	{"-1e19", false, false, true, false, 0, 0, -1e19, 0}, +	{"4i", false, false, false, true, 0, 0, 0, 4i}, +	{"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i}, +	{"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal! +	// complex with 0 imaginary are float (and maybe integer) +	{"0i", true, true, true, true, 0, 0, 0, 0}, +	{"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2}, +	{"-12+0i", true, false, true, true, -12, 0, -12, -12}, +	{"13+0i", true, true, true, true, 13, 13, 13, 13}, +	// funny bases +	{"0123", true, true, true, false, 0123, 0123, 0123, 0}, +	{"-0x0", true, true, true, false, 0, 0, 0, 0}, +	{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0}, +	// character constants +	{`'a'`, true, true, true, false, 'a', 'a', 'a', 0}, +	{`'\n'`, true, true, true, false, '\n', '\n', '\n', 0}, +	{`'\\'`, true, true, true, false, '\\', '\\', '\\', 0}, +	{`'\''`, true, true, true, false, '\'', '\'', '\'', 0}, +	{`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0}, +	{`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, +	{`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, +	{`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0}, +	// some broken syntax +	{text: "+-2"}, +	{text: "0x123."}, +	{text: "1e."}, +	{text: "0xi."}, +	{text: "1+2."}, +	{text: "'x"}, +	{text: "'xx'"}, +} + +func TestNumberParse(t *testing.T) { +	for _, test := range numberTests { +		// If fmt.Sscan thinks it's complex, it's complex.  We can't trust the output +		// because imaginary comes out as a number. +		var c complex128 +		typ := itemNumber +		if test.text[0] == '\'' { +			typ = itemCharConstant +		} else { +			_, err := fmt.Sscan(test.text, &c) +			if err == nil { +				typ = itemComplex +			} +		} +		n, err := newNumber(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) +			continue +		} +		if !ok && err == nil { +			t.Errorf("expected error for %q", test.text) +			continue +		} +		if !ok { +			if *debug { +				fmt.Printf("%s\n\t%s\n", test.text, err) +			} +			continue +		} +		if n.IsComplex != test.isComplex { +			t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex) +		} +		if test.isInt { +			if !n.IsInt { +				t.Errorf("expected integer for %q", test.text) +			} +			if n.Int64 != test.int64 { +				t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64) +			} +		} else if n.IsInt { +			t.Errorf("did not expect integer for %q", test.text) +		} +		if test.isUint { +			if !n.IsUint { +				t.Errorf("expected unsigned integer for %q", test.text) +			} +			if n.Uint64 != test.uint64 { +				t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64) +			} +		} else if n.IsUint { +			t.Errorf("did not expect unsigned integer for %q", test.text) +		} +		if test.isFloat { +			if !n.IsFloat { +				t.Errorf("expected float for %q", test.text) +			} +			if n.Float64 != test.float64 { +				t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64) +			} +		} else if n.IsFloat { +			t.Errorf("did not expect float for %q", test.text) +		} +		if test.isComplex { +			if !n.IsComplex { +				t.Errorf("expected complex for %q", test.text) +			} +			if n.Complex128 != test.complex128 { +				t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128) +			} +		} else if n.IsComplex { +			t.Errorf("did not expect complex for %q", test.text) +		} +	} +} + +type parseTest struct { +	name   string +	input  string +	ok     bool +	result string +} + +const ( +	noError  = true +	hasError = false +) + +var parseTests = []parseTest{ +	{"empty", "", noError, +		`[]`}, +	{"comment", "{{/*\n\n\n*/}}", noError, +		`[]`}, +	{"spaces", " \t\n", noError, +		`[(text: " \t\n")]`}, +	{"text", "some text", noError, +		`[(text: "some text")]`}, +	{"emptyAction", "{{}}", hasError, +		`[(action: [])]`}, +	{"field", "{{.X}}", noError, +		`[(action: [(command: [F=[X]])])]`}, +	{"simple command", "{{printf}}", noError, +		`[(action: [(command: [I=printf])])]`}, +	{"$ invocation", "{{$}}", noError, +		"[(action: [(command: [V=[$]])])]"}, +	{"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError, +		"[({{with [V=[$x]] := [(command: [N=3])]}} [(action: [(command: [V=[$x] N=23])])])]"}, +	{"variable with fields", "{{$.I}}", noError, +		"[(action: [(command: [V=[$ I]])])]"}, +	{"multi-word command", "{{printf `%d` 23}}", noError, +		"[(action: [(command: [I=printf S=`%d` N=23])])]"}, +	{"pipeline", "{{.X|.Y}}", noError, +		`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`}, +	{"pipeline with decl", "{{$x := .X|.Y}}", noError, +		`[(action: [V=[$x]] := [(command: [F=[X]]) (command: [F=[Y]])])]`}, +	{"declaration", "{{.X|.Y}}", noError, +		`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`}, +	{"simple if", "{{if .X}}hello{{end}}", noError, +		`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`}, +	{"if with else", "{{if .X}}true{{else}}false{{end}}", noError, +		`[({{if [(command: [F=[X]])]}} [(text: "true")] {{else}} [(text: "false")])]`}, +	{"simple range", "{{range .X}}hello{{end}}", noError, +		`[({{range [(command: [F=[X]])]}} [(text: "hello")])]`}, +	{"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError, +		`[({{range [(command: [F=[X Y Z]])]}} [(text: "hello")])]`}, +	{"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError, +		`[({{range [(command: [F=[X]])]}} [(text: "hello")({{range [(command: [F=[Y]])]}} [(text: "goodbye")])])]`}, +	{"range with else", "{{range .X}}true{{else}}false{{end}}", noError, +		`[({{range [(command: [F=[X]])]}} [(text: "true")] {{else}} [(text: "false")])]`}, +	{"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError, +		`[({{range [(command: [F=[X]]) (command: [F=[M]])]}} [(text: "true")] {{else}} [(text: "false")])]`}, +	{"range []int", "{{range .SI}}{{.}}{{end}}", noError, +		`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`}, +	{"constants", "{{range .SI 1 -3.2i true false 'a'}}{{end}}", noError, +		`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false N='a'])]}} [])]`}, +	{"template", "{{template `x`}}", noError, +		`[{{template "x"}}]`}, +	{"template with arg", "{{template `x` .Y}}", noError, +		`[{{template "x" [(command: [F=[Y]])]}}]`}, +	{"with", "{{with .X}}hello{{end}}", noError, +		`[({{with [(command: [F=[X]])]}} [(text: "hello")])]`}, +	{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError, +		`[({{with [(command: [F=[X]])]}} [(text: "hello")] {{else}} [(text: "goodbye")])]`}, +	// Errors. +	{"unclosed action", "hello{{range", hasError, ""}, +	{"unmatched end", "{{end}}", hasError, ""}, +	{"missing end", "hello{{range .x}}", hasError, ""}, +	{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""}, +	{"undefined function", "hello{{undefined}}", hasError, ""}, +	{"undefined variable", "{{$x}}", hasError, ""}, +	{"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""}, +	{"variable undefined in template", "{{template $v}}", hasError, ""}, +	{"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""}, +	{"template with field ref", "{{template .X}}", hasError, ""}, +	{"template with var", "{{template $v}}", hasError, ""}, +	{"invalid punctuation", "{{printf 3, 4}}", hasError, ""}, +	{"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""}, +	{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""}, +} + +var builtins = map[string]interface{}{ +	"printf": fmt.Sprintf, +} + +func TestParse(t *testing.T) { +	for _, test := range parseTests { +		tmpl, err := New(test.name).Parse(test.input, builtins) +		switch { +		case err == nil && !test.ok: +			t.Errorf("%q: expected error; got none", test.name) +			continue +		case err != nil && test.ok: +			t.Errorf("%q: unexpected error: %v", test.name, err) +			continue +		case err != nil && !test.ok: +			// expected error, got one +			if *debug { +				fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) +			} +			continue +		} +		result := tmpl.Root.String() +		if result != test.result { +			t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result) +		} +	} +} diff --git a/src/pkg/template/parse/set.go b/src/pkg/template/parse/set.go new file mode 100644 index 000000000..4820da925 --- /dev/null +++ b/src/pkg/template/parse/set.go @@ -0,0 +1,50 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parse + +import ( +	"fmt" +	"os" +	"strconv" +) + +// Set returns a slice of Trees created by parsing the template set +// definition in the argument string. If an error is encountered, +// parsing stops and an empty slice is returned with the error. +func Set(text string, funcs ...map[string]interface{}) (tree map[string]*Tree, err os.Error) { +	tree = make(map[string]*Tree) +	defer (*Tree)(nil).recover(&err) +	lex := lex("set", text) +	const context = "define clause" +	for { +		t := New("set") // name will be updated once we know it. +		t.startParse(funcs, lex) +		// Expect EOF or "{{ define name }}". +		if t.atEOF() { +			break +		} +		t.expect(itemLeftDelim, context) +		t.expect(itemDefine, context) +		name := t.expect(itemString, context) +		t.Name, err = strconv.Unquote(name.val) +		if err != nil { +			t.error(err) +		} +		t.expect(itemRightDelim, context) +		end := t.parse(false) +		if end == nil { +			t.errorf("unexpected EOF in %s", context) +		} +		if end.Type() != NodeEnd { +			t.errorf("unexpected %s in %s", end, context) +		} +		t.stopParse() +		if _, present := tree[t.Name]; present { +			return nil, fmt.Errorf("template: %q multiply defined", name) +		} +		tree[t.Name] = t +	} +	return +} | 
