Commit bf9531f8 authored by Rob Pike's avatar Rob Pike

exp/template: character constants.

Easier to implement than to justify leaving them out.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4662089
parent e7030e7f
...@@ -78,10 +78,9 @@ Arguments ...@@ -78,10 +78,9 @@ Arguments
An argument is a simple value, denoted by one of the following: An argument is a simple value, denoted by one of the following:
- A boolean, string, integer, floating-point, imaginary or complex - A boolean, string, character, integer, floating-point, imaginary
constant in Go syntax. These behave like Go's untyped constants, or complex constant in Go syntax. These behave like Go's untyped
although raw strings may not span newlines. (Character constants are constants, although raw strings may not span newlines.
not supported; this may change.)
- The character '.' (period): - The character '.' (period):
. .
The result is the value of dot. The result is the value of dot.
......
...@@ -37,6 +37,7 @@ type itemType int ...@@ -37,6 +37,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 // 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
itemEOF itemEOF
...@@ -66,6 +67,7 @@ const ( ...@@ -66,6 +67,7 @@ const (
var itemName = map[itemType]string{ var itemName = map[itemType]string{
itemError: "error", itemError: "error",
itemBool: "bool", itemBool: "bool",
itemChar: "char",
itemComplex: "complex", itemComplex: "complex",
itemColonEquals: ":=", itemColonEquals: ":=",
itemEOF: "EOF", itemEOF: "EOF",
...@@ -296,6 +298,8 @@ func lexInsideAction(l *lexer) stateFn { ...@@ -296,6 +298,8 @@ func lexInsideAction(l *lexer) stateFn {
return lexRawQuote return lexRawQuote
case r == '$': case r == '$':
return lexIdentifier return lexIdentifier
case r == '\'':
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 < len(l.input) {
...@@ -348,6 +352,27 @@ Loop: ...@@ -348,6 +352,27 @@ Loop:
return lexInsideAction 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 // lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2" // 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 // and "089" - but when it's wrong the input is invalid and the parser (via
......
...@@ -53,6 +53,18 @@ var lexTests = []lexTest{ ...@@ -53,6 +53,18 @@ var lexTests = []lexTest{
tRight, tRight,
tEOF, 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{ {"bools", "{{true false}}", []item{
tLeft, tLeft,
{itemBool, "true"}, {itemBool, "true"},
...@@ -137,6 +149,10 @@ var lexTests = []lexTest{ ...@@ -137,6 +149,10 @@ var lexTests = []lexTest{
tLeft, tLeft,
{itemError, "unterminated raw quoted string"}, {itemError, "unterminated raw quoted string"},
}}, }},
{"unclosed char constant", "{{'\n}}", []item{
tLeft,
{itemError, "unterminated character constant"},
}},
{"bad number", "{{3k}}", []item{ {"bad number", "{{3k}}", []item{
tLeft, tLeft,
{itemError, `bad number syntax: "3k"`}, {itemError, `bad number syntax: "3k"`},
......
...@@ -289,9 +289,28 @@ type numberNode struct { ...@@ -289,9 +289,28 @@ type numberNode struct {
text string 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} 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. // fmt.Sscan can parse the pair, so let it do the work.
if _, err := fmt.Sscan(text, &n.complex128); err != nil { if _, err := fmt.Sscan(text, &n.complex128); err != nil {
return nil, err return nil, err
...@@ -713,7 +732,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) { ...@@ -713,7 +732,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
t.errorf("missing value for %s", context) t.errorf("missing value for %s", context)
} }
return return
case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemVariable, itemNumber, itemRawString, itemString: case itemBool, itemChar, itemComplex, itemDot, itemField, itemIdentifier, itemVariable, itemNumber, itemRawString, itemString:
t.backup() t.backup()
pipe.append(t.command()) pipe.append(t.command())
default: default:
...@@ -853,8 +872,8 @@ Loop: ...@@ -853,8 +872,8 @@ Loop:
cmd.append(newField(token.val)) cmd.append(newField(token.val))
case itemBool: case itemBool:
cmd.append(newBool(token.val == "true")) cmd.append(newBool(token.val == "true"))
case itemComplex, itemNumber: case itemChar, itemComplex, itemNumber:
number, err := newNumber(token.val, token.typ == itemComplex) number, err := newNumber(token.val, token.typ)
if err != nil { if err != nil {
t.error(err) t.error(err)
} }
......
...@@ -29,6 +29,8 @@ var numberTests = []numberTest{ ...@@ -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},
{"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint. {"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint.
{"73", true, true, true, false, 73, 73, 73, 0}, {"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},
{"+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}, {"100", true, true, true, false, 100, 100, 100, 0},
...@@ -39,6 +41,7 @@ var numberTests = []numberTest{ ...@@ -39,6 +41,7 @@ var numberTests = []numberTest{
{"-1e19", false, false, true, false, 0, 0, -1e19, 0}, {"-1e19", false, false, true, false, 0, 0, -1e19, 0},
{"4i", false, false, false, true, 0, 0, 0, 4i}, {"4i", false, false, false, true, 0, 0, 0, 4i},
{"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i}, {"-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) // complex with 0 imaginary are float (and maybe integer)
{"0i", true, true, true, true, 0, 0, 0, 0}, {"0i", true, true, true, true, 0, 0, 0, 0},
{"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2}, {"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2},
...@@ -48,12 +51,23 @@ var numberTests = []numberTest{ ...@@ -48,12 +51,23 @@ var numberTests = []numberTest{
{"0123", true, true, true, false, 0123, 0123, 0123, 0}, {"0123", true, true, true, false, 0123, 0123, 0123, 0},
{"-0x0", true, true, true, false, 0, 0, 0, 0}, {"-0x0", true, true, true, false, 0, 0, 0, 0},
{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 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 // some broken syntax
{text: "+-2"}, {text: "+-2"},
{text: "0x123."}, {text: "0x123."},
{text: "1e."}, {text: "1e."},
{text: "0xi."}, {text: "0xi."},
{text: "1+2."}, {text: "1+2."},
{text: "'x"},
{text: "'xx'"},
} }
func TestNumberParse(t *testing.T) { func TestNumberParse(t *testing.T) {
...@@ -61,11 +75,19 @@ 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 // If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
// because imaginary comes out as a number. // because imaginary comes out as a number.
var c complex128 var c complex128
_, err := fmt.Sscan(test.text, &c) typ := itemNumber
n, err := newNumber(test.text, err == nil) 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 ok := test.isInt || test.isUint || test.isFloat || test.isComplex
if ok && err != nil { if ok && err != nil {
t.Errorf("unexpected error for %q", test.text) t.Errorf("unexpected error for %q: %s", test.text, err)
continue continue
} }
if !ok && err == nil { if !ok && err == nil {
...@@ -73,6 +95,9 @@ func TestNumberParse(t *testing.T) { ...@@ -73,6 +95,9 @@ func TestNumberParse(t *testing.T) {
continue continue
} }
if !ok { if !ok {
if *debug {
fmt.Printf("%s\n\t%s\n", test.text, err)
}
continue continue
} }
if n.isComplex != test.isComplex { if n.isComplex != test.isComplex {
...@@ -174,8 +199,8 @@ var parseTests = []parseTest{ ...@@ -174,8 +199,8 @@ var parseTests = []parseTest{
`[({{range [(command: [F=[X]]) (command: [F=[M]])]}} [(text: "true")] {{else}} [(text: "false")])]`}, `[({{range [(command: [F=[X]]) (command: [F=[M]])]}} [(text: "true")] {{else}} [(text: "false")])]`},
{"range []int", "{{range .SI}}{{.}}{{end}}", noError, {"range []int", "{{range .SI}}{{.}}{{end}}", noError,
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`}, `[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError, {"constants", "{{range .SI 1 -3.2i true false 'a'}}{{end}}", noError,
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`}, `[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false N='a'])]}} [])]`},
{"template", "{{template `x`}}", noError, {"template", "{{template `x`}}", noError,
"[{{template S=`x`}}]"}, "[{{template S=`x`}}]"},
{"template", "{{template `x` .Y}}", noError, {"template", "{{template `x` .Y}}", noError,
......
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