From 72efdea20e96cba1b28352c1b0343b510caa8402 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Mon, 4 Jul 2011 11:50:35 +1000 Subject: [PATCH] exp/template: allow complex numbers, add 'with', 'define', and 'template' keywords. Also simplify the handling of keywords. R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/4639096 --- src/pkg/exp/template/lex.go | 88 +++++++++++++++++++++----------- src/pkg/exp/template/lex_test.go | 7 ++- 2 files changed, 63 insertions(+), 32 deletions(-) diff --git a/src/pkg/exp/template/lex.go b/src/pkg/exp/template/lex.go index 1919cf4715..d22d825a1d 100644 --- a/src/pkg/exp/template/lex.go +++ b/src/pkg/exp/template/lex.go @@ -18,60 +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 - itemBool // boolean constant - 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 '.', possibly chained ('.x.y') itemIdentifier // alphanumeric identifier - itemIf // if keyword itemLeftMeta // left meta-string - itemNumber // number + itemNumber // simple number, including imaginary itemPipe // pipe symbol - itemRange // range keyword itemRawString // raw quoted string (includes quotes) itemRightMeta // right meta-string 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", itemBool: "bool", - itemDot: ".", + itemComplex: "complex", itemEOF: "EOF", - itemElse: "else", - itemEnd: "end", itemField: "field", itemIdentifier: "identifier", - itemIf: "if", itemLeftMeta: "left meta", itemNumber: "number", itemPipe: "pipe", - itemRange: "range", itemRawString: "raw string", itemRightMeta: "rightMeta", 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 { @@ -83,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 @@ -282,7 +296,7 @@ Loop: 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) @@ -301,8 +315,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? @@ -323,10 +352,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. diff --git a/src/pkg/exp/template/lex_test.go b/src/pkg/exp/template/lex_test.go index 62bce6daa0..e13a7247a9 100644 --- a/src/pkg/exp/template/lex_test.go +++ b/src/pkg/exp/template/lex_test.go @@ -35,7 +35,7 @@ var lexTests = []lexTest{ {"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}}", []item{ + {"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{ tLeft, {itemNumber, "1"}, {itemNumber, "02"}, @@ -43,6 +43,8 @@ var lexTests = []lexTest{ {itemNumber, "-7.2i"}, {itemNumber, "1e3"}, {itemNumber, "+1.2e-4"}, + {itemNumber, "4.2i"}, + {itemComplex, "1+2i"}, tRight, tEOF, }}, @@ -68,12 +70,13 @@ var lexTests = []lexTest{ tRight, tEOF, }}, - {"keywords", "{{range if else end}}", []item{ + {"keywords", "{{range if else end with}}", []item{ tLeft, {itemRange, "range"}, {itemIf, "if"}, {itemElse, "else"}, {itemEnd, "end"}, + {itemWith, "with"}, tRight, tEOF, }},