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 }