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  }