summaryrefslogtreecommitdiff
path: root/src/pkg/exp/template/lex.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/exp/template/lex.go')
-rw-r--r--src/pkg/exp/template/lex.go180
1 files changed, 119 insertions, 61 deletions
diff --git a/src/pkg/exp/template/lex.go b/src/pkg/exp/template/lex.go
index 826d3eb88..d78152979 100644
--- a/src/pkg/exp/template/lex.go
+++ b/src/pkg/exp/template/lex.go
@@ -18,58 +18,71 @@ type item struct {
}
func (i item) String() string {
- switch i.typ {
- case itemEOF:
+ switch {
+ case i.typ == itemEOF:
return "EOF"
- case itemError:
+ case i.typ == itemError:
return i.val
- }
- if len(i.val) > 10 {
+ 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 item.
+// itemType identifies the type of lex items.
type itemType int
const (
- itemError itemType = iota // error occurred; value is text of error
- itemDot // the cursor, spelled '.'.
+ itemError itemType = iota // error occurred; value is text of error
+ itemBool // boolean constant
+ itemComplex // complex constant (1+2i); imaginary is just a number
itemEOF
- itemElse // else keyword
- itemEnd // end keyword
- itemField // alphanumeric identifier, starting with '.'.
+ itemField // alphanumeric identifier, starting with '.', possibly chained ('.x.y')
itemIdentifier // alphanumeric identifier
- itemIf // if keyword
- itemLeftMeta // left meta-string
- itemNumber // number
+ itemLeftDelim // left action delimiter
+ itemNumber // simple number, including imaginary
itemPipe // pipe symbol
- itemRange // range keyword
itemRawString // raw quoted string (includes quotes)
- itemRightMeta // right meta-string
+ itemRightDelim // right action delimiter
itemString // quoted string (includes quotes)
itemText // plain text
+ // 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",
- itemDot: ".",
+ itemBool: "bool",
+ itemComplex: "complex",
itemEOF: "EOF",
- itemElse: "else",
- itemEnd: "end",
itemField: "field",
itemIdentifier: "identifier",
- itemIf: "if",
- itemLeftMeta: "left meta",
+ itemLeftDelim: "left delim",
itemNumber: "number",
itemPipe: "pipe",
- itemRange: "range",
itemRawString: "raw string",
- itemRightMeta: "rightMeta",
+ itemRightDelim: "right delim",
itemString: "string",
- itemText: "text",
+ // keywords
+ itemDot: ".",
+ itemDefine: "define",
+ itemElse: "else",
+ itemIf: "if",
+ itemEnd: "end",
+ itemRange: "range",
+ itemTemplate: "template",
+ itemWith: "with",
}
func (i itemType) String() string {
@@ -81,11 +94,14 @@ func (i itemType) String() string {
}
var key = map[string]itemType{
- ".": itemDot,
- "else": itemElse,
- "end": itemEnd,
- "if": itemIf,
- "range": itemRange,
+ ".": itemDot,
+ "define": itemDefine,
+ "else": itemElse,
+ "end": itemEnd,
+ "if": itemIf,
+ "range": itemRange,
+ "template": itemTemplate,
+ "with": itemWith,
}
const eof = -1
@@ -97,6 +113,7 @@ type stateFn func(*lexer) stateFn
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.
@@ -166,38 +183,47 @@ func (l *lexer) errorf(format string, args ...interface{}) stateFn {
return nil
}
-// run lexes the input by executing state functions until nil.
-func (l *lexer) run() {
- for state := lexText; state != nil; {
- state = state(l)
+// 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)
+ }
}
- close(l.items)
+ panic("not reached")
}
-// lex launches a new scanner and returns the channel of items.
-func lex(name, input string) (*lexer, chan item) {
+// lex creates a new scanner for the input string.
+func lex(name, input string) *lexer {
l := &lexer{
name: name,
input: input,
- items: make(chan item),
+ state: lexText,
+ items: make(chan item, 2), // Two items of buffering is sufficient for all state functions
}
- go l.run()
- return l, l.items
+ return l
}
// state functions
-const leftMeta = "{{"
-const rightMeta = "}}"
+const (
+ leftDelim = "{{"
+ rightDelim = "}}"
+ leftComment = "{{/*"
+ rightComment = "*/}}"
+)
-// lexText scans until a metacharacter
+// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
for {
- if strings.HasPrefix(l.input[l.pos:], leftMeta) {
+ if strings.HasPrefix(l.input[l.pos:], leftDelim) {
if l.pos > l.start {
l.emit(itemText)
}
- return lexLeftMeta
+ return lexLeftDelim
}
if l.next() == eof {
break
@@ -211,28 +237,42 @@ func lexText(l *lexer) stateFn {
return nil
}
-// leftMeta scans the left "metacharacter", which is known to be present.
-func lexLeftMeta(l *lexer) stateFn {
- l.pos += len(leftMeta)
- l.emit(itemLeftMeta)
+// 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
}
-// rightMeta scans the right "metacharacter", which is known to be present.
-func lexRightMeta(l *lexer) stateFn {
- l.pos += len(rightMeta)
- l.emit(itemRightMeta)
+// 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 "metacharacters".
+// 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:], rightMeta) {
- return lexRightMeta
+ if strings.HasPrefix(l.input[l.pos:], rightDelim) {
+ return lexRightDelim
}
switch r := l.next(); {
case r == eof || r == '\n':
@@ -273,15 +313,19 @@ Loop:
for {
switch r := l.next(); {
case isAlphaNumeric(r):
- // absorb
+ // absorb.
+ case r == '.' && l.input[l.start] == '.':
+ // field chaining; absorb into one token.
default:
l.backup()
word := l.input[l.start:l.pos]
switch {
- case key[word] != itemError:
+ case key[word] > itemKeyword:
l.emit(key[word])
case word[0] == '.':
l.emit(itemField)
+ case word == "true", word == "false":
+ l.emit(itemBool)
default:
l.emit(itemIdentifier)
}
@@ -295,8 +339,23 @@ Loop:
// 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.
-// TODO: without expressions you can do imaginary but not complex.
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?
@@ -317,10 +376,9 @@ func lexNumber(l *lexer) stateFn {
// Next thing mustn't be alphanumeric.
if isAlphaNumeric(l.peek()) {
l.next()
- return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
+ return false
}
- l.emit(itemNumber)
- return lexInsideAction
+ return true
}
// lexQuote scans a quoted string.