scanner_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. package parser_test
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "strings"
  6. "testing"
  7. "time"
  8. "github.com/d5/tengo/v2/parser"
  9. "github.com/d5/tengo/v2/require"
  10. "github.com/d5/tengo/v2/token"
  11. )
  12. var testFileSet = parser.NewFileSet()
  13. type scanResult struct {
  14. Token token.Token
  15. Literal string
  16. Line int
  17. Column int
  18. }
  19. func TestScanner_Scan(t *testing.T) {
  20. var testCases = [...]struct {
  21. token token.Token
  22. literal string
  23. }{
  24. {token.Comment, "/* a comment */"},
  25. {token.Comment, "// a comment \n"},
  26. {token.Comment, "/*\r*/"},
  27. {token.Comment, "/**\r/*/"},
  28. {token.Comment, "/**\r\r/*/"},
  29. {token.Comment, "//\r\n"},
  30. {token.Ident, "foobar"},
  31. {token.Ident, "a۰۱۸"},
  32. {token.Ident, "foo६४"},
  33. {token.Ident, "bar9876"},
  34. {token.Ident, "ŝ"},
  35. {token.Ident, "ŝfoo"},
  36. {token.Int, "0"},
  37. {token.Int, "1"},
  38. {token.Int, "123456789012345678890"},
  39. {token.Int, "01234567"},
  40. {token.Int, "0xcafebabe"},
  41. {token.Float, "0."},
  42. {token.Float, ".0"},
  43. {token.Float, "3.14159265"},
  44. {token.Float, "1e0"},
  45. {token.Float, "1e+100"},
  46. {token.Float, "1e-100"},
  47. {token.Float, "2.71828e-1000"},
  48. {token.Char, "'a'"},
  49. {token.Char, "'\\000'"},
  50. {token.Char, "'\\xFF'"},
  51. {token.Char, "'\\uff16'"},
  52. {token.Char, "'\\U0000ff16'"},
  53. {token.String, "`foobar`"},
  54. {token.String, "`" + `foo
  55. bar` +
  56. "`",
  57. },
  58. {token.String, "`\r`"},
  59. {token.String, "`foo\r\nbar`"},
  60. {token.Add, "+"},
  61. {token.Sub, "-"},
  62. {token.Mul, "*"},
  63. {token.Quo, "/"},
  64. {token.Rem, "%"},
  65. {token.And, "&"},
  66. {token.Or, "|"},
  67. {token.Xor, "^"},
  68. {token.Shl, "<<"},
  69. {token.Shr, ">>"},
  70. {token.AndNot, "&^"},
  71. {token.AddAssign, "+="},
  72. {token.SubAssign, "-="},
  73. {token.MulAssign, "*="},
  74. {token.QuoAssign, "/="},
  75. {token.RemAssign, "%="},
  76. {token.AndAssign, "&="},
  77. {token.OrAssign, "|="},
  78. {token.XorAssign, "^="},
  79. {token.ShlAssign, "<<="},
  80. {token.ShrAssign, ">>="},
  81. {token.AndNotAssign, "&^="},
  82. {token.LAnd, "&&"},
  83. {token.LOr, "||"},
  84. {token.Inc, "++"},
  85. {token.Dec, "--"},
  86. {token.Equal, "=="},
  87. {token.Less, "<"},
  88. {token.Greater, ">"},
  89. {token.Assign, "="},
  90. {token.Not, "!"},
  91. {token.NotEqual, "!="},
  92. {token.LessEq, "<="},
  93. {token.GreaterEq, ">="},
  94. {token.Define, ":="},
  95. {token.Ellipsis, "..."},
  96. {token.LParen, "("},
  97. {token.LBrack, "["},
  98. {token.LBrace, "{"},
  99. {token.Comma, ","},
  100. {token.Period, "."},
  101. {token.RParen, ")"},
  102. {token.RBrack, "]"},
  103. {token.RBrace, "}"},
  104. {token.Semicolon, ";"},
  105. {token.Colon, ":"},
  106. {token.Break, "break"},
  107. {token.Continue, "continue"},
  108. {token.Else, "else"},
  109. {token.For, "for"},
  110. {token.Func, "func"},
  111. {token.If, "if"},
  112. {token.Return, "return"},
  113. {token.Export, "export"},
  114. }
  115. // combine
  116. var lines []string
  117. var lineSum int
  118. lineNos := make([]int, len(testCases))
  119. columnNos := make([]int, len(testCases))
  120. for i, tc := range testCases {
  121. // add 0-2 lines before each test case
  122. emptyLines := rand.Intn(3)
  123. for j := 0; j < emptyLines; j++ {
  124. lines = append(lines, strings.Repeat(" ", rand.Intn(10)))
  125. }
  126. // add test case line with some whitespaces around it
  127. emptyColumns := rand.Intn(10)
  128. lines = append(lines, fmt.Sprintf("%s%s%s",
  129. strings.Repeat(" ", emptyColumns),
  130. tc.literal,
  131. strings.Repeat(" ", rand.Intn(10))))
  132. lineNos[i] = lineSum + emptyLines + 1
  133. lineSum += emptyLines + countLines(tc.literal)
  134. columnNos[i] = emptyColumns + 1
  135. }
  136. // expected results
  137. var expected []scanResult
  138. var expectedSkipComments []scanResult
  139. for i, tc := range testCases {
  140. // expected literal
  141. var expectedLiteral string
  142. switch tc.token {
  143. case token.Comment:
  144. // strip CRs in comments
  145. expectedLiteral = string(parser.StripCR([]byte(tc.literal),
  146. tc.literal[1] == '*'))
  147. //-style comment literal doesn't contain newline
  148. if expectedLiteral[1] == '/' {
  149. expectedLiteral = expectedLiteral[:len(expectedLiteral)-1]
  150. }
  151. case token.Ident:
  152. expectedLiteral = tc.literal
  153. case token.Semicolon:
  154. expectedLiteral = ";"
  155. default:
  156. if tc.token.IsLiteral() {
  157. // strip CRs in raw string
  158. expectedLiteral = tc.literal
  159. if expectedLiteral[0] == '`' {
  160. expectedLiteral = string(parser.StripCR(
  161. []byte(expectedLiteral), false))
  162. }
  163. } else if tc.token.IsKeyword() {
  164. expectedLiteral = tc.literal
  165. }
  166. }
  167. res := scanResult{
  168. Token: tc.token,
  169. Literal: expectedLiteral,
  170. Line: lineNos[i],
  171. Column: columnNos[i],
  172. }
  173. expected = append(expected, res)
  174. if tc.token != token.Comment {
  175. expectedSkipComments = append(expectedSkipComments, res)
  176. }
  177. }
  178. scanExpect(t, strings.Join(lines, "\n"),
  179. parser.ScanComments|parser.DontInsertSemis, expected...)
  180. scanExpect(t, strings.Join(lines, "\n"),
  181. parser.DontInsertSemis, expectedSkipComments...)
  182. }
  183. func TestStripCR(t *testing.T) {
  184. for _, tc := range []struct {
  185. input string
  186. expect string
  187. }{
  188. {"//\n", "//\n"},
  189. {"//\r\n", "//\n"},
  190. {"//\r\r\r\n", "//\n"},
  191. {"//\r*\r/\r\n", "//*/\n"},
  192. {"/**/", "/**/"},
  193. {"/*\r/*/", "/*/*/"},
  194. {"/*\r*/", "/**/"},
  195. {"/**\r/*/", "/**\r/*/"},
  196. {"/*\r/\r*\r/*/", "/*/*\r/*/"},
  197. {"/*\r\r\r\r*/", "/**/"},
  198. } {
  199. actual := string(parser.StripCR([]byte(tc.input),
  200. len(tc.input) >= 2 && tc.input[1] == '*'))
  201. require.Equal(t, tc.expect, actual)
  202. }
  203. }
  204. func scanExpect(
  205. t *testing.T,
  206. input string,
  207. mode parser.ScanMode,
  208. expected ...scanResult,
  209. ) {
  210. testFile := testFileSet.AddFile("test", -1, len(input))
  211. s := parser.NewScanner(
  212. testFile,
  213. []byte(input),
  214. func(_ parser.SourceFilePos, msg string) { require.Fail(t, msg) },
  215. mode)
  216. for idx, e := range expected {
  217. tok, literal, pos := s.Scan()
  218. filePos := testFile.Position(pos)
  219. require.Equal(t, e.Token, tok, "[%d] expected: %s, actual: %s",
  220. idx, e.Token.String(), tok.String())
  221. require.Equal(t, e.Literal, literal)
  222. require.Equal(t, e.Line, filePos.Line)
  223. require.Equal(t, e.Column, filePos.Column)
  224. }
  225. tok, _, _ := s.Scan()
  226. require.Equal(t, token.EOF, tok, "more tokens left")
  227. require.Equal(t, 0, s.ErrorCount())
  228. }
  229. func countLines(s string) int {
  230. if s == "" {
  231. return 0
  232. }
  233. n := 1
  234. for i := 0; i < len(s); i++ {
  235. if s[i] == '\n' {
  236. n++
  237. }
  238. }
  239. return n
  240. }
  241. func init() {
  242. rand.Seed(time.Now().UnixNano())
  243. }