]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: character constants.
authorRob Pike <r@golang.org>
Mon, 11 Jul 2011 01:46:22 +0000 (11:46 +1000)
committerRob Pike <r@golang.org>
Mon, 11 Jul 2011 01:46:22 +0000 (11:46 +1000)
Easier to implement than to justify leaving them out.

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

src/pkg/exp/template/doc.go
src/pkg/exp/template/lex.go
src/pkg/exp/template/lex_test.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/parse_test.go

index 11d45504ef6816f15cc9f4d67e4fcf9872341f54..ae0b97ceda80335e69ea330770c8dbc292a2c3fc 100644 (file)
@@ -78,10 +78,9 @@ Arguments
 
 An argument is a simple value, denoted by one of the following:
 
-       - A boolean, string, integer, floating-point, imaginary or complex
-         constant in Go syntax. These behave like Go's untyped constants,
-         although raw strings may not span newlines. (Character constants are
-         not supported; this may change.)
+       - A boolean, string, character, integer, floating-point, imaginary
+         or complex constant in Go syntax. These behave like Go's untyped
+         constants, although raw strings may not span newlines.
        - The character '.' (period):
                .
          The result is the value of dot.
index e8763c55c6b7e98fdf728fcca84de1962e67def2..7aebe02a3106877718f69bd21dcab0e12c376b90 100644 (file)
@@ -37,6 +37,7 @@ type itemType int
 const (
        itemError       itemType = iota // error occurred; value is text of error
        itemBool                        // boolean constant
+       itemChar                        // character constant
        itemComplex                     // complex constant (1+2i); imaginary is just a number
        itemColonEquals                 // colon-equals (':=') introducing a declaration
        itemEOF
@@ -66,6 +67,7 @@ const (
 var itemName = map[itemType]string{
        itemError:       "error",
        itemBool:        "bool",
+       itemChar:        "char",
        itemComplex:     "complex",
        itemColonEquals: ":=",
        itemEOF:         "EOF",
@@ -296,6 +298,8 @@ func lexInsideAction(l *lexer) stateFn {
                        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) {
@@ -348,6 +352,27 @@ 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(itemChar)
+       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
index d2156fa71291957bd0e28995e79d5388a857329f..e88fd363fd4e0bd561e404665a23d89b1fd87671 100644 (file)
@@ -53,6 +53,18 @@ var lexTests = []lexTest{
                tRight,
                tEOF,
        }},
+       {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
+               tLeft,
+               {itemChar, `'a'`},
+               {itemChar, `'\n'`},
+               {itemChar, `'\''`},
+               {itemChar, `'\\'`},
+               {itemChar, `'\u00FF'`},
+               {itemChar, `'\xFF'`},
+               {itemChar, `'本'`},
+               tRight,
+               tEOF,
+       }},
        {"bools", "{{true false}}", []item{
                tLeft,
                {itemBool, "true"},
@@ -137,6 +149,10 @@ var lexTests = []lexTest{
                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"`},
index b7fea497e2429d640055643a2011ec435bbc7f77..77d554d3b5fcee4f3538153a2039619e7957004b 100644 (file)
@@ -289,9 +289,28 @@ type numberNode struct {
        text       string
 }
 
-func newNumber(text string, isComplex bool) (*numberNode, os.Error) {
+func newNumber(text string, typ itemType) (*numberNode, os.Error) {
        n := &numberNode{nodeType: nodeNumber, text: text}
-       if isComplex {
+       switch typ {
+       case itemChar:
+               if len(text) < 3 {
+                       return nil, fmt.Errorf("illegal character constant: %s", text)
+               }
+               rune, _, tail, err := strconv.UnquoteChar(text[1:len(text)-1], text[0])
+               if err != nil {
+                       return nil, err
+               }
+               if len(tail) > 0 {
+                       return nil, fmt.Errorf("extra bytes in 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
@@ -713,7 +732,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
                                t.errorf("missing value for %s", context)
                        }
                        return
-               case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemVariable, itemNumber, itemRawString, itemString:
+               case itemBool, itemChar, itemComplex, itemDot, itemField, itemIdentifier, itemVariable, itemNumber, itemRawString, itemString:
                        t.backup()
                        pipe.append(t.command())
                default:
@@ -853,8 +872,8 @@ Loop:
                        cmd.append(newField(token.val))
                case itemBool:
                        cmd.append(newBool(token.val == "true"))
-               case itemComplex, itemNumber:
-                       number, err := newNumber(token.val, token.typ == itemComplex)
+               case itemChar, itemComplex, itemNumber:
+                       number, err := newNumber(token.val, token.typ)
                        if err != nil {
                                t.error(err)
                        }
index 7524ac8b251583fcbbdedb5f321f4c539cf56bce..7439ec80925a7b2959f51d7156b502321b00e330 100644 (file)
@@ -29,6 +29,8 @@ var numberTests = []numberTest{
        {"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},
@@ -39,6 +41,7 @@ var numberTests = []numberTest{
        {"-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},
@@ -48,12 +51,23 @@ var numberTests = []numberTest{
        {"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) {
@@ -61,11 +75,19 @@ func TestNumberParse(t *testing.T) {
                // 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
-               _, err := fmt.Sscan(test.text, &c)
-               n, err := newNumber(test.text, err == nil)
+               typ := itemNumber
+               if test.text[0] == '\'' {
+                       typ = itemChar
+               } 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", test.text)
+                       t.Errorf("unexpected error for %q: %s", test.text, err)
                        continue
                }
                if !ok && err == nil {
@@ -73,6 +95,9 @@ func TestNumberParse(t *testing.T) {
                        continue
                }
                if !ok {
+                       if *debug {
+                               fmt.Printf("%s\n\t%s\n", test.text, err)
+                       }
                        continue
                }
                if n.isComplex != test.isComplex {
@@ -174,8 +199,8 @@ var parseTests = []parseTest{
                `[({{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 }}{{end}}", noError,
-               `[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
+       {"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 S=`x`}}]"},
        {"template", "{{template `x` .Y}}", noError,