2015-10-04 17:16:43 +00:00
|
|
|
|
package scanner
|
2015-10-03 18:06:30 +00:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
2015-10-03 21:20:26 +00:00
|
|
|
|
var f100 = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
|
|
|
|
|
2015-10-04 17:19:39 +00:00
|
|
|
|
type tokenPair struct {
|
2015-10-07 09:24:03 +00:00
|
|
|
|
tok TokenType
|
2015-10-03 18:06:30 +00:00
|
|
|
|
text string
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 15:18:09 +00:00
|
|
|
|
var tokenLists = map[string][]tokenPair{
|
|
|
|
|
"comment": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{COMMENT, "//"},
|
|
|
|
|
{COMMENT, "////"},
|
|
|
|
|
{COMMENT, "// comment"},
|
|
|
|
|
{COMMENT, "// /* comment */"},
|
|
|
|
|
{COMMENT, "// // comment //"},
|
|
|
|
|
{COMMENT, "//" + f100},
|
|
|
|
|
{COMMENT, "#"},
|
|
|
|
|
{COMMENT, "##"},
|
|
|
|
|
{COMMENT, "# comment"},
|
|
|
|
|
{COMMENT, "# /* comment */"},
|
|
|
|
|
{COMMENT, "# # comment #"},
|
|
|
|
|
{COMMENT, "#" + f100},
|
|
|
|
|
{COMMENT, "/**/"},
|
|
|
|
|
{COMMENT, "/***/"},
|
|
|
|
|
{COMMENT, "/* comment */"},
|
|
|
|
|
{COMMENT, "/* // comment */"},
|
|
|
|
|
{COMMENT, "/* /* comment */"},
|
|
|
|
|
{COMMENT, "/*\n comment\n*/"},
|
|
|
|
|
{COMMENT, "/*" + f100 + "*/"},
|
2015-10-05 15:18:09 +00:00
|
|
|
|
},
|
|
|
|
|
"operator": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{LBRACK, "["},
|
|
|
|
|
{LBRACE, "{"},
|
|
|
|
|
{COMMA, ","},
|
|
|
|
|
{PERIOD, "."},
|
|
|
|
|
{RBRACK, "]"},
|
|
|
|
|
{RBRACE, "}"},
|
|
|
|
|
{ASSIGN, "="},
|
|
|
|
|
{ADD, "+"},
|
|
|
|
|
{SUB, "-"},
|
2015-10-05 15:18:09 +00:00
|
|
|
|
},
|
|
|
|
|
"bool": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{BOOL, "true"},
|
|
|
|
|
{BOOL, "false"},
|
2015-10-05 15:18:09 +00:00
|
|
|
|
},
|
2015-10-05 10:44:55 +00:00
|
|
|
|
"ident": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{IDENT, "a"},
|
|
|
|
|
{IDENT, "a0"},
|
|
|
|
|
{IDENT, "foobar"},
|
|
|
|
|
{IDENT, "abc123"},
|
|
|
|
|
{IDENT, "LGTM"},
|
|
|
|
|
{IDENT, "_"},
|
|
|
|
|
{IDENT, "_abc123"},
|
|
|
|
|
{IDENT, "abc123_"},
|
|
|
|
|
{IDENT, "_abc_123_"},
|
|
|
|
|
{IDENT, "_äöü"},
|
|
|
|
|
{IDENT, "_本"},
|
|
|
|
|
{IDENT, "äöü"},
|
|
|
|
|
{IDENT, "本"},
|
|
|
|
|
{IDENT, "a۰۱۸"},
|
|
|
|
|
{IDENT, "foo६४"},
|
|
|
|
|
{IDENT, "bar9876"},
|
2015-10-05 15:18:09 +00:00
|
|
|
|
},
|
|
|
|
|
"string": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{STRING, `" "`},
|
|
|
|
|
{STRING, `"a"`},
|
|
|
|
|
{STRING, `"本"`},
|
|
|
|
|
{STRING, `"\a"`},
|
|
|
|
|
{STRING, `"\b"`},
|
|
|
|
|
{STRING, `"\f"`},
|
|
|
|
|
{STRING, `"\n"`},
|
|
|
|
|
{STRING, `"\r"`},
|
|
|
|
|
{STRING, `"\t"`},
|
|
|
|
|
{STRING, `"\v"`},
|
|
|
|
|
{STRING, `"\""`},
|
|
|
|
|
{STRING, `"\000"`},
|
|
|
|
|
{STRING, `"\777"`},
|
|
|
|
|
{STRING, `"\x00"`},
|
|
|
|
|
{STRING, `"\xff"`},
|
|
|
|
|
{STRING, `"\u0000"`},
|
|
|
|
|
{STRING, `"\ufA16"`},
|
|
|
|
|
{STRING, `"\U00000000"`},
|
|
|
|
|
{STRING, `"\U0000ffAB"`},
|
|
|
|
|
{STRING, `"` + f100 + `"`},
|
2015-10-05 15:18:09 +00:00
|
|
|
|
},
|
|
|
|
|
"number": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{NUMBER, "0"},
|
|
|
|
|
{NUMBER, "1"},
|
|
|
|
|
{NUMBER, "9"},
|
|
|
|
|
{NUMBER, "42"},
|
|
|
|
|
{NUMBER, "1234567890"},
|
|
|
|
|
{NUMBER, "00"},
|
|
|
|
|
{NUMBER, "01"},
|
|
|
|
|
{NUMBER, "07"},
|
|
|
|
|
{NUMBER, "042"},
|
|
|
|
|
{NUMBER, "01234567"},
|
|
|
|
|
{NUMBER, "0x0"},
|
|
|
|
|
{NUMBER, "0x1"},
|
|
|
|
|
{NUMBER, "0xf"},
|
|
|
|
|
{NUMBER, "0x42"},
|
|
|
|
|
{NUMBER, "0x123456789abcDEF"},
|
|
|
|
|
{NUMBER, "0x" + f100},
|
|
|
|
|
{NUMBER, "0X0"},
|
|
|
|
|
{NUMBER, "0X1"},
|
|
|
|
|
{NUMBER, "0XF"},
|
|
|
|
|
{NUMBER, "0X42"},
|
|
|
|
|
{NUMBER, "0X123456789abcDEF"},
|
|
|
|
|
{NUMBER, "0X" + f100},
|
|
|
|
|
{NUMBER, "0e0"},
|
|
|
|
|
{NUMBER, "1e0"},
|
|
|
|
|
{NUMBER, "42e0"},
|
|
|
|
|
{NUMBER, "01234567890e0"},
|
|
|
|
|
{NUMBER, "0E0"},
|
|
|
|
|
{NUMBER, "1E0"},
|
|
|
|
|
{NUMBER, "42E0"},
|
|
|
|
|
{NUMBER, "01234567890E0"},
|
|
|
|
|
{NUMBER, "0e+10"},
|
|
|
|
|
{NUMBER, "1e-10"},
|
|
|
|
|
{NUMBER, "42e+10"},
|
|
|
|
|
{NUMBER, "01234567890e-10"},
|
|
|
|
|
{NUMBER, "0E+10"},
|
|
|
|
|
{NUMBER, "1E-10"},
|
|
|
|
|
{NUMBER, "42E+10"},
|
|
|
|
|
{NUMBER, "01234567890E-10"},
|
2015-10-05 15:18:09 +00:00
|
|
|
|
},
|
|
|
|
|
"float": []tokenPair{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{FLOAT, "0."},
|
|
|
|
|
{FLOAT, "1."},
|
|
|
|
|
{FLOAT, "42."},
|
|
|
|
|
{FLOAT, "01234567890."},
|
|
|
|
|
{FLOAT, ".0"},
|
|
|
|
|
{FLOAT, ".1"},
|
|
|
|
|
{FLOAT, ".42"},
|
|
|
|
|
{FLOAT, ".0123456789"},
|
|
|
|
|
{FLOAT, "0.0"},
|
|
|
|
|
{FLOAT, "1.0"},
|
|
|
|
|
{FLOAT, "42.0"},
|
|
|
|
|
{FLOAT, "01234567890.0"},
|
|
|
|
|
{FLOAT, "01.8e0"},
|
|
|
|
|
{FLOAT, "1.4e0"},
|
|
|
|
|
{FLOAT, "42.2e0"},
|
|
|
|
|
{FLOAT, "01234567890.12e0"},
|
|
|
|
|
{FLOAT, "0.E0"},
|
|
|
|
|
{FLOAT, "1.12E0"},
|
|
|
|
|
{FLOAT, "42.123E0"},
|
|
|
|
|
{FLOAT, "01234567890.213E0"},
|
|
|
|
|
{FLOAT, "0.2e+10"},
|
|
|
|
|
{FLOAT, "1.2e-10"},
|
|
|
|
|
{FLOAT, "42.54e+10"},
|
|
|
|
|
{FLOAT, "01234567890.98e-10"},
|
|
|
|
|
{FLOAT, "0.1E+10"},
|
|
|
|
|
{FLOAT, "1.1E-10"},
|
|
|
|
|
{FLOAT, "42.1E+10"},
|
|
|
|
|
{FLOAT, "01234567890.1E-10"},
|
2015-10-05 10:44:55 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
2015-10-04 19:53:20 +00:00
|
|
|
|
|
2015-10-05 15:23:56 +00:00
|
|
|
|
var orderedTokenLists = []string{
|
|
|
|
|
"comment",
|
|
|
|
|
"operator",
|
|
|
|
|
"bool",
|
|
|
|
|
"ident",
|
|
|
|
|
"string",
|
|
|
|
|
"number",
|
|
|
|
|
"float",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPosition(t *testing.T) {
|
|
|
|
|
// create artifical source code
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
|
|
|
|
|
for _, listName := range orderedTokenLists {
|
|
|
|
|
for _, ident := range tokenLists[listName] {
|
|
|
|
|
fmt.Fprintf(buf, "\t\t\t\t%s\n", ident.text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 16:53:56 +00:00
|
|
|
|
s := NewScanner(buf.Bytes())
|
2015-10-05 15:23:56 +00:00
|
|
|
|
|
2015-10-07 22:38:39 +00:00
|
|
|
|
pos := Pos{"", 4, 1, 5}
|
2015-10-05 15:23:56 +00:00
|
|
|
|
s.Scan()
|
|
|
|
|
for _, listName := range orderedTokenLists {
|
|
|
|
|
|
|
|
|
|
for _, k := range tokenLists[listName] {
|
2015-10-06 16:03:32 +00:00
|
|
|
|
curPos := s.tokPos
|
2015-10-05 15:23:56 +00:00
|
|
|
|
// fmt.Printf("[%q] s = %+v:%+v\n", k.text, curPos.Offset, curPos.Column)
|
2015-10-05 22:11:02 +00:00
|
|
|
|
|
2015-10-05 15:23:56 +00:00
|
|
|
|
if curPos.Offset != pos.Offset {
|
|
|
|
|
t.Fatalf("offset = %d, want %d for %q", curPos.Offset, pos.Offset, k.text)
|
|
|
|
|
}
|
|
|
|
|
if curPos.Line != pos.Line {
|
|
|
|
|
t.Fatalf("line = %d, want %d for %q", curPos.Line, pos.Line, k.text)
|
|
|
|
|
}
|
|
|
|
|
if curPos.Column != pos.Column {
|
|
|
|
|
t.Fatalf("column = %d, want %d for %q", curPos.Column, pos.Column, k.text)
|
|
|
|
|
}
|
|
|
|
|
pos.Offset += 4 + len(k.text) + 1 // 4 tabs + token bytes + newline
|
|
|
|
|
pos.Line += countNewlines(k.text) + 1 // each token is on a new line
|
|
|
|
|
s.Scan()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// make sure there were no token-internal errors reported by scanner
|
|
|
|
|
if s.ErrorCount != 0 {
|
|
|
|
|
t.Errorf("%d errors", s.ErrorCount)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 10:44:55 +00:00
|
|
|
|
func TestComment(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["comment"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOperator(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["operator"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBool(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["bool"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestIdent(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["ident"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestString(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["string"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNumber(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["number"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFloat(t *testing.T) {
|
|
|
|
|
testTokenList(t, tokenLists["float"])
|
2015-10-04 19:53:20 +00:00
|
|
|
|
}
|
2015-10-05 14:34:45 +00:00
|
|
|
|
|
2015-10-06 17:57:10 +00:00
|
|
|
|
func TestRealExample(t *testing.T) {
|
|
|
|
|
complexHCL := `// This comes from Terraform, as a test
|
|
|
|
|
variable "foo" {
|
|
|
|
|
default = "bar"
|
|
|
|
|
description = "bar"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
provider "aws" {
|
|
|
|
|
access_key = "foo"
|
|
|
|
|
secret_key = "bar"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource "aws_security_group" "firewall" {
|
|
|
|
|
count = 5
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource aws_instance "web" {
|
|
|
|
|
ami = "${var.foo}"
|
|
|
|
|
security_groups = [
|
|
|
|
|
"foo",
|
|
|
|
|
"${aws_security_group.firewall.foo}"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
network_interface {
|
|
|
|
|
device_index = 0
|
|
|
|
|
description = "Main network interface"
|
|
|
|
|
}
|
|
|
|
|
}`
|
|
|
|
|
|
|
|
|
|
literals := []struct {
|
2015-10-07 09:24:03 +00:00
|
|
|
|
token TokenType
|
2015-10-06 17:57:10 +00:00
|
|
|
|
literal string
|
|
|
|
|
}{
|
2015-10-07 09:24:03 +00:00
|
|
|
|
{COMMENT, `// This comes from Terraform, as a test`},
|
|
|
|
|
{IDENT, `variable`},
|
|
|
|
|
{STRING, `"foo"`},
|
|
|
|
|
{LBRACE, `{`},
|
|
|
|
|
{IDENT, `default`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{STRING, `"bar"`},
|
|
|
|
|
{IDENT, `description`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{STRING, `"bar"`},
|
|
|
|
|
{RBRACE, `}`},
|
|
|
|
|
{IDENT, `provider`},
|
|
|
|
|
{STRING, `"aws"`},
|
|
|
|
|
{LBRACE, `{`},
|
|
|
|
|
{IDENT, `access_key`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{STRING, `"foo"`},
|
|
|
|
|
{IDENT, `secret_key`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{STRING, `"bar"`},
|
|
|
|
|
{RBRACE, `}`},
|
|
|
|
|
{IDENT, `resource`},
|
|
|
|
|
{STRING, `"aws_security_group"`},
|
|
|
|
|
{STRING, `"firewall"`},
|
|
|
|
|
{LBRACE, `{`},
|
|
|
|
|
{IDENT, `count`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{NUMBER, `5`},
|
|
|
|
|
{RBRACE, `}`},
|
|
|
|
|
{IDENT, `resource`},
|
|
|
|
|
{IDENT, `aws_instance`},
|
|
|
|
|
{STRING, `"web"`},
|
|
|
|
|
{LBRACE, `{`},
|
|
|
|
|
{IDENT, `ami`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{STRING, `"${var.foo}"`},
|
|
|
|
|
{IDENT, `security_groups`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{LBRACK, `[`},
|
|
|
|
|
{STRING, `"foo"`},
|
|
|
|
|
{COMMA, `,`},
|
|
|
|
|
{STRING, `"${aws_security_group.firewall.foo}"`},
|
|
|
|
|
{RBRACK, `]`},
|
|
|
|
|
{IDENT, `network_interface`},
|
|
|
|
|
{LBRACE, `{`},
|
|
|
|
|
{IDENT, `device_index`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{NUMBER, `0`},
|
|
|
|
|
{IDENT, `description`},
|
|
|
|
|
{ASSIGN, `=`},
|
|
|
|
|
{STRING, `"Main network interface"`},
|
|
|
|
|
{RBRACE, `}`},
|
|
|
|
|
{RBRACE, `}`},
|
|
|
|
|
{EOF, ``},
|
2015-10-06 17:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := NewScanner([]byte(complexHCL))
|
|
|
|
|
for _, l := range literals {
|
|
|
|
|
tok := s.Scan()
|
2015-10-07 09:24:03 +00:00
|
|
|
|
if l.token != tok.Type() {
|
|
|
|
|
t.Errorf("got: %s want %s for %s\n", tok, l.token, tok.String())
|
2015-10-06 17:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 09:24:03 +00:00
|
|
|
|
if l.literal != tok.String() {
|
|
|
|
|
t.Errorf("got: %s want %s\n", tok, l.literal)
|
2015-10-06 17:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 16:53:56 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 15:48:26 +00:00
|
|
|
|
func TestError(t *testing.T) {
|
2015-10-07 09:24:03 +00:00
|
|
|
|
testError(t, "\x80", "1:1", "illegal UTF-8 encoding", ILLEGAL)
|
|
|
|
|
testError(t, "\xff", "1:1", "illegal UTF-8 encoding", ILLEGAL)
|
2015-10-05 15:48:26 +00:00
|
|
|
|
|
2015-10-07 09:24:03 +00:00
|
|
|
|
testError(t, "ab\x80", "1:3", "illegal UTF-8 encoding", IDENT)
|
|
|
|
|
testError(t, "abc\xff", "1:4", "illegal UTF-8 encoding", IDENT)
|
2015-10-05 15:48:26 +00:00
|
|
|
|
|
2015-10-07 09:24:03 +00:00
|
|
|
|
testError(t, `"ab`+"\x80", "1:4", "illegal UTF-8 encoding", STRING)
|
|
|
|
|
testError(t, `"abc`+"\xff", "1:5", "illegal UTF-8 encoding", STRING)
|
2015-10-05 15:48:26 +00:00
|
|
|
|
|
2015-10-07 09:24:03 +00:00
|
|
|
|
testError(t, `01238`, "1:6", "illegal octal number", NUMBER)
|
|
|
|
|
testError(t, `01238123`, "1:9", "illegal octal number", NUMBER)
|
|
|
|
|
testError(t, `0x`, "1:3", "illegal hexadecimal number", NUMBER)
|
|
|
|
|
testError(t, `0xg`, "1:3", "illegal hexadecimal number", NUMBER)
|
|
|
|
|
testError(t, `'aa'`, "1:1", "illegal char", ILLEGAL)
|
2015-10-05 15:48:26 +00:00
|
|
|
|
|
2015-10-07 09:24:03 +00:00
|
|
|
|
testError(t, `"`, "1:2", "literal not terminated", STRING)
|
|
|
|
|
testError(t, `"abc`, "1:5", "literal not terminated", STRING)
|
|
|
|
|
testError(t, `"abc`+"\n", "1:5", "literal not terminated", STRING)
|
|
|
|
|
testError(t, `/*/`, "1:4", "comment not terminated", COMMENT)
|
2015-10-05 15:48:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 09:24:03 +00:00
|
|
|
|
func testError(t *testing.T, src, pos, msg string, tok TokenType) {
|
2015-10-06 16:53:56 +00:00
|
|
|
|
s := NewScanner([]byte(src))
|
2015-10-05 15:48:26 +00:00
|
|
|
|
|
|
|
|
|
errorCalled := false
|
2015-10-07 22:38:39 +00:00
|
|
|
|
s.Error = func(p Pos, m string) {
|
2015-10-05 15:48:26 +00:00
|
|
|
|
if !errorCalled {
|
|
|
|
|
if pos != p.String() {
|
|
|
|
|
t.Errorf("pos = %q, want %q for %q", p, pos, src)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if m != msg {
|
|
|
|
|
t.Errorf("msg = %q, want %q for %q", m, msg, src)
|
|
|
|
|
}
|
|
|
|
|
errorCalled = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tk := s.Scan()
|
2015-10-07 09:24:03 +00:00
|
|
|
|
if tk.Type() != tok {
|
2015-10-05 15:48:26 +00:00
|
|
|
|
t.Errorf("tok = %s, want %s for %q", tk, tok, src)
|
|
|
|
|
}
|
|
|
|
|
if !errorCalled {
|
|
|
|
|
t.Errorf("error handler not called for %q", src)
|
|
|
|
|
}
|
|
|
|
|
if s.ErrorCount == 0 {
|
|
|
|
|
t.Errorf("count = %d, want > 0 for %q", s.ErrorCount, src)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 15:23:56 +00:00
|
|
|
|
func testTokenList(t *testing.T, tokenList []tokenPair) {
|
|
|
|
|
// create artifical source code
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
for _, ident := range tokenList {
|
|
|
|
|
fmt.Fprintf(buf, "%s\n", ident.text)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 16:53:56 +00:00
|
|
|
|
s := NewScanner(buf.Bytes())
|
2015-10-05 15:23:56 +00:00
|
|
|
|
for _, ident := range tokenList {
|
|
|
|
|
tok := s.Scan()
|
2015-10-07 09:24:03 +00:00
|
|
|
|
if tok.Type() != ident.tok {
|
2015-10-05 15:23:56 +00:00
|
|
|
|
t.Errorf("tok = %q want %q for %q\n", tok, ident.tok, ident.text)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 12:04:34 +00:00
|
|
|
|
if tok.String() != ident.text {
|
|
|
|
|
t.Errorf("text = %q want %q", tok.String(), ident.text)
|
2015-10-05 15:23:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 14:34:45 +00:00
|
|
|
|
func countNewlines(s string) int {
|
|
|
|
|
n := 0
|
|
|
|
|
for _, ch := range s {
|
|
|
|
|
if ch == '\n' {
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n
|
|
|
|
|
}
|