1package query
2
3import (
4	"github.com/pelletier/go-toml"
5	"testing"
6)
7
8func testQLFlow(t *testing.T, input string, expectedFlow []token) {
9	ch := lexQuery(input)
10	for idx, expected := range expectedFlow {
11		token := <-ch
12		if token != expected {
13			t.Log("While testing #", idx, ":", input)
14			t.Log("compared (got)", token, "to (expected)", expected)
15			t.Log("\tvalue:", token.val, "<->", expected.val)
16			t.Log("\tvalue as bytes:", []byte(token.val), "<->", []byte(expected.val))
17			t.Log("\ttype:", token.typ.String(), "<->", expected.typ.String())
18			t.Log("\tline:", token.Line, "<->", expected.Line)
19			t.Log("\tcolumn:", token.Col, "<->", expected.Col)
20			t.Log("compared", token, "to", expected)
21			t.FailNow()
22		}
23	}
24
25	tok, ok := <-ch
26	if ok {
27		t.Log("channel is not closed!")
28		t.Log(len(ch)+1, "tokens remaining:")
29
30		t.Log("token ->", tok)
31		for token := range ch {
32			t.Log("token ->", token)
33		}
34		t.FailNow()
35	}
36}
37
38func TestLexSpecialChars(t *testing.T) {
39	testQLFlow(t, " .$[]..()?*", []token{
40		{toml.Position{1, 2}, tokenDot, "."},
41		{toml.Position{1, 3}, tokenDollar, "$"},
42		{toml.Position{1, 4}, tokenLeftBracket, "["},
43		{toml.Position{1, 5}, tokenRightBracket, "]"},
44		{toml.Position{1, 6}, tokenDotDot, ".."},
45		{toml.Position{1, 8}, tokenLeftParen, "("},
46		{toml.Position{1, 9}, tokenRightParen, ")"},
47		{toml.Position{1, 10}, tokenQuestion, "?"},
48		{toml.Position{1, 11}, tokenStar, "*"},
49		{toml.Position{1, 12}, tokenEOF, ""},
50	})
51}
52
53func TestLexString(t *testing.T) {
54	testQLFlow(t, "'foo\n'", []token{
55		{toml.Position{1, 2}, tokenString, "foo\n"},
56		{toml.Position{2, 2}, tokenEOF, ""},
57	})
58}
59
60func TestLexDoubleString(t *testing.T) {
61	testQLFlow(t, `"bar"`, []token{
62		{toml.Position{1, 2}, tokenString, "bar"},
63		{toml.Position{1, 6}, tokenEOF, ""},
64	})
65}
66
67func TestLexStringEscapes(t *testing.T) {
68	testQLFlow(t, `"foo \" \' \b \f \/ \t \r \\ \u03A9 \U00012345 \n bar"`, []token{
69		{toml.Position{1, 2}, tokenString, "foo \" ' \b \f / \t \r \\ \u03A9 \U00012345 \n bar"},
70		{toml.Position{1, 55}, tokenEOF, ""},
71	})
72}
73
74func TestLexStringUnfinishedUnicode4(t *testing.T) {
75	testQLFlow(t, `"\u000"`, []token{
76		{toml.Position{1, 2}, tokenError, "unfinished unicode escape"},
77	})
78}
79
80func TestLexStringUnfinishedUnicode8(t *testing.T) {
81	testQLFlow(t, `"\U0000"`, []token{
82		{toml.Position{1, 2}, tokenError, "unfinished unicode escape"},
83	})
84}
85
86func TestLexStringInvalidEscape(t *testing.T) {
87	testQLFlow(t, `"\x"`, []token{
88		{toml.Position{1, 2}, tokenError, "invalid escape sequence: \\x"},
89	})
90}
91
92func TestLexStringUnfinished(t *testing.T) {
93	testQLFlow(t, `"bar`, []token{
94		{toml.Position{1, 2}, tokenError, "unclosed string"},
95	})
96}
97
98func TestLexKey(t *testing.T) {
99	testQLFlow(t, "foo", []token{
100		{toml.Position{1, 1}, tokenKey, "foo"},
101		{toml.Position{1, 4}, tokenEOF, ""},
102	})
103}
104
105func TestLexRecurse(t *testing.T) {
106	testQLFlow(t, "$..*", []token{
107		{toml.Position{1, 1}, tokenDollar, "$"},
108		{toml.Position{1, 2}, tokenDotDot, ".."},
109		{toml.Position{1, 4}, tokenStar, "*"},
110		{toml.Position{1, 5}, tokenEOF, ""},
111	})
112}
113
114func TestLexBracketKey(t *testing.T) {
115	testQLFlow(t, "$[foo]", []token{
116		{toml.Position{1, 1}, tokenDollar, "$"},
117		{toml.Position{1, 2}, tokenLeftBracket, "["},
118		{toml.Position{1, 3}, tokenKey, "foo"},
119		{toml.Position{1, 6}, tokenRightBracket, "]"},
120		{toml.Position{1, 7}, tokenEOF, ""},
121	})
122}
123
124func TestLexSpace(t *testing.T) {
125	testQLFlow(t, "foo bar baz", []token{
126		{toml.Position{1, 1}, tokenKey, "foo"},
127		{toml.Position{1, 5}, tokenKey, "bar"},
128		{toml.Position{1, 9}, tokenKey, "baz"},
129		{toml.Position{1, 12}, tokenEOF, ""},
130	})
131}
132
133func TestLexInteger(t *testing.T) {
134	testQLFlow(t, "100 +200 -300", []token{
135		{toml.Position{1, 1}, tokenInteger, "100"},
136		{toml.Position{1, 5}, tokenInteger, "+200"},
137		{toml.Position{1, 10}, tokenInteger, "-300"},
138		{toml.Position{1, 14}, tokenEOF, ""},
139	})
140}
141
142func TestLexFloat(t *testing.T) {
143	testQLFlow(t, "100.0 +200.0 -300.0", []token{
144		{toml.Position{1, 1}, tokenFloat, "100.0"},
145		{toml.Position{1, 7}, tokenFloat, "+200.0"},
146		{toml.Position{1, 14}, tokenFloat, "-300.0"},
147		{toml.Position{1, 20}, tokenEOF, ""},
148	})
149}
150
151func TestLexFloatWithMultipleDots(t *testing.T) {
152	testQLFlow(t, "4.2.", []token{
153		{toml.Position{1, 1}, tokenError, "cannot have two dots in one float"},
154	})
155}
156
157func TestLexFloatLeadingDot(t *testing.T) {
158	testQLFlow(t, "+.1", []token{
159		{toml.Position{1, 1}, tokenError, "cannot start float with a dot"},
160	})
161}
162
163func TestLexFloatWithTrailingDot(t *testing.T) {
164	testQLFlow(t, "42.", []token{
165		{toml.Position{1, 1}, tokenError, "float cannot end with a dot"},
166	})
167}
168
169func TestLexNumberWithoutDigit(t *testing.T) {
170	testQLFlow(t, "+", []token{
171		{toml.Position{1, 1}, tokenError, "no digit in that number"},
172	})
173}
174
175func TestLexUnknown(t *testing.T) {
176	testQLFlow(t, "^", []token{
177		{toml.Position{1, 1}, tokenError, "unexpected char: '94'"},
178	})
179}
180