github.com/CiscoM31/godata@v1.0.10/expression_parser_fixture_test.go (about)

     1  package godata
     2  
     3  type expectedParseNode struct {
     4  	Value       string    // The expected token value.
     5  	Type        TokenType // The expected token type.
     6  	Depth       int       // The expected tree depth.
     7  	BooleanExpr bool      // True if this is expected to be a boolean expression.
     8  }
     9  
    10  var testCases = []struct {
    11  	expression    string
    12  	infixTokens   []*Token            // The expected tokens.
    13  	postfixTokens []*Token            // The expected infix tokens.
    14  	tree          []expectedParseNode // The expected tree.
    15  }{
    16  	{
    17  		expression: "fractionalseconds(StartTime) lt 0.123456",
    18  		tree: []expectedParseNode{
    19  			{Value: "lt", Depth: 0, Type: ExpressionTokenLogical},
    20  			{Value: "fractionalseconds", Depth: 1, Type: ExpressionTokenFunc},
    21  			{Value: "StartTime", Depth: 2, Type: ExpressionTokenLiteral},
    22  			{Value: "0.123456", Depth: 1, Type: ExpressionTokenFloat},
    23  		},
    24  	},
    25  	{
    26  		// Test precedence. 'and' has higher precedence compared to 'or'.
    27  		expression: "a or b and c", // same as a or (b and c)
    28  		tree: []expectedParseNode{
    29  			{Value: "or", Depth: 0, Type: ExpressionTokenLogical},
    30  			{Value: "a", Depth: 1, Type: ExpressionTokenLiteral},
    31  			{Value: "and", Depth: 1, Type: ExpressionTokenLogical},
    32  			{Value: "b", Depth: 2, Type: ExpressionTokenLiteral},
    33  			{Value: "c", Depth: 2, Type: ExpressionTokenLiteral},
    34  		},
    35  	},
    36  	{
    37  		// Same expression as above, with explicit parenthesis. The result should be the same
    38  		expression: "a or (b and c)",
    39  		tree: []expectedParseNode{
    40  			{Value: "or", Depth: 0, Type: ExpressionTokenLogical},
    41  			{Value: "a", Depth: 1, Type: ExpressionTokenLiteral},
    42  			{Value: "and", Depth: 1, Type: ExpressionTokenLogical},
    43  			{Value: "b", Depth: 2, Type: ExpressionTokenLiteral},
    44  			{Value: "c", Depth: 2, Type: ExpressionTokenLiteral},
    45  		},
    46  	},
    47  	{
    48  		// Validate precedence between 'and', 'or'.
    49  		expression: "a and b or c",
    50  		tree: []expectedParseNode{
    51  			{Value: "or", Depth: 0, Type: ExpressionTokenLogical},
    52  			{Value: "and", Depth: 1, Type: ExpressionTokenLogical},
    53  			{Value: "a", Depth: 2, Type: ExpressionTokenLiteral},
    54  			{Value: "b", Depth: 2, Type: ExpressionTokenLiteral},
    55  			{Value: "c", Depth: 1, Type: ExpressionTokenLiteral},
    56  		},
    57  	},
    58  	{
    59  		// Validate precedence between assignment and 'or'.
    60  		expression: "a=b or c",
    61  		tree: []expectedParseNode{
    62  			{Value: "=", Depth: 0, Type: ExpressionTokenAssignement},
    63  			{Value: "a", Depth: 1, Type: ExpressionTokenLiteral},
    64  			{Value: "or", Depth: 1, Type: ExpressionTokenLogical},
    65  			{Value: "b", Depth: 2, Type: ExpressionTokenLiteral},
    66  			{Value: "c", Depth: 2, Type: ExpressionTokenLiteral},
    67  		},
    68  	},
    69  	{
    70  		expression: "Address/City eq 'Redmond'",
    71  		tree: []expectedParseNode{
    72  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
    73  			{Value: "/", Depth: 1, Type: ExpressionTokenNav},
    74  			{Value: "Address", Depth: 2, Type: ExpressionTokenLiteral},
    75  			{Value: "City", Depth: 2, Type: ExpressionTokenLiteral},
    76  			{Value: "'Redmond'", Depth: 1, Type: ExpressionTokenString},
    77  		},
    78  	},
    79  	{
    80  		expression: "case(false:0,true:1)",
    81  		tree: []expectedParseNode{
    82  			{Value: "case", Depth: 0, Type: ExpressionTokenCase},
    83  			{Value: "", Depth: 1, Type: ExpressionTokenCasePair},
    84  			{Value: "false", Depth: 2, Type: ExpressionTokenBoolean},
    85  			{Value: "0", Depth: 2, Type: ExpressionTokenInteger},
    86  			{Value: "", Depth: 1, Type: ExpressionTokenCasePair},
    87  			{Value: "true", Depth: 2, Type: ExpressionTokenBoolean},
    88  			{Value: "1", Depth: 2, Type: ExpressionTokenInteger},
    89  		},
    90  	},
    91  	{
    92  		expression: "case(prop eq 'one':1,true:0)",
    93  		tree: []expectedParseNode{
    94  			{Value: "case", Depth: 0, Type: ExpressionTokenCase},
    95  			{Value: "", Depth: 1, Type: ExpressionTokenCasePair},
    96  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
    97  			{Value: "prop", Depth: 3, Type: ExpressionTokenLiteral},
    98  			{Value: "'one'", Depth: 3, Type: ExpressionTokenString},
    99  			{Value: "1", Depth: 2, Type: ExpressionTokenInteger},
   100  			{Value: "", Depth: 1, Type: ExpressionTokenCasePair},
   101  			{Value: "true", Depth: 2, Type: ExpressionTokenBoolean},
   102  			{Value: "0", Depth: 2, Type: ExpressionTokenInteger},
   103  		},
   104  	},
   105  	{
   106  		expression: "case(contains(prop,'val'):0,true:1)",
   107  		tree: []expectedParseNode{
   108  			{Value: "case", Depth: 0, Type: ExpressionTokenCase},
   109  			{Value: "", Depth: 1, Type: ExpressionTokenCasePair},
   110  			{Value: "contains", Depth: 2, Type: ExpressionTokenFunc},
   111  			{Value: "prop", Depth: 3, Type: ExpressionTokenLiteral},
   112  			{Value: "'val'", Depth: 3, Type: ExpressionTokenString},
   113  			{Value: "0", Depth: 2, Type: ExpressionTokenInteger},
   114  			{Value: "", Depth: 1, Type: ExpressionTokenCasePair},
   115  			{Value: "true", Depth: 2, Type: ExpressionTokenBoolean},
   116  			{Value: "1", Depth: 2, Type: ExpressionTokenInteger},
   117  		},
   118  	},
   119  	{
   120  		/*
   121  			{
   122  				"Tags": [
   123  					"Site",
   124  					{ "Key": "Environment" },
   125  					{ "d" : { "d": 123456 }},
   126  					{ "FirstName" : "Bob", "LastName": "Smith"}
   127  				],
   128  				"FullName": "BobSmith"
   129  			}
   130  		*/
   131  
   132  		// The argument of a lambda operator is a case-sensitive lambda variable name followed by a colon (:) and a Boolean expression that
   133  		// uses the lambda variable name to refer to properties of members of the collection identified by the navigation path.
   134  		// If the name chosen for the lambda variable matches a property name of the current resource referenced by the resource path, the lambda variable takes precedence.
   135  		// Clients can prefix properties of the current resource referenced by the resource path with $it.
   136  		// Other path expressions in the Boolean expression neither prefixed with the lambda variable nor $it are evaluated in the scope of
   137  		// the collection instances at the origin of the navigation path prepended to the lambda operator.
   138  		expression: "Tags/any(d:d eq 'Site' or 'Environment' eq d/Key or d/d/d eq 123456 or concat(d/FirstName, d/LastName) eq $it/FullName)",
   139  		tree: []expectedParseNode{
   140  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   141  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   142  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   143  			{Value: "d", Depth: 2, Type: ExpressionTokenLiteral},
   144  			{Value: "or", Depth: 2, Type: ExpressionTokenLogical},
   145  			{Value: "or", Depth: 3, Type: ExpressionTokenLogical},
   146  			{Value: "or", Depth: 4, Type: ExpressionTokenLogical},
   147  			{Value: "eq", Depth: 5, Type: ExpressionTokenLogical},
   148  			{Value: "d", Depth: 6, Type: ExpressionTokenLiteral},
   149  			{Value: "'Site'", Depth: 6, Type: ExpressionTokenString},
   150  			{Value: "eq", Depth: 5, Type: ExpressionTokenLogical},
   151  			{Value: "'Environment'", Depth: 6, Type: ExpressionTokenString},
   152  			{Value: "/", Depth: 6, Type: ExpressionTokenNav},
   153  			{Value: "d", Depth: 7, Type: ExpressionTokenLiteral},
   154  			{Value: "Key", Depth: 7, Type: ExpressionTokenLiteral},
   155  			{Value: "eq", Depth: 4, Type: ExpressionTokenLogical},
   156  			{Value: "/", Depth: 5, Type: ExpressionTokenNav},
   157  			{Value: "/", Depth: 6, Type: ExpressionTokenNav},
   158  			{Value: "d", Depth: 7, Type: ExpressionTokenLiteral},
   159  			{Value: "d", Depth: 7, Type: ExpressionTokenLiteral},
   160  			{Value: "d", Depth: 6, Type: ExpressionTokenLiteral},
   161  			{Value: "123456", Depth: 5, Type: ExpressionTokenInteger},
   162  			{Value: "eq", Depth: 3, Type: ExpressionTokenLogical},
   163  			{Value: "concat", Depth: 4, Type: ExpressionTokenFunc},
   164  			{Value: "/", Depth: 5, Type: ExpressionTokenNav},
   165  			{Value: "d", Depth: 6, Type: ExpressionTokenLiteral},
   166  			{Value: "FirstName", Depth: 6, Type: ExpressionTokenLiteral},
   167  			{Value: "/", Depth: 5, Type: ExpressionTokenNav},
   168  			{Value: "d", Depth: 6, Type: ExpressionTokenLiteral},
   169  			{Value: "LastName", Depth: 6, Type: ExpressionTokenLiteral},
   170  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   171  			{Value: "$it", Depth: 5, Type: ExpressionTokenIt},
   172  			{Value: "FullName", Depth: 5, Type: ExpressionTokenLiteral},
   173  		},
   174  	},
   175  	{
   176  		// matches documents where any of the geo coordinates in the locations field is within the given polygon.
   177  		expression: "locations/any(loc: geo.intersects(loc, geography'SRID=0;Polygon((-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581))'))",
   178  		infixTokens: []*Token{
   179  			{Value: "locations", Type: ExpressionTokenLiteral},
   180  			{Value: "/", Type: ExpressionTokenLambdaNav},
   181  			{Value: "any", Type: ExpressionTokenLambda},
   182  			{Value: "(", Type: ExpressionTokenOpenParen},
   183  			{Value: "loc", Type: ExpressionTokenLiteral},
   184  			{Value: ",", Type: ExpressionTokenColon}, // TODO: this should be a colon (?)
   185  			{Value: "geo.intersects", Type: ExpressionTokenFunc},
   186  			{Value: "(", Type: ExpressionTokenOpenParen},
   187  			{Value: "loc", Type: ExpressionTokenLiteral},
   188  			{Value: ",", Type: ExpressionTokenComma},
   189  			{Value: "geography'SRID=0;Polygon((-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581))'", Type: ExpressionTokenGeographyPolygon},
   190  			{Value: ")", Type: ExpressionTokenCloseParen},
   191  			{Value: ")", Type: ExpressionTokenCloseParen},
   192  		},
   193  
   194  		tree: []expectedParseNode{
   195  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   196  			{Value: "locations", Depth: 1, Type: ExpressionTokenLiteral},
   197  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   198  			{Value: "loc", Depth: 2, Type: ExpressionTokenLiteral},
   199  			{Value: "geo.intersects", Depth: 2, Type: ExpressionTokenFunc},
   200  			{Value: "loc", Depth: 3, Type: ExpressionTokenLiteral},
   201  			{Value: "geography'SRID=0;Polygon((-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581))'", Depth: 3, Type: ExpressionTokenGeographyPolygon},
   202  		},
   203  	},
   204  	{
   205  		// geographyPolygon   = geographyPrefix SQUOTE fullPolygonLiteral SQUOTE
   206  		// geographyPrefix = "geography"
   207  		// fullPolygonLiteral = sridLiteral polygonLiteral
   208  		// sridLiteral      = "SRID" EQ 1*5DIGIT SEMI
   209  		// polygonLiteral     = "Polygon" polygonData
   210  		// polygonData        = OPEN ringLiteral *( COMMA ringLiteral ) CLOSE
   211  		// positionLiteral  = doubleValue SP doubleValue  ; longitude, then latitude
   212  		expression: "geo.intersects(location, geometry'SRID=123;Polygon((-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581))')",
   213  		tree: []expectedParseNode{
   214  			{Value: "geo.intersects", Depth: 0, Type: ExpressionTokenFunc},
   215  			{Value: "location", Depth: 1, Type: ExpressionTokenLiteral},
   216  			{Value: "geometry'SRID=123;Polygon((-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581))'", Depth: 1, Type: ExpressionTokenGeometryPolygon},
   217  		},
   218  	},
   219  	{
   220  		expression: "Tags/any(d:d/Key eq 'Site' and d/Value lt 10)",
   221  		tree: []expectedParseNode{
   222  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   223  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   224  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   225  			{Value: "d", Depth: 2, Type: ExpressionTokenLiteral},
   226  			{Value: "and", Depth: 2, Type: ExpressionTokenLogical},
   227  			{Value: "eq", Depth: 3, Type: ExpressionTokenLogical},
   228  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   229  			{Value: "d", Depth: 5, Type: ExpressionTokenLiteral},
   230  			{Value: "Key", Depth: 5, Type: ExpressionTokenLiteral},
   231  			{Value: "'Site'", Depth: 4, Type: ExpressionTokenString},
   232  			{Value: "lt", Depth: 3, Type: ExpressionTokenLogical},
   233  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   234  			{Value: "d", Depth: 5, Type: ExpressionTokenLiteral},
   235  			{Value: "Value", Depth: 5, Type: ExpressionTokenLiteral},
   236  			{Value: "10", Depth: 4, Type: ExpressionTokenInteger},
   237  		},
   238  	},
   239  	{
   240  		expression: "City eq ''",
   241  		tree: []expectedParseNode{
   242  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   243  			{Value: "City", Depth: 1, Type: ExpressionTokenLiteral},
   244  			{Value: "''", Depth: 1, Type: ExpressionTokenString},
   245  		},
   246  	},
   247  	{
   248  		// TestExpressionInOperator tests the "IN" operator with a comma-separated list of values.
   249  		expression: "City in ( 'Seattle', 'Atlanta', 'Paris' )",
   250  		infixTokens: []*Token{
   251  			{Value: "City", Type: ExpressionTokenLiteral},
   252  			{Value: "in", Type: ExpressionTokenLogical},
   253  			{Value: "(", Type: ExpressionTokenOpenParen},
   254  			{Value: "'Seattle'", Type: ExpressionTokenString},
   255  			{Value: ",", Type: ExpressionTokenComma},
   256  			{Value: "'Atlanta'", Type: ExpressionTokenString},
   257  			{Value: ",", Type: ExpressionTokenComma},
   258  			{Value: "'Paris'", Type: ExpressionTokenString},
   259  			{Value: ")", Type: ExpressionTokenCloseParen},
   260  		},
   261  		postfixTokens: []*Token{
   262  			{Value: "City", Type: ExpressionTokenLiteral},
   263  			{Value: "'Seattle'", Type: ExpressionTokenString},
   264  			{Value: "'Atlanta'", Type: ExpressionTokenString},
   265  			{Value: "'Paris'", Type: ExpressionTokenString},
   266  			{Value: "3", Type: TokenTypeArgCount},
   267  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   268  			{Value: "in", Type: ExpressionTokenLogical},
   269  		},
   270  		tree: []expectedParseNode{
   271  			{Value: "in", Depth: 0, Type: ExpressionTokenLogical},
   272  			{Value: "City", Depth: 1, Type: ExpressionTokenLiteral},
   273  			{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   274  			{Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString},
   275  			{Value: "'Atlanta'", Depth: 2, Type: ExpressionTokenString},
   276  			{Value: "'Paris'", Depth: 2, Type: ExpressionTokenString},
   277  		},
   278  	},
   279  	{
   280  		// TestExpressionInOperatorSingleValue tests the "IN" operator with a list containing a single value.
   281  		expression: "City in ( 'Seattle' )",
   282  		infixTokens: []*Token{
   283  			{Value: "City", Type: ExpressionTokenLiteral},
   284  			{Value: "in", Type: ExpressionTokenLogical},
   285  			{Value: "(", Type: ExpressionTokenOpenParen},
   286  			{Value: "'Seattle'", Type: ExpressionTokenString},
   287  			{Value: ")", Type: ExpressionTokenCloseParen},
   288  		},
   289  		postfixTokens: []*Token{
   290  			{Value: "City", Type: ExpressionTokenLiteral},
   291  			{Value: "'Seattle'", Type: ExpressionTokenString},
   292  			{Value: "1", Type: TokenTypeArgCount},
   293  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   294  			{Value: "in", Type: ExpressionTokenLogical},
   295  		},
   296  		tree: []expectedParseNode{
   297  			{Value: "in", Depth: 0, Type: ExpressionTokenLogical},
   298  			{Value: "City", Depth: 1, Type: ExpressionTokenLiteral},
   299  			{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   300  			{Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString},
   301  		},
   302  	},
   303  	{
   304  		// TestExpressionInOperatorEmptyList tests the "IN" operator with a list containing no value.
   305  		expression: "City in ( )",
   306  		infixTokens: []*Token{
   307  			{Value: "City", Type: ExpressionTokenLiteral},
   308  			{Value: "in", Type: ExpressionTokenLogical},
   309  			{Value: "(", Type: ExpressionTokenOpenParen},
   310  			{Value: ")", Type: ExpressionTokenCloseParen},
   311  		},
   312  		postfixTokens: []*Token{
   313  			{Value: "City", Type: ExpressionTokenLiteral},
   314  			{Value: "0", Type: TokenTypeArgCount},
   315  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   316  			{Value: "in", Type: ExpressionTokenLogical},
   317  		},
   318  		tree: []expectedParseNode{
   319  			{Value: "in", Depth: 0, Type: ExpressionTokenLogical},
   320  			{Value: "City", Depth: 1, Type: ExpressionTokenLiteral},
   321  			{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   322  		},
   323  	},
   324  	{
   325  		// Note: according to ODATA ABNF notation, there must be a space between not and open parenthesis.
   326  		// http://docs.oasis-open.org/odata/odata/v4.01/csprd03/abnf/odata-abnf-construction-rules.txt
   327  		expression: "not(City eq 'Seattle')",
   328  		infixTokens: []*Token{
   329  			{Value: "not", Type: ExpressionTokenLogical},
   330  			{Value: "(", Type: ExpressionTokenOpenParen},
   331  			{Value: "City", Type: ExpressionTokenLiteral},
   332  			{Value: "eq", Type: ExpressionTokenLogical},
   333  			{Value: "'Seattle'", Type: ExpressionTokenString},
   334  			{Value: ")", Type: ExpressionTokenCloseParen},
   335  		},
   336  		tree: []expectedParseNode{
   337  			{Value: "not", Depth: 0, Type: ExpressionTokenLogical},
   338  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
   339  			{Value: "City", Depth: 2, Type: ExpressionTokenLiteral},
   340  			{Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString},
   341  		},
   342  	},
   343  	{
   344  		// Not in list
   345  		expression: "not ( City in ( 'Seattle', 'Atlanta' ) )",
   346  		tree: []expectedParseNode{
   347  			{Value: "not", Depth: 0, Type: ExpressionTokenLogical},
   348  			{Value: "in", Depth: 1, Type: ExpressionTokenLogical},
   349  			{Value: "City", Depth: 2, Type: ExpressionTokenLiteral},
   350  			{Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr},
   351  			{Value: "'Seattle'", Depth: 3, Type: ExpressionTokenString},
   352  			{Value: "'Atlanta'", Depth: 3, Type: ExpressionTokenString},
   353  		},
   354  	},
   355  	{
   356  		// tests the "IN" operator with a comma-separated list
   357  		// of values, one of which is a function call which itself has a comma-separated list of values.
   358  		// 'Atlanta' is enclosed in a unecessary parenExpr to validate the expression is properly unwrapped.
   359  		expression: "City in ( 'Seattle', concat('San', 'Francisco'), ('Atlanta') )",
   360  		infixTokens: []*Token{
   361  			{Value: "City", Type: ExpressionTokenLiteral},
   362  			{Value: "in", Type: ExpressionTokenLogical},
   363  			{Value: "(", Type: ExpressionTokenOpenParen},
   364  			{Value: "'Seattle'", Type: ExpressionTokenString},
   365  			{Value: ",", Type: ExpressionTokenComma},
   366  			{Value: "concat", Type: ExpressionTokenFunc},
   367  			{Value: "(", Type: ExpressionTokenOpenParen},
   368  			{Value: "'San'", Type: ExpressionTokenString},
   369  			{Value: ",", Type: ExpressionTokenComma},
   370  			{Value: "'Francisco'", Type: ExpressionTokenString},
   371  			{Value: ")", Type: ExpressionTokenCloseParen},
   372  			{Value: ",", Type: ExpressionTokenComma},
   373  			{Value: "(", Type: ExpressionTokenOpenParen},
   374  			{Value: "'Atlanta'", Type: ExpressionTokenString},
   375  			{Value: ")", Type: ExpressionTokenCloseParen},
   376  			{Value: ")", Type: ExpressionTokenCloseParen},
   377  		},
   378  		tree: []expectedParseNode{
   379  			{Value: "in", Depth: 0, Type: ExpressionTokenLogical},
   380  			{Value: "City", Depth: 1, Type: ExpressionTokenLiteral},
   381  			{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   382  			{Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString},
   383  			{Value: "concat", Depth: 2, Type: ExpressionTokenFunc},
   384  			{Value: "'San'", Depth: 3, Type: ExpressionTokenString},
   385  			{Value: "'Francisco'", Depth: 3, Type: ExpressionTokenString},
   386  			{Value: "'Atlanta'", Depth: 2, Type: ExpressionTokenString},
   387  		},
   388  	},
   389  	{
   390  		expression: "Tags/all(d:d/Key eq 'Site')",
   391  		tree: []expectedParseNode{
   392  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   393  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   394  			{Value: "all", Depth: 1, Type: ExpressionTokenLambda},
   395  			{Value: "d", Depth: 2, Type: ExpressionTokenLiteral},
   396  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
   397  			{Value: "/", Depth: 3, Type: ExpressionTokenNav},
   398  			{Value: "d", Depth: 4, Type: ExpressionTokenLiteral},
   399  			{Value: "Key", Depth: 4, Type: ExpressionTokenLiteral},
   400  			{Value: "'Site'", Depth: 3, Type: ExpressionTokenString},
   401  		},
   402  	},
   403  	{
   404  		// substring can take 2 or 3 arguments.
   405  		expression: "substring(CompanyName,1) eq 'Foo'",
   406  		tree: []expectedParseNode{
   407  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   408  			{Value: "substring", Depth: 1, Type: ExpressionTokenFunc},
   409  			{Value: "CompanyName", Depth: 2, Type: ExpressionTokenLiteral},
   410  			{Value: "1", Depth: 2, Type: ExpressionTokenInteger},
   411  			{Value: "'Foo'", Depth: 1, Type: ExpressionTokenString},
   412  		},
   413  	},
   414  	{
   415  		expression: "substring(CompanyName,1,2) eq 'lf'",
   416  		tree: []expectedParseNode{
   417  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   418  			{Value: "substring", Depth: 1, Type: ExpressionTokenFunc},
   419  			{Value: "CompanyName", Depth: 2, Type: ExpressionTokenLiteral},
   420  			{Value: "1", Depth: 2, Type: ExpressionTokenInteger},
   421  			{Value: "2", Depth: 2, Type: ExpressionTokenInteger},
   422  			{Value: "'lf'", Depth: 1, Type: ExpressionTokenString},
   423  		},
   424  	},
   425  	{
   426  		// Previously, the parser was incorrectly interpreting the 'geo.xxx' functions as the 'ge' operator.
   427  		expression: "geo.distance(CurrentPosition,TargetPosition)",
   428  		tree: []expectedParseNode{
   429  			{Value: "geo.distance", Depth: 0, Type: ExpressionTokenFunc},
   430  			{Value: "CurrentPosition", Depth: 1, Type: ExpressionTokenLiteral},
   431  			{Value: "TargetPosition", Depth: 1, Type: ExpressionTokenLiteral},
   432  		},
   433  	},
   434  	{
   435  		expression: "Tags/any(var:var/Key eq 'Site')",
   436  		tree: []expectedParseNode{
   437  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   438  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   439  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   440  			{Value: "var", Depth: 2, Type: ExpressionTokenLiteral},
   441  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
   442  			{Value: "/", Depth: 3, Type: ExpressionTokenNav},
   443  			{Value: "var", Depth: 4, Type: ExpressionTokenLiteral},
   444  			{Value: "Key", Depth: 4, Type: ExpressionTokenLiteral},
   445  			{Value: "'Site'", Depth: 3, Type: ExpressionTokenString},
   446  		},
   447  	},
   448  	{
   449  		expression: "Price/any(t:not (12345 eq t ))",
   450  		tree: []expectedParseNode{
   451  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   452  			{Value: "Price", Depth: 1, Type: ExpressionTokenLiteral},
   453  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   454  			{Value: "t", Depth: 2, Type: ExpressionTokenLiteral},
   455  			{Value: "not", Depth: 2, Type: ExpressionTokenLogical},
   456  			{Value: "eq", Depth: 3, Type: ExpressionTokenLogical},
   457  			{Value: "12345", Depth: 4, Type: ExpressionTokenInteger},
   458  			{Value: "t", Depth: 4, Type: ExpressionTokenLiteral},
   459  		},
   460  	},
   461  	{
   462  		expression: "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London')",
   463  		tree: []expectedParseNode{
   464  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   465  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   466  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   467  			{Value: "var", Depth: 2, Type: ExpressionTokenLiteral},
   468  			{Value: "and", Depth: 2, Type: ExpressionTokenLogical},
   469  			{Value: "eq", Depth: 3, Type: ExpressionTokenLogical},
   470  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   471  			{Value: "var", Depth: 5, Type: ExpressionTokenLiteral},
   472  			{Value: "Key", Depth: 5, Type: ExpressionTokenLiteral},
   473  			{Value: "'Site'", Depth: 4, Type: ExpressionTokenString},
   474  			{Value: "eq", Depth: 3, Type: ExpressionTokenLogical},
   475  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   476  			{Value: "var", Depth: 5, Type: ExpressionTokenLiteral},
   477  			{Value: "Value", Depth: 5, Type: ExpressionTokenLiteral},
   478  			{Value: "'London'", Depth: 4, Type: ExpressionTokenString},
   479  		},
   480  	},
   481  	{
   482  		expression: "Enabled/any(t:t/Value eq Config/any(c:c/AdminState eq 'TRUE'))",
   483  		tree: []expectedParseNode{
   484  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   485  			{Value: "Enabled", Depth: 1, Type: ExpressionTokenLiteral},
   486  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   487  			{Value: "t", Depth: 2, Type: ExpressionTokenLiteral},
   488  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
   489  			{Value: "/", Depth: 3, Type: ExpressionTokenNav},
   490  			{Value: "t", Depth: 4, Type: ExpressionTokenLiteral},
   491  			{Value: "Value", Depth: 4, Type: ExpressionTokenLiteral},
   492  			{Value: "/", Depth: 3, Type: ExpressionTokenLambdaNav},
   493  			{Value: "Config", Depth: 4, Type: ExpressionTokenLiteral},
   494  			{Value: "any", Depth: 4, Type: ExpressionTokenLambda},
   495  			{Value: "c", Depth: 5, Type: ExpressionTokenLiteral},
   496  			{Value: "eq", Depth: 5, Type: ExpressionTokenLogical},
   497  			{Value: "/", Depth: 6, Type: ExpressionTokenNav},
   498  			{Value: "c", Depth: 7, Type: ExpressionTokenLiteral},
   499  			{Value: "AdminState", Depth: 7, Type: ExpressionTokenLiteral},
   500  			{Value: "'TRUE'", Depth: 6, Type: ExpressionTokenString},
   501  		},
   502  	},
   503  	{
   504  		// Validate the any() lambda function with multiple nested properties.
   505  		expression: "Config/any(var:var/Config/Priority eq 123)",
   506  		tree: []expectedParseNode{
   507  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   508  			{Value: "Config", Depth: 1, Type: ExpressionTokenLiteral},
   509  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   510  			{Value: "var", Depth: 2, Type: ExpressionTokenLiteral},
   511  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
   512  			{Value: "/", Depth: 3, Type: ExpressionTokenNav},
   513  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   514  			{Value: "var", Depth: 5, Type: ExpressionTokenLiteral},
   515  			{Value: "Config", Depth: 5, Type: ExpressionTokenLiteral},
   516  			{Value: "Priority", Depth: 4, Type: ExpressionTokenLiteral},
   517  			{Value: "123", Depth: 3, Type: ExpressionTokenInteger},
   518  		},
   519  	},
   520  	{
   521  		expression: "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London' or Price gt 1.0)",
   522  		tree: []expectedParseNode{
   523  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   524  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   525  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   526  			{Value: "var", Depth: 2, Type: ExpressionTokenLiteral},
   527  			{Value: "or", Depth: 2, Type: ExpressionTokenLogical},
   528  			{Value: "and", Depth: 3, Type: ExpressionTokenLogical},
   529  			{Value: "eq", Depth: 4, Type: ExpressionTokenLogical},
   530  			{Value: "/", Depth: 5, Type: ExpressionTokenNav},
   531  			{Value: "var", Depth: 6, Type: ExpressionTokenLiteral},
   532  			{Value: "Key", Depth: 6, Type: ExpressionTokenLiteral},
   533  			{Value: "'Site'", Depth: 5, Type: ExpressionTokenString},
   534  			{Value: "eq", Depth: 4, Type: ExpressionTokenLogical},
   535  			{Value: "/", Depth: 5, Type: ExpressionTokenNav},
   536  			{Value: "var", Depth: 6, Type: ExpressionTokenLiteral},
   537  			{Value: "Value", Depth: 6, Type: ExpressionTokenLiteral},
   538  			{Value: "'London'", Depth: 5, Type: ExpressionTokenString},
   539  			{Value: "gt", Depth: 3, Type: ExpressionTokenLogical},
   540  			{Value: "Price", Depth: 4, Type: ExpressionTokenLiteral},
   541  			{Value: "1.0", Depth: 4, Type: ExpressionTokenFloat},
   542  		},
   543  	},
   544  	{
   545  		expression: "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London' or Price gt 1.0 or contains(var/Value, 'Smith'))",
   546  		tree: []expectedParseNode{
   547  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   548  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   549  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   550  			{Value: "var", Depth: 2, Type: ExpressionTokenLiteral},
   551  			{Value: "or", Depth: 2, Type: ExpressionTokenLogical},
   552  			{Value: "or", Depth: 3, Type: ExpressionTokenLogical},
   553  			{Value: "and", Depth: 4, Type: ExpressionTokenLogical},
   554  			{Value: "eq", Depth: 5, Type: ExpressionTokenLogical},
   555  			{Value: "/", Depth: 6, Type: ExpressionTokenNav},
   556  			{Value: "var", Depth: 7, Type: ExpressionTokenLiteral},
   557  			{Value: "Key", Depth: 7, Type: ExpressionTokenLiteral},
   558  			{Value: "'Site'", Depth: 6, Type: ExpressionTokenString},
   559  			{Value: "eq", Depth: 5, Type: ExpressionTokenLogical},
   560  			{Value: "/", Depth: 6, Type: ExpressionTokenNav},
   561  			{Value: "var", Depth: 7, Type: ExpressionTokenLiteral},
   562  			{Value: "Value", Depth: 7, Type: ExpressionTokenLiteral},
   563  			{Value: "'London'", Depth: 6, Type: ExpressionTokenString},
   564  			{Value: "gt", Depth: 4, Type: ExpressionTokenLogical},
   565  			{Value: "Price", Depth: 5, Type: ExpressionTokenLiteral},
   566  			{Value: "1.0", Depth: 5, Type: ExpressionTokenFloat},
   567  			{Value: "contains", Depth: 3, Type: ExpressionTokenFunc},
   568  			{Value: "/", Depth: 4, Type: ExpressionTokenNav},
   569  			{Value: "var", Depth: 5, Type: ExpressionTokenLiteral},
   570  			{Value: "Value", Depth: 5, Type: ExpressionTokenLiteral},
   571  			{Value: "'Smith'", Depth: 4, Type: ExpressionTokenString},
   572  		},
   573  	},
   574  	{
   575  		expression: "Product/Address/City eq 'Redmond'",
   576  		tree: []expectedParseNode{
   577  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   578  			{Value: "/", Depth: 1, Type: ExpressionTokenNav},
   579  			{Value: "/", Depth: 2, Type: ExpressionTokenNav},
   580  			{Value: "Product", Depth: 3, Type: ExpressionTokenLiteral},
   581  			{Value: "Address", Depth: 3, Type: ExpressionTokenLiteral},
   582  			{Value: "City", Depth: 2, Type: ExpressionTokenLiteral},
   583  			{Value: "'Redmond'", Depth: 1, Type: ExpressionTokenString},
   584  		},
   585  	},
   586  	{
   587  		// TestSubstringNestedFunction tests the substring function with a nested call
   588  		// to substring, with the use of 2-argument and 3-argument substring.
   589  		// Previously, the parser was incorrectly interpreting the 'substringof' function as the 'sub' operator.
   590  
   591  		expression: "substring(substring('Francisco', 1), 3, 2) eq 'ci'",
   592  		infixTokens: []*Token{
   593  			{Value: "substring", Type: ExpressionTokenFunc},
   594  			{Value: "(", Type: ExpressionTokenOpenParen},
   595  			{Value: "substring", Type: ExpressionTokenFunc},
   596  			{Value: "(", Type: ExpressionTokenOpenParen},
   597  			{Value: "'Francisco'", Type: ExpressionTokenString},
   598  			{Value: ",", Type: ExpressionTokenComma},
   599  			{Value: "1", Type: ExpressionTokenInteger},
   600  			{Value: ")", Type: ExpressionTokenCloseParen},
   601  			{Value: ",", Type: ExpressionTokenComma},
   602  			{Value: "3", Type: ExpressionTokenInteger},
   603  			{Value: ",", Type: ExpressionTokenComma},
   604  			{Value: "2", Type: ExpressionTokenInteger},
   605  			{Value: ")", Type: ExpressionTokenCloseParen},
   606  			{Value: "eq", Type: ExpressionTokenLogical},
   607  			{Value: "'ci'", Type: ExpressionTokenString},
   608  		},
   609  		postfixTokens: []*Token{
   610  			{Value: "'Francisco'", Type: ExpressionTokenString},
   611  			{Value: "1", Type: ExpressionTokenInteger},
   612  			{Value: "2", Type: TokenTypeArgCount}, // The number of function arguments.
   613  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   614  			{Value: "substring", Type: ExpressionTokenFunc},
   615  			{Value: "3", Type: ExpressionTokenInteger},
   616  			{Value: "2", Type: ExpressionTokenInteger},
   617  			{Value: "3", Type: TokenTypeArgCount}, // The number of function arguments.
   618  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   619  			{Value: "substring", Type: ExpressionTokenFunc},
   620  			{Value: "'ci'", Type: ExpressionTokenString},
   621  			{Value: "eq", Type: ExpressionTokenLogical},
   622  		},
   623  		tree: []expectedParseNode{
   624  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   625  			{Value: "substring", Depth: 1, Type: ExpressionTokenFunc},
   626  			{Value: "substring", Depth: 2, Type: ExpressionTokenFunc},
   627  			{Value: "'Francisco'", Depth: 3, Type: ExpressionTokenString},
   628  			{Value: "1", Depth: 3, Type: ExpressionTokenInteger},
   629  			{Value: "3", Depth: 2, Type: ExpressionTokenInteger},
   630  			{Value: "2", Depth: 2, Type: ExpressionTokenInteger},
   631  			{Value: "'ci'", Depth: 1, Type: ExpressionTokenString},
   632  		},
   633  	},
   634  	{
   635  		// Previously, the parser was incorrectly interpreting the 'substringof' function as the 'sub' operator.
   636  		expression: "substringof('Alfreds', CompanyName) eq true",
   637  		infixTokens: []*Token{
   638  			{Value: "substringof", Type: ExpressionTokenFunc},
   639  			{Value: "(", Type: ExpressionTokenOpenParen},
   640  			{Value: "'Alfreds'", Type: ExpressionTokenString},
   641  			{Value: ",", Type: ExpressionTokenComma},
   642  			{Value: "CompanyName", Type: ExpressionTokenLiteral},
   643  			{Value: ")", Type: ExpressionTokenCloseParen},
   644  			{Value: "eq", Type: ExpressionTokenLogical},
   645  			{Value: "true", Type: ExpressionTokenBoolean},
   646  		},
   647  		postfixTokens: []*Token{
   648  			{Value: "'Alfreds'", Type: ExpressionTokenString},
   649  			{Value: "CompanyName", Type: ExpressionTokenLiteral},
   650  			{Value: "2", Type: TokenTypeArgCount}, // The number of function arguments.
   651  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   652  			{Value: "substringof", Type: ExpressionTokenFunc},
   653  			{Value: "true", Type: ExpressionTokenBoolean},
   654  			{Value: "eq", Type: ExpressionTokenLogical},
   655  		},
   656  		tree: []expectedParseNode{
   657  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   658  			{Value: "substringof", Depth: 1, Type: ExpressionTokenFunc},
   659  			{Value: "'Alfreds'", Depth: 2, Type: ExpressionTokenString},
   660  			{Value: "CompanyName", Depth: 2, Type: ExpressionTokenLiteral},
   661  			{Value: "true", Depth: 1, Type: ExpressionTokenBoolean},
   662  		},
   663  	},
   664  	{
   665  		expression: "exists(Name,false)",
   666  		infixTokens: []*Token{
   667  			{Value: "exists", Type: ExpressionTokenFunc},
   668  			{Value: "(", Type: ExpressionTokenOpenParen},
   669  			{Value: "Name", Type: ExpressionTokenLiteral},
   670  			{Value: ",", Type: ExpressionTokenComma},
   671  			{Value: "false", Type: ExpressionTokenBoolean},
   672  			{Value: ")", Type: ExpressionTokenCloseParen},
   673  		},
   674  		tree: []expectedParseNode{
   675  			{Value: "exists", Depth: 0, Type: ExpressionTokenFunc},
   676  			{Value: "Name", Depth: 1, Type: ExpressionTokenLiteral},
   677  			{Value: "false", Depth: 1, Type: ExpressionTokenBoolean},
   678  		},
   679  	},
   680  	{
   681  		expression: "not (A eq B)",
   682  		tree: []expectedParseNode{
   683  			{Value: "not", Depth: 0, Type: ExpressionTokenLogical},
   684  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
   685  			{Value: "A", Depth: 2, Type: ExpressionTokenLiteral},
   686  			{Value: "B", Depth: 2, Type: ExpressionTokenLiteral},
   687  		},
   688  	},
   689  	{
   690  		expression: "not endswith(Name,'ilk')",
   691  		infixTokens: []*Token{
   692  			{Value: "not", Type: ExpressionTokenLogical},
   693  			{Value: "endswith", Type: ExpressionTokenFunc},
   694  			{Value: "(", Type: ExpressionTokenOpenParen},
   695  			{Value: "Name", Type: ExpressionTokenLiteral},
   696  			{Value: ",", Type: ExpressionTokenComma},
   697  			{Value: "'ilk'", Type: ExpressionTokenString},
   698  			{Value: ")", Type: ExpressionTokenCloseParen},
   699  		},
   700  		tree: []expectedParseNode{
   701  			{Value: "not", Depth: 0, Type: ExpressionTokenLogical},
   702  			{Value: "endswith", Depth: 1, Type: ExpressionTokenFunc},
   703  			{Value: "Name", Depth: 2, Type: ExpressionTokenLiteral},
   704  			{Value: "'ilk'", Depth: 2, Type: ExpressionTokenString},
   705  		},
   706  	},
   707  	{
   708  		// See http://docs.oasis-open.org/odata/odata/v4.01/csprd02/part1-protocol/odata-v4.01-csprd02-part1-protocol.html#_Toc486263411
   709  		// Test 'in', which is the 'Is a member of' operator.
   710  		expression: "contains(LastName, 'Smith') and Site in ('London', 'Paris', 'San Francisco', 'Dallas') and FirstName eq 'John'",
   711  		infixTokens: []*Token{
   712  			{Value: "contains", Type: ExpressionTokenFunc},
   713  			{Value: "(", Type: ExpressionTokenOpenParen},
   714  			{Value: "LastName", Type: ExpressionTokenLiteral},
   715  			{Value: ",", Type: ExpressionTokenComma},
   716  			{Value: "'Smith'", Type: ExpressionTokenString},
   717  			{Value: ")", Type: ExpressionTokenCloseParen},
   718  			{Value: "and", Type: ExpressionTokenLogical},
   719  			{Value: "Site", Type: ExpressionTokenLiteral},
   720  			{Value: "in", Type: ExpressionTokenLogical},
   721  			{Value: "(", Type: ExpressionTokenOpenParen},
   722  			{Value: "'London'", Type: ExpressionTokenString},
   723  			{Value: ",", Type: ExpressionTokenComma},
   724  			{Value: "'Paris'", Type: ExpressionTokenString},
   725  			{Value: ",", Type: ExpressionTokenComma},
   726  			{Value: "'San Francisco'", Type: ExpressionTokenString},
   727  			{Value: ",", Type: ExpressionTokenComma},
   728  			{Value: "'Dallas'", Type: ExpressionTokenString},
   729  			{Value: ")", Type: ExpressionTokenCloseParen},
   730  			{Value: "and", Type: ExpressionTokenLogical},
   731  			{Value: "FirstName", Type: ExpressionTokenLiteral},
   732  			{Value: "eq", Type: ExpressionTokenLogical},
   733  			{Value: "'John'", Type: ExpressionTokenString},
   734  		},
   735  		tree: []expectedParseNode{
   736  			{Value: "and", Depth: 0, Type: ExpressionTokenLogical},
   737  			{Value: "and", Depth: 1, Type: ExpressionTokenLogical},
   738  			{Value: "contains", Depth: 2, Type: ExpressionTokenFunc},
   739  			{Value: "LastName", Depth: 3, Type: ExpressionTokenLiteral},
   740  			{Value: "'Smith'", Depth: 3, Type: ExpressionTokenString},
   741  			{Value: "in", Depth: 2, Type: ExpressionTokenLogical},
   742  			{Value: "Site", Depth: 3, Type: ExpressionTokenLiteral},
   743  			{Value: TokenListExpr, Depth: 3, Type: TokenTypeListExpr},
   744  			{Value: "'London'", Depth: 4, Type: ExpressionTokenString},
   745  			{Value: "'Paris'", Depth: 4, Type: ExpressionTokenString},
   746  			{Value: "'San Francisco'", Depth: 4, Type: ExpressionTokenString},
   747  			{Value: "'Dallas'", Depth: 4, Type: ExpressionTokenString},
   748  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
   749  			{Value: "FirstName", Depth: 2, Type: ExpressionTokenLiteral},
   750  			{Value: "'John'", Depth: 2, Type: ExpressionTokenString},
   751  		},
   752  	},
   753  	{
   754  		expression: "Tags/any(d:d eq 'Site')",
   755  		infixTokens: []*Token{
   756  			{Value: "Tags", Type: ExpressionTokenLiteral},
   757  			{Value: "/", Type: ExpressionTokenLambdaNav},
   758  			{Value: "any", Type: ExpressionTokenLambda},
   759  			{Value: "(", Type: ExpressionTokenOpenParen},
   760  			{Value: "d", Type: ExpressionTokenLiteral},
   761  			{Value: ",", Type: ExpressionTokenColon},
   762  			{Value: "d", Type: ExpressionTokenLiteral},
   763  			{Value: "eq", Type: ExpressionTokenLogical},
   764  			{Value: "'Site'", Type: ExpressionTokenString},
   765  			{Value: ")", Type: ExpressionTokenCloseParen},
   766  		},
   767  		tree: []expectedParseNode{
   768  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   769  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   770  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   771  			{Value: "d", Depth: 2, Type: ExpressionTokenLiteral},
   772  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
   773  			{Value: "d", Depth: 3, Type: ExpressionTokenLiteral},
   774  			{Value: "'Site'", Depth: 3, Type: ExpressionTokenString},
   775  		},
   776  	},
   777  	{
   778  		expression: "GuidValue eq 01234567-89ab-cdef-0123-456789abcdef",
   779  		infixTokens: []*Token{
   780  			{Value: "GuidValue", Type: ExpressionTokenLiteral},
   781  			{Value: "eq", Type: ExpressionTokenLogical},
   782  			{Value: "01234567-89ab-cdef-0123-456789abcdef", Type: ExpressionTokenGuid},
   783  		},
   784  		tree: []expectedParseNode{
   785  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   786  			{Value: "GuidValue", Depth: 1, Type: ExpressionTokenLiteral},
   787  			{Value: "01234567-89ab-cdef-0123-456789abcdef", Depth: 1, Type: ExpressionTokenGuid},
   788  		},
   789  	},
   790  	{
   791  		expression: "Task eq duration'P12DT23H59M59.999999999999S'",
   792  		infixTokens: []*Token{
   793  			{Value: "Task", Type: ExpressionTokenLiteral},
   794  			{Value: "eq", Type: ExpressionTokenLogical},
   795  			// Note the duration token is extracted.
   796  			{Value: "P12DT23H59M59.999999999999S", Type: ExpressionTokenDuration},
   797  		},
   798  		tree: []expectedParseNode{
   799  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   800  			{Value: "Task", Depth: 1, Type: ExpressionTokenLiteral},
   801  			{Value: "P12DT23H59M59.999999999999S", Depth: 1, Type: ExpressionTokenDuration},
   802  		},
   803  	},
   804  	{
   805  		expression: "Task eq 'P12DT23H59M59.999999999999S'",
   806  		infixTokens: []*Token{
   807  			{Value: "Task", Type: ExpressionTokenLiteral},
   808  			{Value: "eq", Type: ExpressionTokenLogical},
   809  			{Value: "P12DT23H59M59.999999999999S", Type: ExpressionTokenDuration},
   810  		},
   811  		tree: []expectedParseNode{
   812  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
   813  			{Value: "Task", Depth: 1, Type: ExpressionTokenLiteral},
   814  			{Value: "P12DT23H59M59.999999999999S", Depth: 1, Type: ExpressionTokenDuration},
   815  		},
   816  	},
   817  	{
   818  		expression: "Tags/any()",
   819  		infixTokens: []*Token{
   820  			{Value: "Tags", Type: ExpressionTokenLiteral},
   821  			{Value: "/", Type: ExpressionTokenLambdaNav},
   822  			{Value: "any", Type: ExpressionTokenLambda},
   823  			{Value: "(", Type: ExpressionTokenOpenParen},
   824  			{Value: ")", Type: ExpressionTokenCloseParen},
   825  		},
   826  		tree: []expectedParseNode{
   827  			{Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav},
   828  			{Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral},
   829  			{Value: "any", Depth: 1, Type: ExpressionTokenLambda},
   830  		},
   831  	},
   832  	{
   833  		expression: "Price div 2 gt 3.5",
   834  		infixTokens: []*Token{
   835  			{Value: "Price", Type: ExpressionTokenLiteral},
   836  			{Value: "div", Type: ExpressionTokenOp},
   837  			{Value: "2", Type: ExpressionTokenInteger},
   838  			{Value: "gt", Type: ExpressionTokenLogical},
   839  			{Value: "3.5", Type: ExpressionTokenFloat},
   840  		},
   841  		tree: []expectedParseNode{
   842  			{Value: "gt", Depth: 0, Type: ExpressionTokenLogical},
   843  			{Value: "div", Depth: 1, Type: ExpressionTokenOp},
   844  			{Value: "Price", Depth: 2, Type: ExpressionTokenLiteral},
   845  			{Value: "2", Depth: 2, Type: ExpressionTokenInteger},
   846  			{Value: "3.5", Depth: 1, Type: ExpressionTokenFloat},
   847  		},
   848  	},
   849  	{
   850  		expression: "Price divby 2 gt 3.5",
   851  		infixTokens: []*Token{
   852  			{Value: "Price", Type: ExpressionTokenLiteral},
   853  			{Value: "divby", Type: ExpressionTokenOp},
   854  			{Value: "2", Type: ExpressionTokenInteger},
   855  			{Value: "gt", Type: ExpressionTokenLogical},
   856  			{Value: "3.5", Type: ExpressionTokenFloat},
   857  		},
   858  		tree: []expectedParseNode{
   859  			{Value: "gt", Depth: 0, Type: ExpressionTokenLogical},
   860  			{Value: "divby", Depth: 1, Type: ExpressionTokenOp},
   861  			{Value: "Price", Depth: 2, Type: ExpressionTokenLiteral},
   862  			{Value: "2", Depth: 2, Type: ExpressionTokenInteger},
   863  			{Value: "3.5", Depth: 1, Type: ExpressionTokenFloat},
   864  		},
   865  	},
   866  	{
   867  		expression: "not Enabled",
   868  		infixTokens: []*Token{
   869  			{Value: "not", Type: ExpressionTokenLogical},
   870  			{Value: "Enabled", Type: ExpressionTokenLiteral},
   871  		},
   872  		tree: []expectedParseNode{
   873  			{Value: "not", Depth: 0, Type: ExpressionTokenLogical},
   874  			{Value: "Enabled", Depth: 1, Type: ExpressionTokenLiteral},
   875  		},
   876  	},
   877  	{
   878  		// TestExpressionInOperatorBothSides tests the "IN" operator.
   879  		// Use a listExpr on both sides of the IN operator.
   880  		//   listExpr  = OPEN BWS commonExpr BWS *( COMMA BWS commonExpr BWS ) CLOSE
   881  		// Validate if a list is within another list.
   882  		expression: "(1, 2) in ( ('ab', 'cd'), (1, 2), ('abcdefghijk', 'def') )",
   883  		infixTokens: []*Token{
   884  			{Value: "(", Type: ExpressionTokenOpenParen},
   885  			{Value: "1", Type: ExpressionTokenInteger},
   886  			{Value: ",", Type: ExpressionTokenComma},
   887  			{Value: "2", Type: ExpressionTokenInteger},
   888  			{Value: ")", Type: ExpressionTokenCloseParen},
   889  			{Value: "in", Type: ExpressionTokenLogical},
   890  			{Value: "(", Type: ExpressionTokenOpenParen},
   891  
   892  			{Value: "(", Type: ExpressionTokenOpenParen},
   893  			{Value: "'ab'", Type: ExpressionTokenString},
   894  			{Value: ",", Type: ExpressionTokenComma},
   895  			{Value: "'cd'", Type: ExpressionTokenString},
   896  			{Value: ")", Type: ExpressionTokenCloseParen},
   897  			{Value: ",", Type: ExpressionTokenComma},
   898  
   899  			{Value: "(", Type: ExpressionTokenOpenParen},
   900  			{Value: "1", Type: ExpressionTokenInteger},
   901  			{Value: ",", Type: ExpressionTokenComma},
   902  			{Value: "2", Type: ExpressionTokenInteger},
   903  			{Value: ")", Type: ExpressionTokenCloseParen},
   904  			{Value: ",", Type: ExpressionTokenComma},
   905  
   906  			{Value: "(", Type: ExpressionTokenOpenParen},
   907  			{Value: "'abcdefghijk'", Type: ExpressionTokenString},
   908  			{Value: ",", Type: ExpressionTokenComma},
   909  			{Value: "'def'", Type: ExpressionTokenString},
   910  			{Value: ")", Type: ExpressionTokenCloseParen},
   911  			{Value: ")", Type: ExpressionTokenCloseParen},
   912  		},
   913  		postfixTokens: []*Token{
   914  			{Value: "1", Type: ExpressionTokenInteger},
   915  			{Value: "2", Type: ExpressionTokenInteger},
   916  			{Value: "2", Type: TokenTypeArgCount},
   917  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   918  
   919  			{Value: "'ab'", Type: ExpressionTokenString},
   920  			{Value: "'cd'", Type: ExpressionTokenString},
   921  			{Value: "2", Type: TokenTypeArgCount},
   922  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   923  
   924  			{Value: "1", Type: ExpressionTokenInteger},
   925  			{Value: "2", Type: ExpressionTokenInteger},
   926  			{Value: "2", Type: TokenTypeArgCount},
   927  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   928  
   929  			{Value: "'abcdefghijk'", Type: ExpressionTokenString},
   930  			{Value: "'def'", Type: ExpressionTokenString},
   931  			{Value: "2", Type: TokenTypeArgCount},
   932  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   933  
   934  			{Value: "3", Type: TokenTypeArgCount},
   935  			{Value: TokenListExpr, Type: TokenTypeListExpr},
   936  
   937  			{Value: "in", Type: ExpressionTokenLogical},
   938  		},
   939  		tree: []expectedParseNode{
   940  			{Value: "in", Depth: 0, Type: ExpressionTokenLogical},
   941  			{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   942  			{Value: "1", Depth: 2, Type: ExpressionTokenInteger},
   943  			{Value: "2", Depth: 2, Type: ExpressionTokenInteger},
   944  			//  ('ab', 'cd'), (1, 2), ('abc', 'def')
   945  			{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   946  			{Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr},
   947  			{Value: "'ab'", Depth: 3, Type: ExpressionTokenString},
   948  			{Value: "'cd'", Depth: 3, Type: ExpressionTokenString},
   949  			{Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr},
   950  			{Value: "1", Depth: 3, Type: ExpressionTokenInteger},
   951  			{Value: "2", Depth: 3, Type: ExpressionTokenInteger},
   952  			{Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr},
   953  			{Value: "'abcdefghijk'", Depth: 3, Type: ExpressionTokenString},
   954  			{Value: "'def'", Depth: 3, Type: ExpressionTokenString},
   955  		},
   956  	},
   957  	{
   958  		// TestExpressionInOperatorBothSides tests the "IN" operator.
   959  		// Use a listExpr on both sides of the IN operator.
   960  		//   listExpr  = OPEN BWS commonExpr BWS *( COMMA BWS commonExpr BWS ) CLOSE
   961  		// Validate if a list is within another list.
   962  		expression: "Name eq 'Milk' and (1, 2) in ( ('ab', 'cd'), (1, 2), ('abc', 'def') )",
   963  		tree: []expectedParseNode{
   964  			{Value: "and", Depth: 0, Type: ExpressionTokenLogical},
   965  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
   966  			{Value: "Name", Depth: 2, Type: ExpressionTokenLiteral},
   967  			{Value: "'Milk'", Depth: 2, Type: ExpressionTokenString},
   968  
   969  			{Value: "in", Depth: 1, Type: ExpressionTokenLogical},
   970  			{Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr},
   971  			{Value: "1", Depth: 3, Type: ExpressionTokenInteger},
   972  			{Value: "2", Depth: 3, Type: ExpressionTokenInteger},
   973  			//  ('ab', 'cd'), (1, 2), ('abc', 'def')
   974  			{Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr},
   975  			{Value: TokenListExpr, Depth: 3, Type: TokenTypeListExpr},
   976  			{Value: "'ab'", Depth: 4, Type: ExpressionTokenString},
   977  			{Value: "'cd'", Depth: 4, Type: ExpressionTokenString},
   978  			{Value: TokenListExpr, Depth: 3, Type: TokenTypeListExpr},
   979  			{Value: "1", Depth: 4, Type: ExpressionTokenInteger},
   980  			{Value: "2", Depth: 4, Type: ExpressionTokenInteger},
   981  			{Value: TokenListExpr, Depth: 3, Type: TokenTypeListExpr},
   982  			{Value: "'abc'", Depth: 4, Type: ExpressionTokenString},
   983  			{Value: "'def'", Depth: 4, Type: ExpressionTokenString},
   984  		},
   985  	},
   986  	{
   987  		expression: "Name eq 'Milk' and Price lt 2.55",
   988  		infixTokens: []*Token{
   989  			{Value: "Name", Type: ExpressionTokenLiteral},
   990  			{Value: "eq", Type: ExpressionTokenLogical},
   991  			{Value: "'Milk'", Type: ExpressionTokenString},
   992  			{Value: "and", Type: ExpressionTokenLogical},
   993  			{Value: "Price", Type: ExpressionTokenLiteral},
   994  			{Value: "lt", Type: ExpressionTokenLogical},
   995  			{Value: "2.55", Type: ExpressionTokenFloat},
   996  		},
   997  		tree: []expectedParseNode{
   998  			{Value: "and", Depth: 0, Type: ExpressionTokenLogical},
   999  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
  1000  			{Value: "Name", Depth: 2, Type: ExpressionTokenLiteral},
  1001  			{Value: "'Milk'", Depth: 2, Type: ExpressionTokenString},
  1002  			{Value: "lt", Depth: 1, Type: ExpressionTokenLogical},
  1003  			{Value: "Price", Depth: 2, Type: ExpressionTokenLiteral},
  1004  			{Value: "2.55", Depth: 2, Type: ExpressionTokenFloat},
  1005  		},
  1006  	},
  1007  	{
  1008  		// The syntax for ODATA functions follows the inline parameter syntax. The function name must be followed
  1009  		// by an opening parenthesis, followed by a comma-separated list of parameters, followed by a closing parenthesis.
  1010  		// For example:
  1011  		// GET serviceRoot/Airports?$filter=contains(Location/Address, 'San Francisco')
  1012  		expression: "contains(LastName, 'Smith') and FirstName eq 'John' and City eq 'Houston'",
  1013  		infixTokens: []*Token{
  1014  			{Value: "contains", Type: ExpressionTokenFunc},
  1015  			{Value: "(", Type: ExpressionTokenOpenParen},
  1016  			{Value: "LastName", Type: ExpressionTokenLiteral},
  1017  			{Value: ",", Type: ExpressionTokenComma},
  1018  			{Value: "'Smith'", Type: ExpressionTokenString},
  1019  			{Value: ")", Type: ExpressionTokenCloseParen},
  1020  			{Value: "and", Type: ExpressionTokenLogical},
  1021  			{Value: "FirstName", Type: ExpressionTokenLiteral},
  1022  			{Value: "eq", Type: ExpressionTokenLogical},
  1023  			{Value: "'John'", Type: ExpressionTokenString},
  1024  			{Value: "and", Type: ExpressionTokenLogical},
  1025  			{Value: "City", Type: ExpressionTokenLiteral},
  1026  			{Value: "eq", Type: ExpressionTokenLogical},
  1027  			{Value: "'Houston'", Type: ExpressionTokenString},
  1028  		},
  1029  		tree: []expectedParseNode{
  1030  			{Value: "and", Depth: 0, Type: ExpressionTokenLogical},
  1031  			{Value: "and", Depth: 1, Type: ExpressionTokenLogical},
  1032  			{Value: "contains", Depth: 2, Type: ExpressionTokenFunc},
  1033  			{Value: "LastName", Depth: 3, Type: ExpressionTokenLiteral},
  1034  			{Value: "'Smith'", Depth: 3, Type: ExpressionTokenString},
  1035  			{Value: "eq", Depth: 2, Type: ExpressionTokenLogical},
  1036  			{Value: "FirstName", Depth: 3, Type: ExpressionTokenLiteral},
  1037  			{Value: "'John'", Depth: 3, Type: ExpressionTokenString},
  1038  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
  1039  			{Value: "City", Depth: 2, Type: ExpressionTokenLiteral},
  1040  			{Value: "'Houston'", Depth: 2, Type: ExpressionTokenString},
  1041  		},
  1042  	},
  1043  	{
  1044  		// Test ODATA syntax with nested function calls
  1045  		expression: "contains(LastName, toupper('Smith')) or FirstName eq 'John'",
  1046  		infixTokens: []*Token{
  1047  			{Value: "contains", Type: ExpressionTokenFunc},
  1048  			{Value: "(", Type: ExpressionTokenOpenParen},
  1049  			{Value: "LastName", Type: ExpressionTokenLiteral},
  1050  			{Value: ",", Type: ExpressionTokenComma},
  1051  			{Value: "toupper", Type: ExpressionTokenFunc},
  1052  			{Value: "(", Type: ExpressionTokenOpenParen},
  1053  			{Value: "'Smith'", Type: ExpressionTokenString},
  1054  			{Value: ")", Type: ExpressionTokenCloseParen},
  1055  			{Value: ")", Type: ExpressionTokenCloseParen},
  1056  			{Value: "or", Type: ExpressionTokenLogical},
  1057  			{Value: "FirstName", Type: ExpressionTokenLiteral},
  1058  			{Value: "eq", Type: ExpressionTokenLogical},
  1059  			{Value: "'John'", Type: ExpressionTokenString},
  1060  		},
  1061  		tree: []expectedParseNode{
  1062  			{Value: "or", Depth: 0, Type: ExpressionTokenLogical},
  1063  			{Value: "contains", Depth: 1, Type: ExpressionTokenFunc},
  1064  			{Value: "LastName", Depth: 2, Type: ExpressionTokenLiteral},
  1065  			{Value: "toupper", Depth: 2, Type: ExpressionTokenFunc},
  1066  			{Value: "'Smith'", Depth: 3, Type: ExpressionTokenString},
  1067  			{Value: "eq", Depth: 1, Type: ExpressionTokenLogical},
  1068  			{Value: "FirstName", Depth: 2, Type: ExpressionTokenLiteral},
  1069  			{Value: "'John'", Depth: 2, Type: ExpressionTokenString},
  1070  		},
  1071  	},
  1072  	{
  1073  		expression: "LastName eq null",
  1074  		tree: []expectedParseNode{
  1075  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
  1076  			{Value: "LastName", Depth: 1, Type: ExpressionTokenLiteral},
  1077  			{Value: "null", Depth: 1, Type: ExpressionTokenNull},
  1078  		},
  1079  	},
  1080  	{
  1081  		expression: "Enabled eq true",
  1082  		tree: []expectedParseNode{
  1083  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
  1084  			{Value: "Enabled", Depth: 1, Type: ExpressionTokenLiteral},
  1085  			{Value: "true", Depth: 1, Type: ExpressionTokenBoolean},
  1086  		},
  1087  	},
  1088  	{
  1089  		// A property navigation path without arguments (see next fixture).
  1090  		expression: "Products/Value",
  1091  		tree: []expectedParseNode{
  1092  			{Value: "/", Depth: 0, Type: ExpressionTokenNav},
  1093  			{Value: "Products", Depth: 1, Type: ExpressionTokenLiteral},
  1094  			{Value: "Value", Depth: 1, Type: ExpressionTokenLiteral},
  1095  		},
  1096  	},
  1097  	{
  1098  		// A property navigation path without arguments (see next fixture).
  1099  		expression: "Products/Value eq 2",
  1100  		tree: []expectedParseNode{
  1101  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
  1102  			{Value: "/", Depth: 1, Type: ExpressionTokenNav},
  1103  			{Value: "Products", Depth: 2, Type: ExpressionTokenLiteral},
  1104  			{Value: "Value", Depth: 2, Type: ExpressionTokenLiteral},
  1105  			{Value: "2", Depth: 1, Type: ExpressionTokenInteger},
  1106  		},
  1107  	},
  1108  	{
  1109  		// Common Schema Definition Language (CSDL) JSON Representation, Section 14.4.1 specifies the syntax of path expressions.
  1110  		// See Example 65 in section 14.4.1.1
  1111  		expression: "Products(sku='abc123',vendor='globex')/Value",
  1112  		infixTokens: []*Token{
  1113  			{Value: "Products", Type: ExpressionTokenLiteral},
  1114  			{Value: "(", Type: ExpressionTokenOpenParen},
  1115  			{Value: "sku", Type: ExpressionTokenLiteral},
  1116  			{Value: "=", Type: ExpressionTokenAssignement},
  1117  			{Value: "'abc123'", Type: ExpressionTokenString},
  1118  			{Value: ",", Type: ExpressionTokenComma},
  1119  			{Value: "vendor", Type: ExpressionTokenLiteral},
  1120  			{Value: "=", Type: ExpressionTokenAssignement},
  1121  			{Value: "'globex'", Type: ExpressionTokenString},
  1122  			{Value: ")", Type: ExpressionTokenCloseParen},
  1123  			{Value: "/", Type: ExpressionTokenNav},
  1124  			{Value: "Value", Type: ExpressionTokenLiteral},
  1125  		},
  1126  		postfixTokens: []*Token{
  1127  			{Value: "sku", Type: ExpressionTokenLiteral},
  1128  			{Value: "'abc123'", Type: ExpressionTokenString},
  1129  			{Value: "=", Type: ExpressionTokenAssignement},
  1130  			{Value: "vendor", Type: ExpressionTokenLiteral},
  1131  			{Value: "'globex'", Type: ExpressionTokenString},
  1132  			{Value: "=", Type: ExpressionTokenAssignement},
  1133  			{Value: "2", Type: TokenTypeArgCount}, // The argument count
  1134  			{Value: TokenListExpr, Type: TokenTypeListExpr},
  1135  			{Value: "Products", Type: ExpressionTokenLiteral},
  1136  			{Value: "Value", Type: ExpressionTokenLiteral},
  1137  			{Value: "/", Type: ExpressionTokenNav},
  1138  		},
  1139  		tree: []expectedParseNode{
  1140  			{Value: "/", Depth: 0, Type: ExpressionTokenNav},
  1141  			{Value: "Products", Depth: 1, Type: ExpressionTokenLiteral},
  1142  			{Value: "=", Depth: 2, Type: ExpressionTokenAssignement},
  1143  			{Value: "sku", Depth: 3, Type: ExpressionTokenLiteral},
  1144  			{Value: "'abc123'", Depth: 3, Type: ExpressionTokenString},
  1145  			{Value: "=", Depth: 2, Type: ExpressionTokenAssignement},
  1146  			{Value: "vendor", Depth: 3, Type: ExpressionTokenLiteral},
  1147  			{Value: "'globex'", Depth: 3, Type: ExpressionTokenString},
  1148  			{Value: "Value", Depth: 1, Type: ExpressionTokenLiteral},
  1149  		},
  1150  	},
  1151  	{
  1152  		// Navigation within a collection with a single argument.
  1153  		expression: "Products(sku='abc123')/Value",
  1154  		tree: []expectedParseNode{
  1155  			{Value: "/", Depth: 0, Type: ExpressionTokenNav},
  1156  			{Value: "Products", Depth: 1, Type: ExpressionTokenLiteral},
  1157  			{Value: "=", Depth: 2, Type: ExpressionTokenAssignement},
  1158  			{Value: "sku", Depth: 3, Type: ExpressionTokenLiteral},
  1159  			{Value: "'abc123'", Depth: 3, Type: ExpressionTokenString},
  1160  			{Value: "Value", Depth: 1, Type: ExpressionTokenLiteral},
  1161  		},
  1162  	},
  1163  	{
  1164  		// Navigation within nested collections.
  1165  		expression: "Products(sku='abc123')/Components(id='abc')/Name",
  1166  		tree: []expectedParseNode{
  1167  			{Value: "/", Depth: 0, Type: ExpressionTokenNav},
  1168  			{Value: "/", Depth: 1, Type: ExpressionTokenNav},
  1169  			{Value: "Products", Depth: 2, Type: ExpressionTokenLiteral},
  1170  			{Value: "=", Depth: 3, Type: ExpressionTokenAssignement},
  1171  			{Value: "sku", Depth: 4, Type: ExpressionTokenLiteral},
  1172  			{Value: "'abc123'", Depth: 4, Type: ExpressionTokenString},
  1173  			{Value: "Components", Depth: 2, Type: ExpressionTokenLiteral},
  1174  			{Value: "=", Depth: 3, Type: ExpressionTokenAssignement},
  1175  			{Value: "id", Depth: 4, Type: ExpressionTokenLiteral},
  1176  			{Value: "'abc'", Depth: 4, Type: ExpressionTokenString},
  1177  			{Value: "Name", Depth: 1, Type: ExpressionTokenLiteral},
  1178  		},
  1179  	},
  1180  	{
  1181  		// Navigation within property collection and sub-expression.
  1182  		expression: "Products(sku=concat('abc', '123'))/Name",
  1183  		tree: []expectedParseNode{
  1184  			{Value: "/", Depth: 0, Type: ExpressionTokenNav},
  1185  			{Value: "Products", Depth: 1, Type: ExpressionTokenLiteral},
  1186  			{Value: "=", Depth: 2, Type: ExpressionTokenAssignement},
  1187  			{Value: "sku", Depth: 3, Type: ExpressionTokenLiteral},
  1188  			{Value: "concat", Depth: 3, Type: ExpressionTokenFunc},
  1189  			{Value: "'abc'", Depth: 4, Type: ExpressionTokenString},
  1190  			{Value: "'123'", Depth: 4, Type: ExpressionTokenString},
  1191  			{Value: "Name", Depth: 1, Type: ExpressionTokenLiteral},
  1192  		},
  1193  	},
  1194  	{
  1195  		// Navigation within a collection with a single argument.
  1196  		// TODO: should we allow this?
  1197  		expression: "Products('abc123')/Value",
  1198  		tree: []expectedParseNode{
  1199  			{Value: "/", Depth: 0, Type: ExpressionTokenNav},
  1200  			{Value: "Products", Depth: 1, Type: ExpressionTokenLiteral},
  1201  			{Value: "'abc123'", Depth: 2, Type: ExpressionTokenString},
  1202  			{Value: "Value", Depth: 1, Type: ExpressionTokenLiteral},
  1203  		},
  1204  	},
  1205  	{
  1206  		// Navigation within a collection with a single argument, no nested path.
  1207  		expression: "Products(sku='abc123')",
  1208  		postfixTokens: []*Token{
  1209  			{Value: "sku", Type: ExpressionTokenLiteral},
  1210  			{Value: "'abc123'", Type: ExpressionTokenString},
  1211  			{Value: "=", Type: ExpressionTokenAssignement},
  1212  			{Value: "1", Type: TokenTypeArgCount}, // The argument count
  1213  			{Value: TokenListExpr, Type: TokenTypeListExpr},
  1214  			{Value: "Products", Type: ExpressionTokenLiteral},
  1215  		},
  1216  		tree: []expectedParseNode{
  1217  			{Value: "Products", Depth: 0, Type: ExpressionTokenLiteral},
  1218  			{Value: "=", Depth: 1, Type: ExpressionTokenAssignement},
  1219  			{Value: "sku", Depth: 2, Type: ExpressionTokenLiteral},
  1220  			{Value: "'abc123'", Depth: 2, Type: ExpressionTokenString},
  1221  		},
  1222  	},
  1223  	{
  1224  		// Because 'not' is right-associative, the expression below is the same as not (not true).
  1225  		// If it were left-associative, it would be parsed as (not not) true, which is not a valid expression.
  1226  		expression: "not not true",
  1227  		tree: []expectedParseNode{
  1228  			{Value: "not", Depth: 0, Type: ExpressionTokenLogical},
  1229  			{Value: "not", Depth: 1, Type: ExpressionTokenLogical},
  1230  			{Value: "true", Depth: 2, Type: ExpressionTokenBoolean},
  1231  		},
  1232  	},
  1233  	{
  1234  		// duration      = [ "duration" ] SQUOTE durationValue SQUOTE
  1235  		expression: "TaskDuration eq duration'P12DT23H59M59.999999999999S'",
  1236  		tree: []expectedParseNode{
  1237  			{Value: "eq", Depth: 0, Type: ExpressionTokenLogical},
  1238  			{Value: "TaskDuration", Depth: 1, Type: ExpressionTokenLiteral},
  1239  			{Value: "P12DT23H59M59.999999999999S", Depth: 1, Type: ExpressionTokenDuration},
  1240  		},
  1241  	},
  1242  	{
  1243  		expression: "totalseconds(EndTime sub StartTime) lt duration'PT23H59M'",
  1244  		tree: []expectedParseNode{
  1245  			{Value: "lt", Depth: 0, Type: ExpressionTokenLogical},
  1246  			{Value: "totalseconds", Depth: 1, Type: ExpressionTokenFunc},
  1247  			{Value: "sub", Depth: 2, Type: ExpressionTokenOp},
  1248  			{Value: "EndTime", Depth: 3, Type: ExpressionTokenLiteral},
  1249  			{Value: "StartTime", Depth: 3, Type: ExpressionTokenLiteral},
  1250  			{Value: "PT23H59M", Depth: 1, Type: ExpressionTokenDuration},
  1251  		},
  1252  	},
  1253  }