github.com/MontFerret/ferret@v0.18.0/pkg/compiler/compiler_member_test.go (about) 1 package compiler_test 2 3 import ( 4 "context" 5 "fmt" 6 "regexp" 7 "strconv" 8 "strings" 9 "testing" 10 11 "github.com/MontFerret/ferret/pkg/parser" 12 "github.com/MontFerret/ferret/pkg/runtime/core" 13 14 . "github.com/smartystreets/goconvey/convey" 15 16 "github.com/MontFerret/ferret/pkg/compiler" 17 ) 18 19 func TestMember(t *testing.T) { 20 Convey("Computed properties", t, func() { 21 Convey("Array by literal", func() { 22 c := compiler.New() 23 24 p, err := c.Compile(` 25 LET arr = [1,2,3,4] 26 27 RETURN arr[1] 28 `) 29 30 So(err, ShouldBeNil) 31 32 out, err := p.Run(context.Background()) 33 34 So(err, ShouldBeNil) 35 36 So(string(out), ShouldEqual, `2`) 37 }) 38 39 Convey("Array by variable", func() { 40 c := compiler.New() 41 42 p, err := c.Compile(` 43 LET arr = [1,2,3,4] 44 LET idx = 1 45 46 RETURN arr[idx] 47 `) 48 49 So(err, ShouldBeNil) 50 51 out, err := p.Run(context.Background()) 52 53 So(err, ShouldBeNil) 54 55 So(string(out), ShouldEqual, `2`) 56 }) 57 58 Convey("Object by literal", func() { 59 c := compiler.New() 60 61 p, err := c.Compile(` 62 LET obj = { foo: "bar", qaz: "wsx"} 63 64 RETURN obj["qaz"] 65 `) 66 67 So(err, ShouldBeNil) 68 69 out, err := p.Run(context.Background()) 70 71 So(err, ShouldBeNil) 72 73 So(string(out), ShouldEqual, `"wsx"`) 74 }) 75 76 Convey("Object by literal with property defined as a string", func() { 77 c := compiler.New() 78 79 p, err := c.Compile(` 80 LET obj = { "foo": "bar", "qaz": "wsx"} 81 82 RETURN obj["qaz"] 83 `) 84 85 So(err, ShouldBeNil) 86 87 out, err := p.Run(context.Background()) 88 89 So(err, ShouldBeNil) 90 91 So(string(out), ShouldEqual, `"wsx"`) 92 }) 93 94 Convey("Object by literal with property defined as a multi line string", func() { 95 c := compiler.New() 96 97 p, err := c.Compile(fmt.Sprintf(` 98 LET obj = { "foo": "bar", %s: "wsx"} 99 100 RETURN obj["qaz"] 101 `, "`qaz`")) 102 103 So(err, ShouldBeNil) 104 105 out, err := p.Run(context.Background()) 106 107 So(err, ShouldBeNil) 108 109 So(string(out), ShouldEqual, `"wsx"`) 110 }) 111 112 Convey("Object by variable", func() { 113 c := compiler.New() 114 115 p, err := c.Compile(` 116 LET obj = { foo: "bar", qaz: "wsx"} 117 LET key = "qaz" 118 119 RETURN obj[key] 120 `) 121 122 So(err, ShouldBeNil) 123 124 out, err := p.Run(context.Background()) 125 126 So(err, ShouldBeNil) 127 128 So(string(out), ShouldEqual, `"wsx"`) 129 }) 130 131 Convey("ObjectDecl by literal", func() { 132 c := compiler.New() 133 134 p, err := c.Compile(` 135 RETURN { foo: "bar" }.foo 136 `) 137 So(err, ShouldBeNil) 138 139 out, err := p.Run(context.Background()) 140 So(err, ShouldBeNil) 141 142 So(string(out), ShouldEqual, `"bar"`) 143 }) 144 145 Convey("ObjectDecl by literal passed to func call", func() { 146 c := compiler.New() 147 148 p, err := c.Compile(` 149 RETURN KEEP_KEYS({first: {second: "third"}}.first, "second") 150 `) 151 So(err, ShouldBeNil) 152 153 out, err := p.Run(context.Background()) 154 So(err, ShouldBeNil) 155 156 So(string(out), ShouldEqual, `{"second":"third"}`) 157 }) 158 159 Convey("ObjectDecl by literal as forSource", func() { 160 c := compiler.New() 161 162 p, err := c.Compile(` 163 FOR v, k IN {f: {foo: "bar"}}.f 164 RETURN [k, v] 165 `) 166 So(err, ShouldBeNil) 167 168 out, err := p.Run(context.Background()) 169 So(err, ShouldBeNil) 170 171 So(string(out), ShouldEqual, `[["foo","bar"]]`) 172 }) 173 174 Convey("ObjectDecl by literal as expression", func() { 175 c := compiler.New() 176 177 p, err := c.Compile(` 178 LET inexp = 1 IN {'foo': [1]}.foo 179 LET ternaryexp = FALSE ? TRUE : {foo: TRUE}.foo 180 RETURN inexp && ternaryexp 181 `) 182 So(err, ShouldBeNil) 183 184 out, err := p.Run(context.Background()) 185 So(err, ShouldBeNil) 186 187 So(string(out), ShouldEqual, `true`) 188 }) 189 190 Convey("ArrayDecl by literal", func() { 191 c := compiler.New() 192 193 p, err := c.Compile(` 194 RETURN ["bar", "foo"][0] 195 `) 196 So(err, ShouldBeNil) 197 198 out, err := p.Run(context.Background()) 199 So(err, ShouldBeNil) 200 201 So(string(out), ShouldEqual, `"bar"`) 202 }) 203 204 Convey("ArrayDecl by literal passed to func call", func() { 205 c := compiler.New() 206 207 p, err := c.Compile(` 208 RETURN FIRST([[1, 2]][0]) 209 `) 210 So(err, ShouldBeNil) 211 212 out, err := p.Run(context.Background()) 213 So(err, ShouldBeNil) 214 215 So(string(out), ShouldEqual, `1`) 216 }) 217 218 Convey("ArrayDecl by literal as forSource", func() { 219 c := compiler.New() 220 221 p, err := c.Compile(` 222 FOR i IN [[1, 2]][0] 223 RETURN i 224 `) 225 So(err, ShouldBeNil) 226 227 out, err := p.Run(context.Background()) 228 So(err, ShouldBeNil) 229 230 So(string(out), ShouldEqual, `[1,2]`) 231 }) 232 233 Convey("ArrayDecl by literal as expression", func() { 234 c := compiler.New() 235 236 p, err := c.Compile(` 237 LET inexp = 1 IN [[1]][0] 238 LET ternaryexp = FALSE ? TRUE : [TRUE][0] 239 RETURN inexp && ternaryexp 240 `) 241 So(err, ShouldBeNil) 242 243 out, err := p.Run(context.Background()) 244 So(err, ShouldBeNil) 245 246 So(string(out), ShouldEqual, `true`) 247 }) 248 249 Convey("Deep path", func() { 250 c := compiler.New() 251 252 p, err := c.Compile(` 253 LET obj = { 254 first: { 255 second: { 256 third: { 257 fourth: { 258 fifth: { 259 bottom: true 260 } 261 } 262 } 263 } 264 } 265 } 266 267 RETURN obj.first.second.third.fourth.fifth.bottom 268 `) 269 270 So(err, ShouldBeNil) 271 272 out, err := p.Run(context.Background()) 273 274 So(err, ShouldBeNil) 275 276 So(string(out), ShouldEqual, `true`) 277 }) 278 279 Convey("Deep computed path", func() { 280 c := compiler.New() 281 282 p := c.MustCompile(` 283 LET o1 = { 284 first: { 285 second: { 286 ["third"]: { 287 fourth: { 288 fifth: { 289 bottom: true 290 } 291 } 292 } 293 } 294 } 295 } 296 297 LET o2 = { prop: "third" } 298 299 RETURN o1["first"]["second"][o2.prop]["fourth"]["fifth"].bottom 300 `) 301 302 out, err := p.Run(context.Background()) 303 304 So(err, ShouldBeNil) 305 306 So(string(out), ShouldEqual, `true`) 307 }) 308 309 Convey("Deep computed path 2", func() { 310 c := compiler.New() 311 312 p := c.MustCompile(` 313 LET o1 = { 314 first: { 315 second: { 316 third: { 317 fourth: { 318 fifth: { 319 bottom: true 320 } 321 } 322 } 323 } 324 } 325 } 326 327 LET o2 = { prop: "third" } 328 329 RETURN o1.first["second"][o2.prop].fourth["fifth"]["bottom"] 330 `) 331 332 out, err := p.Run(context.Background()) 333 334 So(err, ShouldBeNil) 335 336 So(string(out), ShouldEqual, `true`) 337 }) 338 339 Convey("Prop after a func call", func() { 340 c := compiler.New() 341 342 p, err := c.Compile(` 343 LET arr = [{ name: "Bob" }] 344 345 RETURN FIRST(arr).name 346 `) 347 348 So(err, ShouldBeNil) 349 350 out, err := p.Run(context.Background()) 351 352 So(err, ShouldBeNil) 353 354 So(string(out), ShouldEqual, `"Bob"`) 355 }) 356 357 Convey("Computed prop after a func call", func() { 358 c := compiler.New() 359 360 p, err := c.Compile(` 361 LET arr = [{ name: { first: "Bob" } }] 362 363 RETURN FIRST(arr)['name'].first 364 `) 365 366 So(err, ShouldBeNil) 367 368 out, err := p.Run(context.Background()) 369 370 So(err, ShouldBeNil) 371 372 So(string(out), ShouldEqual, `"Bob"`) 373 }) 374 375 Convey("Computed property with quotes", func() { 376 c := compiler.New() 377 378 p := c.MustCompile(` 379 LET obj = { 380 attributes: { 381 'data-index': 1 382 } 383 } 384 385 RETURN obj.attributes['data-index'] 386 `) 387 388 out, err := p.Run(context.Background()) 389 390 So(err, ShouldBeNil) 391 392 So(string(out), ShouldEqual, "1") 393 }) 394 }) 395 396 Convey("Optional chaining", t, func() { 397 Convey("Object", func() { 398 Convey("When value does not exist", func() { 399 c := compiler.New() 400 401 p, err := c.Compile(` 402 LET obj = { foo: None } 403 404 RETURN obj.foo?.bar 405 `) 406 407 So(err, ShouldBeNil) 408 409 out, err := p.Run(context.Background()) 410 411 So(err, ShouldBeNil) 412 413 So(string(out), ShouldEqual, `null`) 414 }) 415 416 Convey("When value does exists", func() { 417 c := compiler.New() 418 419 p, err := c.Compile(` 420 LET obj = { foo: { bar: "bar" } } 421 422 RETURN obj.foo?.bar 423 `) 424 425 So(err, ShouldBeNil) 426 427 out, err := p.Run(context.Background()) 428 429 So(err, ShouldBeNil) 430 431 So(string(out), ShouldEqual, `"bar"`) 432 }) 433 }) 434 435 Convey("Array", func() { 436 Convey("When value does not exist", func() { 437 c := compiler.New() 438 439 p, err := c.Compile(` 440 LET obj = { foo: None } 441 442 RETURN obj.foo?.bar?.[0] 443 `) 444 445 So(err, ShouldBeNil) 446 447 out, err := p.Run(context.Background()) 448 449 So(err, ShouldBeNil) 450 451 So(string(out), ShouldEqual, `null`) 452 }) 453 454 Convey("When value does exists", func() { 455 c := compiler.New() 456 457 p, err := c.Compile(` 458 LET obj = { foo: { bar: ["bar"] } } 459 460 RETURN obj.foo?.bar?.[0] 461 `) 462 463 So(err, ShouldBeNil) 464 465 out, err := p.Run(context.Background()) 466 467 So(err, ShouldBeNil) 468 469 So(string(out), ShouldEqual, `"bar"`) 470 }) 471 }) 472 473 Convey("Function", func() { 474 Convey("When value does not exist", func() { 475 c := compiler.New() 476 477 p, err := c.Compile(` 478 RETURN FIRST([])?.foo 479 `) 480 481 So(err, ShouldBeNil) 482 483 out, err := p.Run(context.Background()) 484 485 So(err, ShouldBeNil) 486 487 So(string(out), ShouldEqual, `null`) 488 }) 489 490 Convey("When value does exists", func() { 491 c := compiler.New() 492 493 p, err := c.Compile(` 494 RETURN FIRST([{ foo: "bar" }])?.foo 495 `) 496 497 So(err, ShouldBeNil) 498 499 out, err := p.Run(context.Background()) 500 501 So(err, ShouldBeNil) 502 503 So(string(out), ShouldEqual, `"bar"`) 504 }) 505 506 Convey("When function returns error", func() { 507 c := compiler.New() 508 c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) { 509 return nil, core.ErrNotImplemented 510 }) 511 512 p, err := c.Compile(` 513 RETURN ERROR()?.foo 514 `) 515 516 So(err, ShouldBeNil) 517 518 out, err := p.Run(context.Background()) 519 520 So(err, ShouldBeNil) 521 522 So(string(out), ShouldEqual, `null`) 523 }) 524 }) 525 }) 526 527 Convey("Reserved words as property name", t, func() { 528 p := parser.New("RETURN TRUE") 529 530 r := regexp.MustCompile(`\w+`) 531 532 for idx, l := range p.GetLiteralNames() { 533 if r.MatchString(l) { 534 query := strings.Builder{} 535 query.WriteString("LET o = {\n") 536 query.WriteString(l[1 : len(l)-1]) 537 query.WriteString(":") 538 query.WriteString(strconv.Itoa(idx)) 539 query.WriteString(",\n") 540 query.WriteString("}\n") 541 query.WriteString("RETURN o") 542 543 expected := strings.Builder{} 544 expected.WriteString("{") 545 expected.WriteString(strings.ReplaceAll(l, "'", "\"")) 546 expected.WriteString(":") 547 expected.WriteString(strconv.Itoa(idx)) 548 expected.WriteString("}") 549 550 c := compiler.New() 551 prog, err := c.Compile(query.String()) 552 553 So(err, ShouldBeNil) 554 555 out, err := prog.Run(context.Background()) 556 557 So(err, ShouldBeNil) 558 So(string(out), ShouldEqual, expected.String()) 559 } 560 } 561 }) 562 } 563 564 func BenchmarkMemberArray(b *testing.B) { 565 p := compiler.New().MustCompile(` 566 LET arr = [[[[1]]]] 567 568 RETURN arr[0][0][0][0] 569 `) 570 571 for n := 0; n < b.N; n++ { 572 p.Run(context.Background()) 573 } 574 } 575 576 func BenchmarkMemberObject(b *testing.B) { 577 p := compiler.New().MustCompile(` 578 LET obj = { 579 first: { 580 second: { 581 third: { 582 fourth: { 583 fifth: { 584 bottom: true 585 } 586 } 587 } 588 } 589 } 590 } 591 592 RETURN obj.first.second.third.fourth.fifth.bottom 593 `) 594 595 for n := 0; n < b.N; n++ { 596 p.Run(context.Background()) 597 } 598 } 599 600 func BenchmarkMemberObjectComputed(b *testing.B) { 601 p := compiler.New().MustCompile(` 602 LET obj = { 603 first: { 604 second: { 605 third: { 606 fourth: { 607 fifth: { 608 bottom: true 609 } 610 } 611 } 612 } 613 } 614 } 615 616 RETURN obj["first"]["second"]["third"]["fourth"]["fifth"]["bottom"] 617 `) 618 619 for n := 0; n < b.N; n++ { 620 p.Run(context.Background()) 621 } 622 }