2019-12-24 18:42:30 +03:00
|
|
|
|
package parser_test
|
2019-01-09 10:17:42 +03:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2019-12-30 00:38:51 +03:00
|
|
|
|
"github.com/d5/tengo/v2/parser"
|
|
|
|
|
"github.com/d5/tengo/v2/require"
|
|
|
|
|
"github.com/d5/tengo/v2/token"
|
2019-01-09 10:17:42 +03:00
|
|
|
|
)
|
|
|
|
|
|
2019-12-24 18:42:30 +03:00
|
|
|
|
var testFileSet = parser.NewFileSet()
|
2019-01-09 10:17:42 +03:00
|
|
|
|
|
|
|
|
|
type scanResult struct {
|
|
|
|
|
Token token.Token
|
|
|
|
|
Literal string
|
|
|
|
|
Line int
|
|
|
|
|
Column int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestScanner_Scan(t *testing.T) {
|
|
|
|
|
var testCases = [...]struct {
|
|
|
|
|
token token.Token
|
|
|
|
|
literal string
|
|
|
|
|
}{
|
|
|
|
|
{token.Comment, "/* a comment */"},
|
|
|
|
|
{token.Comment, "// a comment \n"},
|
|
|
|
|
{token.Comment, "/*\r*/"},
|
|
|
|
|
{token.Comment, "/**\r/*/"},
|
|
|
|
|
{token.Comment, "/**\r\r/*/"},
|
|
|
|
|
{token.Comment, "//\r\n"},
|
|
|
|
|
{token.Ident, "foobar"},
|
|
|
|
|
{token.Ident, "a۰۱۸"},
|
|
|
|
|
{token.Ident, "foo६४"},
|
|
|
|
|
{token.Ident, "bar9876"},
|
|
|
|
|
{token.Ident, "ŝ"},
|
|
|
|
|
{token.Ident, "ŝfoo"},
|
|
|
|
|
{token.Int, "0"},
|
|
|
|
|
{token.Int, "1"},
|
|
|
|
|
{token.Int, "123456789012345678890"},
|
|
|
|
|
{token.Int, "01234567"},
|
|
|
|
|
{token.Int, "0xcafebabe"},
|
|
|
|
|
{token.Float, "0."},
|
|
|
|
|
{token.Float, ".0"},
|
|
|
|
|
{token.Float, "3.14159265"},
|
|
|
|
|
{token.Float, "1e0"},
|
|
|
|
|
{token.Float, "1e+100"},
|
|
|
|
|
{token.Float, "1e-100"},
|
|
|
|
|
{token.Float, "2.71828e-1000"},
|
2019-01-09 19:58:18 +03:00
|
|
|
|
{token.Char, "'a'"},
|
|
|
|
|
{token.Char, "'\\000'"},
|
|
|
|
|
{token.Char, "'\\xFF'"},
|
|
|
|
|
{token.Char, "'\\uff16'"},
|
|
|
|
|
{token.Char, "'\\U0000ff16'"},
|
|
|
|
|
{token.String, "`foobar`"},
|
|
|
|
|
{token.String, "`" + `foo
|
2019-01-09 10:17:42 +03:00
|
|
|
|
bar` +
|
|
|
|
|
"`",
|
|
|
|
|
},
|
2019-01-09 19:58:18 +03:00
|
|
|
|
{token.String, "`\r`"},
|
|
|
|
|
{token.String, "`foo\r\nbar`"},
|
2019-01-09 10:17:42 +03:00
|
|
|
|
{token.Add, "+"},
|
|
|
|
|
{token.Sub, "-"},
|
|
|
|
|
{token.Mul, "*"},
|
|
|
|
|
{token.Quo, "/"},
|
|
|
|
|
{token.Rem, "%"},
|
|
|
|
|
{token.And, "&"},
|
|
|
|
|
{token.Or, "|"},
|
|
|
|
|
{token.Xor, "^"},
|
|
|
|
|
{token.Shl, "<<"},
|
|
|
|
|
{token.Shr, ">>"},
|
|
|
|
|
{token.AndNot, "&^"},
|
|
|
|
|
{token.AddAssign, "+="},
|
|
|
|
|
{token.SubAssign, "-="},
|
|
|
|
|
{token.MulAssign, "*="},
|
|
|
|
|
{token.QuoAssign, "/="},
|
|
|
|
|
{token.RemAssign, "%="},
|
|
|
|
|
{token.AndAssign, "&="},
|
|
|
|
|
{token.OrAssign, "|="},
|
|
|
|
|
{token.XorAssign, "^="},
|
|
|
|
|
{token.ShlAssign, "<<="},
|
|
|
|
|
{token.ShrAssign, ">>="},
|
|
|
|
|
{token.AndNotAssign, "&^="},
|
|
|
|
|
{token.LAnd, "&&"},
|
|
|
|
|
{token.LOr, "||"},
|
|
|
|
|
{token.Inc, "++"},
|
|
|
|
|
{token.Dec, "--"},
|
|
|
|
|
{token.Equal, "=="},
|
|
|
|
|
{token.Less, "<"},
|
|
|
|
|
{token.Greater, ">"},
|
|
|
|
|
{token.Assign, "="},
|
|
|
|
|
{token.Not, "!"},
|
|
|
|
|
{token.NotEqual, "!="},
|
|
|
|
|
{token.LessEq, "<="},
|
|
|
|
|
{token.GreaterEq, ">="},
|
|
|
|
|
{token.Define, ":="},
|
|
|
|
|
{token.Ellipsis, "..."},
|
|
|
|
|
{token.LParen, "("},
|
|
|
|
|
{token.LBrack, "["},
|
|
|
|
|
{token.LBrace, "{"},
|
|
|
|
|
{token.Comma, ","},
|
|
|
|
|
{token.Period, "."},
|
|
|
|
|
{token.RParen, ")"},
|
|
|
|
|
{token.RBrack, "]"},
|
|
|
|
|
{token.RBrace, "}"},
|
|
|
|
|
{token.Semicolon, ";"},
|
|
|
|
|
{token.Colon, ":"},
|
|
|
|
|
{token.Break, "break"},
|
|
|
|
|
{token.Continue, "continue"},
|
|
|
|
|
{token.Else, "else"},
|
|
|
|
|
{token.For, "for"},
|
|
|
|
|
{token.Func, "func"},
|
|
|
|
|
{token.If, "if"},
|
|
|
|
|
{token.Return, "return"},
|
2019-02-02 10:27:29 +03:00
|
|
|
|
{token.Export, "export"},
|
2019-01-09 10:17:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// combine
|
|
|
|
|
var lines []string
|
|
|
|
|
var lineSum int
|
|
|
|
|
lineNos := make([]int, len(testCases))
|
|
|
|
|
columnNos := make([]int, len(testCases))
|
|
|
|
|
for i, tc := range testCases {
|
|
|
|
|
// add 0-2 lines before each test case
|
|
|
|
|
emptyLines := rand.Intn(3)
|
|
|
|
|
for j := 0; j < emptyLines; j++ {
|
|
|
|
|
lines = append(lines, strings.Repeat(" ", rand.Intn(10)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add test case line with some whitespaces around it
|
|
|
|
|
emptyColumns := rand.Intn(10)
|
|
|
|
|
lines = append(lines, fmt.Sprintf("%s%s%s",
|
|
|
|
|
strings.Repeat(" ", emptyColumns),
|
|
|
|
|
tc.literal,
|
|
|
|
|
strings.Repeat(" ", rand.Intn(10))))
|
|
|
|
|
|
|
|
|
|
lineNos[i] = lineSum + emptyLines + 1
|
|
|
|
|
lineSum += emptyLines + countLines(tc.literal)
|
|
|
|
|
columnNos[i] = emptyColumns + 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// expected results
|
|
|
|
|
var expected []scanResult
|
|
|
|
|
var expectedSkipComments []scanResult
|
|
|
|
|
for i, tc := range testCases {
|
|
|
|
|
// expected literal
|
|
|
|
|
var expectedLiteral string
|
|
|
|
|
switch tc.token {
|
|
|
|
|
case token.Comment:
|
|
|
|
|
// strip CRs in comments
|
2019-12-24 18:42:30 +03:00
|
|
|
|
expectedLiteral = string(parser.StripCR([]byte(tc.literal),
|
2019-12-20 22:40:38 +03:00
|
|
|
|
tc.literal[1] == '*'))
|
2019-01-09 10:17:42 +03:00
|
|
|
|
|
|
|
|
|
//-style comment literal doesn't contain newline
|
|
|
|
|
if expectedLiteral[1] == '/' {
|
|
|
|
|
expectedLiteral = expectedLiteral[:len(expectedLiteral)-1]
|
|
|
|
|
}
|
|
|
|
|
case token.Ident:
|
|
|
|
|
expectedLiteral = tc.literal
|
|
|
|
|
case token.Semicolon:
|
|
|
|
|
expectedLiteral = ";"
|
|
|
|
|
default:
|
|
|
|
|
if tc.token.IsLiteral() {
|
|
|
|
|
// strip CRs in raw string
|
|
|
|
|
expectedLiteral = tc.literal
|
|
|
|
|
if expectedLiteral[0] == '`' {
|
2019-12-24 18:42:30 +03:00
|
|
|
|
expectedLiteral = string(parser.StripCR(
|
2019-12-20 22:40:38 +03:00
|
|
|
|
[]byte(expectedLiteral), false))
|
2019-01-09 10:17:42 +03:00
|
|
|
|
}
|
|
|
|
|
} else if tc.token.IsKeyword() {
|
|
|
|
|
expectedLiteral = tc.literal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res := scanResult{
|
|
|
|
|
Token: tc.token,
|
|
|
|
|
Literal: expectedLiteral,
|
|
|
|
|
Line: lineNos[i],
|
|
|
|
|
Column: columnNos[i],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expected = append(expected, res)
|
|
|
|
|
if tc.token != token.Comment {
|
|
|
|
|
expectedSkipComments = append(expectedSkipComments, res)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 22:40:38 +03:00
|
|
|
|
scanExpect(t, strings.Join(lines, "\n"),
|
2019-12-24 18:42:30 +03:00
|
|
|
|
parser.ScanComments|parser.DontInsertSemis, expected...)
|
2019-12-20 22:40:38 +03:00
|
|
|
|
scanExpect(t, strings.Join(lines, "\n"),
|
2019-12-24 18:42:30 +03:00
|
|
|
|
parser.DontInsertSemis, expectedSkipComments...)
|
2019-01-09 10:17:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStripCR(t *testing.T) {
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
input string
|
|
|
|
|
expect string
|
|
|
|
|
}{
|
|
|
|
|
{"//\n", "//\n"},
|
|
|
|
|
{"//\r\n", "//\n"},
|
|
|
|
|
{"//\r\r\r\n", "//\n"},
|
|
|
|
|
{"//\r*\r/\r\n", "//*/\n"},
|
|
|
|
|
{"/**/", "/**/"},
|
|
|
|
|
{"/*\r/*/", "/*/*/"},
|
|
|
|
|
{"/*\r*/", "/**/"},
|
|
|
|
|
{"/**\r/*/", "/**\r/*/"},
|
|
|
|
|
{"/*\r/\r*\r/*/", "/*/*\r/*/"},
|
|
|
|
|
{"/*\r\r\r\r*/", "/**/"},
|
|
|
|
|
} {
|
2019-12-24 18:42:30 +03:00
|
|
|
|
actual := string(parser.StripCR([]byte(tc.input),
|
2019-12-20 22:40:38 +03:00
|
|
|
|
len(tc.input) >= 2 && tc.input[1] == '*'))
|
|
|
|
|
require.Equal(t, tc.expect, actual)
|
2019-01-09 10:17:42 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 22:40:38 +03:00
|
|
|
|
func scanExpect(
|
|
|
|
|
t *testing.T,
|
|
|
|
|
input string,
|
2019-12-24 18:42:30 +03:00
|
|
|
|
mode parser.ScanMode,
|
2019-12-20 22:40:38 +03:00
|
|
|
|
expected ...scanResult,
|
|
|
|
|
) {
|
2019-02-21 03:26:11 +03:00
|
|
|
|
testFile := testFileSet.AddFile("test", -1, len(input))
|
2019-01-09 10:17:42 +03:00
|
|
|
|
|
2019-12-24 18:42:30 +03:00
|
|
|
|
s := parser.NewScanner(
|
2019-01-09 10:17:42 +03:00
|
|
|
|
testFile,
|
|
|
|
|
[]byte(input),
|
2019-12-24 18:42:30 +03:00
|
|
|
|
func(_ parser.SourceFilePos, msg string) { require.Fail(t, msg) },
|
2019-01-09 10:17:42 +03:00
|
|
|
|
mode)
|
|
|
|
|
|
|
|
|
|
for idx, e := range expected {
|
|
|
|
|
tok, literal, pos := s.Scan()
|
|
|
|
|
|
|
|
|
|
filePos := testFile.Position(pos)
|
|
|
|
|
|
2019-12-20 22:40:38 +03:00
|
|
|
|
require.Equal(t, e.Token, tok, "[%d] expected: %s, actual: %s",
|
|
|
|
|
idx, e.Token.String(), tok.String())
|
|
|
|
|
require.Equal(t, e.Literal, literal)
|
|
|
|
|
require.Equal(t, e.Line, filePos.Line)
|
|
|
|
|
require.Equal(t, e.Column, filePos.Column)
|
2019-01-09 10:17:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tok, _, _ := s.Scan()
|
2019-12-20 22:40:38 +03:00
|
|
|
|
require.Equal(t, token.EOF, tok, "more tokens left")
|
|
|
|
|
require.Equal(t, 0, s.ErrorCount())
|
2019-01-09 10:17:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func countLines(s string) int {
|
|
|
|
|
if s == "" {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
n := 1
|
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
|
if s[i] == '\n' {
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
}
|