mirror of
https://github.com/golang/go
synced 2024-11-19 14:34:42 -07:00
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
This commit is contained in:
parent
e7030e7fef
commit
bf9531f80b
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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"`},
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
typ := itemNumber
|
||||
if test.text[0] == '\'' {
|
||||
typ = itemChar
|
||||
} else {
|
||||
_, err := fmt.Sscan(test.text, &c)
|
||||
n, err := newNumber(test.text, err == nil)
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user