1 package unstable
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7 "testing"
8
9 "github.com/stretchr/testify/require"
10 )
11
12 func TestParser_AST_Numbers(t *testing.T) {
13 examples := []struct {
14 desc string
15 input string
16 kind Kind
17 err bool
18 }{
19 {
20 desc: "integer just digits",
21 input: `1234`,
22 kind: Integer,
23 },
24 {
25 desc: "integer zero",
26 input: `0`,
27 kind: Integer,
28 },
29 {
30 desc: "integer sign",
31 input: `+99`,
32 kind: Integer,
33 },
34 {
35 desc: "integer hex uppercase",
36 input: `0xDEADBEEF`,
37 kind: Integer,
38 },
39 {
40 desc: "integer hex lowercase",
41 input: `0xdead_beef`,
42 kind: Integer,
43 },
44 {
45 desc: "integer octal",
46 input: `0o01234567`,
47 kind: Integer,
48 },
49 {
50 desc: "integer binary",
51 input: `0b11010110`,
52 kind: Integer,
53 },
54 {
55 desc: "float zero",
56 input: `0.0`,
57 kind: Float,
58 },
59 {
60 desc: "float positive zero",
61 input: `+0.0`,
62 kind: Float,
63 },
64 {
65 desc: "float negative zero",
66 input: `-0.0`,
67 kind: Float,
68 },
69 {
70 desc: "float pi",
71 input: `3.1415`,
72 kind: Float,
73 },
74 {
75 desc: "float negative",
76 input: `-0.01`,
77 kind: Float,
78 },
79 {
80 desc: "float signed exponent",
81 input: `5e+22`,
82 kind: Float,
83 },
84 {
85 desc: "float exponent lowercase",
86 input: `1e06`,
87 kind: Float,
88 },
89 {
90 desc: "float exponent uppercase",
91 input: `-2E-2`,
92 kind: Float,
93 },
94 {
95 desc: "float fractional with exponent",
96 input: `6.626e-34`,
97 kind: Float,
98 },
99 {
100 desc: "float underscores",
101 input: `224_617.445_991_228`,
102 kind: Float,
103 },
104 {
105 desc: "inf",
106 input: `inf`,
107 kind: Float,
108 },
109 {
110 desc: "inf negative",
111 input: `-inf`,
112 kind: Float,
113 },
114 {
115 desc: "inf positive",
116 input: `+inf`,
117 kind: Float,
118 },
119 {
120 desc: "nan",
121 input: `nan`,
122 kind: Float,
123 },
124 {
125 desc: "nan negative",
126 input: `-nan`,
127 kind: Float,
128 },
129 {
130 desc: "nan positive",
131 input: `+nan`,
132 kind: Float,
133 },
134 }
135
136 for _, e := range examples {
137 e := e
138 t.Run(e.desc, func(t *testing.T) {
139 p := Parser{}
140 p.Reset([]byte(`A = ` + e.input))
141 p.NextExpression()
142 err := p.Error()
143 if e.err {
144 require.Error(t, err)
145 } else {
146 require.NoError(t, err)
147
148 expected := astNode{
149 Kind: KeyValue,
150 Children: []astNode{
151 {Kind: e.kind, Data: []byte(e.input)},
152 {Kind: Key, Data: []byte(`A`)},
153 },
154 }
155 compareNode(t, expected, p.Expression())
156 }
157 })
158 }
159 }
160
161 type (
162 astNode struct {
163 Kind Kind
164 Data []byte
165 Children []astNode
166 }
167 )
168
169 func compareNode(t *testing.T, e astNode, n *Node) {
170 t.Helper()
171 require.Equal(t, e.Kind, n.Kind)
172 require.Equal(t, e.Data, n.Data)
173
174 compareIterator(t, e.Children, n.Children())
175 }
176
177 func compareIterator(t *testing.T, expected []astNode, actual Iterator) {
178 t.Helper()
179 idx := 0
180
181 for actual.Next() {
182 n := actual.Node()
183
184 if idx >= len(expected) {
185 t.Fatal("extra child in actual tree")
186 }
187 e := expected[idx]
188
189 compareNode(t, e, n)
190
191 idx++
192 }
193
194 if idx < len(expected) {
195 t.Fatal("missing children in actual", "idx =", idx, "expected =", len(expected))
196 }
197 }
198
199
200 func TestParser_AST(t *testing.T) {
201 examples := []struct {
202 desc string
203 input string
204 ast astNode
205 err bool
206 }{
207 {
208 desc: "simple string assignment",
209 input: `A = "hello"`,
210 ast: astNode{
211 Kind: KeyValue,
212 Children: []astNode{
213 {
214 Kind: String,
215 Data: []byte(`hello`),
216 },
217 {
218 Kind: Key,
219 Data: []byte(`A`),
220 },
221 },
222 },
223 },
224 {
225 desc: "simple bool assignment",
226 input: `A = true`,
227 ast: astNode{
228 Kind: KeyValue,
229 Children: []astNode{
230 {
231 Kind: Bool,
232 Data: []byte(`true`),
233 },
234 {
235 Kind: Key,
236 Data: []byte(`A`),
237 },
238 },
239 },
240 },
241 {
242 desc: "array of strings",
243 input: `A = ["hello", ["world", "again"]]`,
244 ast: astNode{
245 Kind: KeyValue,
246 Children: []astNode{
247 {
248 Kind: Array,
249 Children: []astNode{
250 {
251 Kind: String,
252 Data: []byte(`hello`),
253 },
254 {
255 Kind: Array,
256 Children: []astNode{
257 {
258 Kind: String,
259 Data: []byte(`world`),
260 },
261 {
262 Kind: String,
263 Data: []byte(`again`),
264 },
265 },
266 },
267 },
268 },
269 {
270 Kind: Key,
271 Data: []byte(`A`),
272 },
273 },
274 },
275 },
276 {
277 desc: "array of arrays of strings",
278 input: `A = ["hello", "world"]`,
279 ast: astNode{
280 Kind: KeyValue,
281 Children: []astNode{
282 {
283 Kind: Array,
284 Children: []astNode{
285 {
286 Kind: String,
287 Data: []byte(`hello`),
288 },
289 {
290 Kind: String,
291 Data: []byte(`world`),
292 },
293 },
294 },
295 {
296 Kind: Key,
297 Data: []byte(`A`),
298 },
299 },
300 },
301 },
302 {
303 desc: "inline table",
304 input: `name = { first = "Tom", last = "Preston-Werner" }`,
305 ast: astNode{
306 Kind: KeyValue,
307 Children: []astNode{
308 {
309 Kind: InlineTable,
310 Children: []astNode{
311 {
312 Kind: KeyValue,
313 Children: []astNode{
314 {Kind: String, Data: []byte(`Tom`)},
315 {Kind: Key, Data: []byte(`first`)},
316 },
317 },
318 {
319 Kind: KeyValue,
320 Children: []astNode{
321 {Kind: String, Data: []byte(`Preston-Werner`)},
322 {Kind: Key, Data: []byte(`last`)},
323 },
324 },
325 },
326 },
327 {
328 Kind: Key,
329 Data: []byte(`name`),
330 },
331 },
332 },
333 },
334 }
335
336 for _, e := range examples {
337 e := e
338 t.Run(e.desc, func(t *testing.T) {
339 p := Parser{}
340 p.Reset([]byte(e.input))
341 p.NextExpression()
342 err := p.Error()
343 if e.err {
344 require.Error(t, err)
345 } else {
346 require.NoError(t, err)
347 compareNode(t, e.ast, p.Expression())
348 }
349 })
350 }
351 }
352
353 func BenchmarkParseBasicStringWithUnicode(b *testing.B) {
354 p := &Parser{}
355 b.Run("4", func(b *testing.B) {
356 input := []byte(`"\u1234\u5678\u9ABC\u1234\u5678\u9ABC"`)
357 b.ReportAllocs()
358 b.SetBytes(int64(len(input)))
359
360 for i := 0; i < b.N; i++ {
361 p.parseBasicString(input)
362 }
363 })
364 b.Run("8", func(b *testing.B) {
365 input := []byte(`"\u12345678\u9ABCDEF0\u12345678\u9ABCDEF0"`)
366 b.ReportAllocs()
367 b.SetBytes(int64(len(input)))
368
369 for i := 0; i < b.N; i++ {
370 p.parseBasicString(input)
371 }
372 })
373 }
374
375 func BenchmarkParseBasicStringsEasy(b *testing.B) {
376 p := &Parser{}
377
378 for _, size := range []int{1, 4, 8, 16, 21} {
379 b.Run(strconv.Itoa(size), func(b *testing.B) {
380 input := []byte(`"` + strings.Repeat("A", size) + `"`)
381
382 b.ReportAllocs()
383 b.SetBytes(int64(len(input)))
384
385 for i := 0; i < b.N; i++ {
386 p.parseBasicString(input)
387 }
388 })
389 }
390 }
391
392 func TestParser_AST_DateTimes(t *testing.T) {
393 examples := []struct {
394 desc string
395 input string
396 kind Kind
397 err bool
398 }{
399 {
400 desc: "offset-date-time with delim 'T' and UTC offset",
401 input: `2021-07-21T12:08:05Z`,
402 kind: DateTime,
403 },
404 {
405 desc: "offset-date-time with space delim and +8hours offset",
406 input: `2021-07-21 12:08:05+08:00`,
407 kind: DateTime,
408 },
409 {
410 desc: "local-date-time with nano second",
411 input: `2021-07-21T12:08:05.666666666`,
412 kind: LocalDateTime,
413 },
414 {
415 desc: "local-date-time",
416 input: `2021-07-21T12:08:05`,
417 kind: LocalDateTime,
418 },
419 {
420 desc: "local-date",
421 input: `2021-07-21`,
422 kind: LocalDate,
423 },
424 }
425
426 for _, e := range examples {
427 e := e
428 t.Run(e.desc, func(t *testing.T) {
429 p := Parser{}
430 p.Reset([]byte(`A = ` + e.input))
431 p.NextExpression()
432 err := p.Error()
433 if e.err {
434 require.Error(t, err)
435 } else {
436 require.NoError(t, err)
437
438 expected := astNode{
439 Kind: KeyValue,
440 Children: []astNode{
441 {Kind: e.kind, Data: []byte(e.input)},
442 {Kind: Key, Data: []byte(`A`)},
443 },
444 }
445 compareNode(t, expected, p.Expression())
446 }
447 })
448 }
449 }
450
451
452
453
454
455
456
457
458
459
460 func ExampleParser_comments() {
461 doc := `# Top of the document comment.
462 # Optional, any amount of lines.
463
464 # Above table.
465 [table] # Next to table.
466 # Above simple value.
467 key = "value" # Next to simple value.
468 # Below simple value.
469
470 # Some comment alone.
471
472 # Multiple comments, on multiple lines.
473
474 # Above inline table.
475 name = { first = "Tom", last = "Preston-Werner" } # Next to inline table.
476 # Below inline table.
477
478 # Above array.
479 array = [ 1, 2, 3 ] # Next to one-line array.
480 # Below array.
481
482 # Above multi-line array.
483 key5 = [ # Next to start of inline array.
484 # Second line before array content.
485 1, # Next to first element.
486 # After first element.
487 # Before second element.
488 2,
489 3, # Next to last element
490 # After last element.
491 ] # Next to end of array.
492 # Below multi-line array.
493
494 # Before array table.
495 [[products]] # Next to array table.
496 # After array table.
497 `
498
499 var printGeneric func(*Parser, int, *Node)
500 printGeneric = func(p *Parser, indent int, e *Node) {
501 if e == nil {
502 return
503 }
504 s := p.Shape(e.Raw)
505 x := fmt.Sprintf("%d:%d->%d:%d (%d->%d)", s.Start.Line, s.Start.Column, s.End.Line, s.End.Column, s.Start.Offset, s.End.Offset)
506 fmt.Printf("%-25s | %s%s [%s]\n", x, strings.Repeat(" ", indent), e.Kind, e.Data)
507 printGeneric(p, indent+1, e.Child())
508 printGeneric(p, indent, e.Next())
509 }
510
511 printTree := func(p *Parser) {
512 for p.NextExpression() {
513 e := p.Expression()
514 fmt.Println("---")
515 printGeneric(p, 0, e)
516 }
517 if err := p.Error(); err != nil {
518 panic(err)
519 }
520 }
521
522 p := &Parser{
523 KeepComments: true,
524 }
525 p.Reset([]byte(doc))
526 printTree(p)
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606 }
607
608 func ExampleParser() {
609 doc := `
610 hello = "world"
611 value = 42
612 `
613 p := Parser{}
614 p.Reset([]byte(doc))
615 for p.NextExpression() {
616 e := p.Expression()
617 fmt.Printf("Expression: %s\n", e.Kind)
618 value := e.Value()
619 it := e.Key()
620 k := it.Node()
621 fmt.Printf("%s -> (%s) %s\n", k.Data, value.Kind, value.Data)
622 }
623
624
625
626
627
628
629 }
630
View as plain text