-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathGRAMMAR.ABNF
649 lines (627 loc) · 31.5 KB
/
GRAMMAR.ABNF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
; This file is parsed to generate the specification included in the website
; Double semicolons (;;) at the beginning of a line represent documentation in markdown format for the preceding rule
; intended for inclusion in the html version of the specification.
; Double semicolons inline with a rule represent an alternative header to use when rendering markdown. This can optionally
; include a trailing '\' to indicate that it is to be included with the documentation of the following rule.
;; In this specification, examples are shown through the use of a search function. The syntax for this function is:
;;
;; ```
;; search(<jmespath expr>, <JSON document>) -> <return value>
;; ```
;;
;; For simplicity, the jmespath expression and the JSON document are not quoted. For example:
;;
;; ```
;; search(foo, {"foo": "bar"}) -> "bar"
;; ```
;;
;; The result of applying a JMESPath expression against a JSON document will always result in valid JSON, provided there
;; are no errors during the evaluation process. Structured data in, structured data out.
;;
;; This also means that, with the exception of JMESPath expression types, JMESPath only supports the same types support by JSON:
;;
;; - number (integers and double-precision floating-point format in JSON)
;; - string
;; - boolean (true or false)
;; - array (an ordered, sequence of values)
;; - object (an unordered collection of key value pairs)
;; - null
;;
;; Expression types are discussed in the [Function Expressions](#function-expressions) section.
;;
;; Implementations can map the corresponding JSON types to their language equivalent. For example, a JSON null could map to
;; None in python, and nil in ruby and go.
;;
;; # Grammar
;;
;; JMESPath grammar is specified using ABNF, as described in RFC4234
;;
;; In addition to the grammar, there is the following token precedence that goes from weakest to tightest binding:
;;
;; - pipe: |
;; - or: ||
;; - and: &&
;; - unary not: !
;; - rbracket: ]
;;
;; %%GRAMMAR%%
expression = sub-expression / index-expression / comparator-expression / list-filter-expr
expression =/ or-expression / identifier
expression =/ and-expression / not-expression / paren-expression
expression =/ multi-select-list / multi-select-hash / literal
expression =/ function-expression / pipe-expression / raw-string
expression =/ current-node
sub-expression = expression "." ( identifier / multi-select-list / multi-select-hash / function-expression / "*" ) ;; # Sub-expressions
;; A sub-expression is a combination of two expressions separated by the '.' char. A sub-expression is evaluated as follows:
;;
;; - Evaluate the expression on the left with the original JSON document.
;; - Evaluate the expression on the right with the result of the left expression evaluation.
;;
;; In pseudocode:
;; ```
;; left-evaluation = search(left-expression, original-json-document)
;; result = search(right-expression, left-evaluation)
;; ```
;; A sub-expression is itself an expression, so there can be multiple levels of sub-expressions: grandparent.parent.child.
;; Examples
;;
;; Given a JSON document: ``{"foo": {"bar": "baz"}}``, and a jmespath expression: foo.bar, the evaluation process would be:
;; ```
;; left-evaluation = search("foo", {"foo": {"bar": "baz"}}) -> {"bar": "baz"}
;; result = search("bar": {"bar": "baz"}) -> "baz"
;; ```
;; The final result in this example is "baz".
;;
;; Additional examples:
;; ```
;; search(foo.bar, {"foo": {"bar": "value"}}) -> "value"
;; search(foo."bar", {"foo": {"bar": "value"}}) -> "value"
;; search(foo.bar, {"foo": {"baz": "value"}}) -> null
;; search(foo.bar.baz, {"foo": {"bar": {"baz": "value"}}}) -> "value"
;; ```
pipe-expression = expression "|" expression ;; # Pipe Expressions
;; A pipe expression combines two expressions, separated by the `|` character. It is similar to a sub-expression with two
;; important distinctions:
;;
;; 1. Any expression can be used on the right hand side. A sub-expression restricts the type of expression that can be
;; used on the right hand side.
;; 1. A pipe-expression stops projections on the left hand side for propagating to the right hand side. If the left
;; expression creates a projection, it does not apply to the right hand side.
;;
;; For example, given the following data:
;;
;; ```
;; {"foo": [{"bar": ["first1", "second1"]}, {"bar": ["first2", "second2"]}]}
;; ```
;;
;; The expression `foo[*].bar` gives the result of:
;;
;; ```
;; [
;; [
;; "first1",
;; "second1"
;; ],
;; [
;; "first2",
;; "second2"
;; ]
;; ]
;; ```
;;
;; The first part of the expression, `foo[*]`, creates a projection. At this point, the remaining expression, bar is
;; projected onto each element of the list created from `foo[*]`. If you project the `[0]` expression, you will get the
;; first element from each sub list. The expression `foo[*].bar[0]` will return:
;;
;; ```
;; ["first1", "first2"]
;; ```
;;
;; If you instead wanted only the first sub list, `["first1", "second1"]`, you can use a `pipe-expression`:
;;
;; ```
;; foo[*].bar[0] -> ["first1", "first2"]
;; foo[*].bar | [0] -> ["first1", "second1"]
;; ```
;;
;; ## Examples
;;
;; ```
;; search(foo | bar, {"foo": {"bar": "baz"}}) -> "baz"
;; search(foo[*].bar | [0], {
;; "foo": [{"bar": ["first1", "second1"]},
;; {"bar": ["first2", "second2"]}]}) -> ["first1", "second1"]
;; search(foo | [0], {"foo": [0, 1, 2]}) -> [0]
;; ```
or-expression = expression "||" expression ;; # Or Expressions
;; An or expression will evaluate to either the left expression or the right expression. If the evaluation of the left
;; expression is not false it is used as the return value. If the evaluation of the right expression is not false it is
;; used as the return value. If neither the left or right expression are non-null, then a value of null is returned. A
;; false value corresponds to any of the following conditions:
;;
;; - Empty list: []
;; - Empty object: {}
;; - Empty string: ""
;; - False boolean: false
;; - Null value: null
;;
;; A true value corresponds to any value that is not false.
;;
;; ## Examples
;; ```
;; search(foo || bar, {"foo": "foo-value"}) -> "foo-value"
;; search(foo || bar, {"bar": "bar-value"}) -> "bar-value"
;; search(foo || bar, {"foo": "foo-value", "bar": "bar-value"}) -> "foo-value"
;; search(foo || bar, {"baz": "baz-value"}) -> null
;; search(foo || bar || baz, {"baz": "baz-value"}) -> "baz-value"
;; search(override || mylist[-1], {"mylist": ["one", "two"]}) -> "two"
;; search(override || mylist[-1], {"mylist": ["one", "two"], "override": "yes"}) -> "yes"
;; ```
and-expression = expression "&&" expression ;; # And Expressions
;; An and expression will evaluate to either the left expression or the right expression. If the expression on the left
;; hand side is a truth-like value, then the value on the right hand side is returned. Otherwise the result of the
;; expression on the left hand side is returned. This also reduces to the expected truth table:
;;
;; Truth table for and expressions
;;
;; | LHS | RHS | Result |
;; | --- | --- | ------ |
;; | True | True | True |
;; | True | False | False |
;; | False | True | False |
;; | False | False | False |
;;
;; This is the standard truth table for a logical conjunction (AND).
;;
;; ## Examples
;; ```
;; search(True && False, {"True": true, "False": false}) -> false
;; search(Number && EmptyList, {"Number": 5, EmptyList: []}) -> []
;; search(foo[?a == `1` && b == `2`],
;; {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]}) -> [{"a": 1, "b": 2}]
;; ```
not-expression = "!" expression ;; # Not Expressions
;; A not-expression negates the result of an expression. If the expression results in a truth-like value, a not-expression
;; will change this value to false. If the expression results in a false-like value, a not-expression will change this value
;; to true.
;;
;; ## Examples
;; ```
;; search(!True, {"True": true}) -> false
;; search(!False, {"False": false}) -> true
;; search(!Number, {"Number": 5}) -> false
;; search(!EmptyList, {"EmptyList": []}) -> true
;; ```
paren-expression = "(" expression ")" ;; # Parenthetical Expressions
;; A paren-expression allows a user to override the precedence order of an expression, e.g. (a || b) && c.
;;
;; ## Examples
;; ```
;; search(foo[?(a == `1` || b ==`2`) && c == `5`],
;; {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}) -> []
;; ```
index-expression = expression bracket-specifier / bracket-specifier ;; # Index Expressions \
bracket-specifier = "[" (number / slice-expression) "]"
;; An index expression is used to access elements in a list. Indexing is 0 based, the index of 0 refers to the first
;; element of the list. A negative number is a valid index. A negative number indicates that indexing is relative to the
;; end of the list, specifically:
;;
;; ```
;; negative-index == (length of array) + negative-index
;; ```
;;
;; Given an array of length N, an index of -1 would be equal to a positive index of N - 1, which is the last element of
;; the list. If an index expression refers to an index that is greater than the length of the array, a value of null is returned.
;;
;; For the grammar rule expression bracket-specifier the expression is first evaluated, and then return value from the
;; expression is given as input to the bracket-specifier.
;;
;; Using a "*" character within a bracket-specifier is discussed below in the wildcard expressions section.
bracket-specifier =/ "[]" ;; ## Flatten Operator
;; When the character sequence [] is provided as a bracket specifier, then a flattening operation occurs on the current
;; result. The flattening operator will merge sublists in the current result into a single list. The flattening operator
;; has the following semantics:
;;
;; - Create an empty result list.
;; - Iterate over the elements of the current result.
;; - If the current element is not a list, add to the end of the result list.
;; - If the current element is a list, add each element of the current element to the end of the result list.
;; - The result list is now the new current result.
;;
;; Once the flattening operation has been performed, subsequent operations are projected onto the flattened list with the
;; same semantics as a wildcard expression. Thus the difference between [*] and [] is that [] will first flatten sublists
;; in the current result.
;;
;; ### Examples
;; ```
;; search([0], ["first", "second", "third"]) -> "first"
;; search([-1], ["first", "second", "third"]) -> "third"
;; search([100], ["first", "second", "third"]) -> null
;; search(foo[0], {"foo": ["first", "second", "third"]) -> "first"
;; search(foo[100], {"foo": ["first", "second", "third"]) -> null
;; search(foo[0][0], {"foo": [[0, 1], [1, 2]]}) -> 0
;; ```
slice-expression = [number] ":" [number] [ ":" [number] ] ;; ## Slices
;; A slice expression allows you to select a contiguous subset of an array. A slice has a start, stop, and step value. The
;; general form of a slice is [start:stop:step], but each component is optional and can be omitted.
;;
;; ```note
;; Slices in JMESPath have the same semantics as python slices. If you're familiar with python slices, you're familiar with
;; JMESPath slices.
;; ```
;;
;; Given a start, stop, and step value, the sub elements in an array are extracted as follows:
;;
;; - The first element in the extracted array is the index denoted by start.
;; - The last element in the extracted array is the index denoted by end - 1.
;; - The step value determines how many indices to skip after each element is selected from the array. An array of 1
;; (the default step) will not skip any indices. A step value of 2 will skip every other index while extracting elements
;; from an array. A step value of -1 will extract values in reverse order from the array.
;;
;; Slice expressions adhere to the following rules:
;;
;; - If a negative start position is given, it is calculated as the total length of the array plus the given start position.
;; - If no start position is given, it is assumed to be 0 if the given step is greater than 0 or the end of the array if
;; the given step is less than 0.
;; - If a negative stop position is given, it is calculated as the total length of the array plus the given stop position.
;; - If no stop position is given, it is assumed to be the length of the array if the given step is greater than 0 or 0 if
;; the given step is less than 0.
;; - If the given step is omitted, it it assumed to be 1.
;; - If the given step is 0, an error MUST be raised.
;; - If the element being sliced is not an array, the result is null.
;; - If the element being sliced is an array and yields no results, the result MUST be an empty array.
;;
;; ### Examples
;; ```
;; search([0:4:1], [0, 1, 2, 3]) -> [0, 1, 2, 3]
;; search([0:4], [0, 1, 2, 3]) -> [0, 1, 2, 3]
;; search([0:3], [0, 1, 2, 3]) -> [0, 1, 2]
;; search([:2], [0, 1, 2, 3]) -> [0, 1]
;; search([::2], [0, 1, 2, 3]) -> [0, 2]
;; search([::-1], [0, 1, 2, 3]) -> [3, 2, 1, 0]
;; search([-2:], [0, 1, 2, 3]) -> [2, 3]
;; ```
multi-select-list = "[" ( expression *( "," expression ) ) "]" ;; # MultiSelect List
;; A multiselect expression is used to extract a subset of elements from a JSON hash. There are two version of multiselect,
;; one in which the multiselect expression is enclosed in `{...}` and one which is enclosed in `[...]`. This section describes
;; the `[...]` version. Within the start and closing characters is one or more non expressions separated by a comma. Each
;; expression will be evaluated against the JSON document. Each returned element will be the result of evaluating the
;; expression. A multi-select-list with N expressions will result in a list of length N. Given a multiselect expression
;; `[expr-1,expr-2,...,expr-n]`, the evaluated expression will return `[evaluate(expr-1), evaluate(expr-2), ..., evaluate(expr-n)]`.
;;
;; ## Examples
;; ```
;; search([foo,bar], {"foo": "a", "bar": "b", "baz": "c"}) -> ["a", "b"]
;; search([foo,bar[0]], {"foo": "a", "bar": ["b"], "baz": "c"}) -> ["a", "b"]
;; search([foo,bar.baz], {"foo": "a", "bar": {"baz": "b"}}) -> ["a", "b"]
;; search([foo,baz], {"foo": "a", "bar": "b"}) -> ["a", null]
;; ```
multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" ;; # MultiSelect Hash \
keyval-expr = identifier ":" expression
;; A multi-select-hash expression is similar to a multi-select-list expression, except that a hash is created instead of a
;; list. A multi-select-hash expression also requires key names to be provided, as specified in the keyval-expr rule.
;; Given the following rule:
;;
;; ```
;; keyval-expr = identifier ":" expression
;; ```
;;
;; The identifier is used as the key name and the result of evaluating the expression is the value associated with the identifier key.
;;
;; Each keyval-expr within the multi-select-hash will correspond to a single key value pair in the created hash.
;;
;; ## Examples
;;
;; Given a multi-select-hash expression `{foo: one.two, bar: bar}` and the data `{"bar": "bar", {"one": {"two": "one-two"}}}`,
;; the expression is evaluated as follows:
;;
;; - A hash is created: {}
;; - A key foo is created whose value is the result of evaluating one.two against the provided JSON document: `{"foo": evaluate(one.two, <data>)}`
;; - A key bar is created whose value is the result of evaluting the expression bar against the provided JSON document.
;;
;; The final result will be: `{"foo": "one-two", "bar": "bar"}`.
;;
;; Additional examples:
;; ```
;; search({foo: foo, bar: bar}, {"foo": "a", "bar": "b", "baz": "c"})
;; -> {"foo": "a", "bar": "b"}
;; search({foo: foo, firstbar: bar[0]}, {"foo": "a", "bar": ["b"]})
;; -> {"foo": "a", "firstbar": "b"}
;; search({foo: foo, "bar.baz": bar.baz}, {"foo": "a", "bar": {"baz": "b"}})
;; -> {"foo": "a", "bar.baz": "b"}
;; search({foo: foo, baz: baz}, {"foo": "a", "bar": "b"})
;; -> {"foo": "a", "baz": null}
;; ```
expression =/ "*" ;; # Wildcard Expressions \
bracket-specifier =/ "[" "*" "]"
;; A wildcard expression is a expression of either `*` or `[*]`. A wildcard expression can return multiple elements, and the
;; remaining expressions are evaluated against each returned element from a wildcard expression. The `[*]` syntax applies to
;; a list type and the `*`syntax applies to a hash type.
;;
;; The `[*]` syntax (referred to as a list wildcard expression) will return all the elements in a list. Any subsequent
;; expressions will be evaluated against each individual element. Given an expression `[*].child-expr`, and a list of N
;; elements, the evaluation of this expression would be `[child-expr(el-0), child-expr(el-2), ..., child-expr(el-N)]`.
;; This is referred to as a projection, and the child-expr expression is projected onto the elements of the resulting list.
;;
;; Once a projection has been created, all subsequent expressions are projected onto the resulting list.
;;
;; The * syntax (referred to as a hash wildcard expression) will return a list of the hash element's values. Any subsequent
;; expression will be evaluated against each individual element in the list (this is also referred to as a projection).
;;
;; Note that if any subsequent expression after a wildcard expression returns a null value, it is omitted from the final
;; result list.
;;
;; A list wildcard expression is only valid for the JSON array type. If a list wildcard expression is applied to any other
;; JSON type, a value of `null` is returned.
;;
;; Similarly, a hash wildcard expression is only valid for the JSON object type. If a hash wildcard expression is applied
;; to any other JSON type, a value of null is returned. Note that JSON hashes are explicitly defined as unordered.
;; Therefore a hash wildcard expression can return the values associated with the hash in any order. Implementations
;; are not required to return the hash values in any specific order.
;;
;; ## Examples
;; ```
;; search([*].foo, [{"foo": 1}, {"foo": 2}, {"foo": 3}]) -> [1, 2, 3]
;; search([*].foo, [{"foo": 1}, {"foo": 2}, {"bar": 3}]) -> [1, 2]
;; search('*.foo', {"a": {"foo": 1}, "b": {"foo": 2}, "c": {"bar": 1}}) -> [1, 2]
;; ```
list-filter-expr = "[?" expression "]" ;; # Filter Expressions \
comparator-expression = expression comparator expression ;; \
comparator = "<" / "<=" / "==" / ">=" / ">" / "!="
;; A filter expression provides a way to select JSON elements based on a comparison to another expression. A filter
;; expression is evaluated as follows: for each element in an array evaluate the expression against the element. If the
;; expression evaluates to a truth-like value, the item (in its entirety) is added to the result list. Otherwise it is
;; excluded from the result list. A filter expression is only defined for a JSON array. Attempting to evaluate a filter
;; expression against any other type will return `null`.
;;
;; ## Comparison Operators
;;
;; The following operations are supported:
;;
;; - ==, tests for equality.
;; - !=, tests for inequality.
;; - <, less than.
;; - <=, less than or equal to.
;; - >, greater than.
;; - >=, greater than or equal to.
;;
;; The behavior of each operation is dependent on the type of each evaluated expression.
;;
;; The comparison semantics for each operator are defined below based on the corresponding JSON type:
;;
;; ## Equality Operators
;;
;; For `string`/`number`/`true`/`false`/`null` types, equality is an exact match. A string is equal to another string if
;; they they have the exact sequence of code points. The literal values `true`/`false`/`null` are only equal to their own literal
;; values. Two JSON objects are equal if they have the same set of keys and values (given two JSON objects x and y, for each
;; key value pair (i, j) in x, there exists an equivalent pair (i, j) in y). Two JSON arrays are equal if they have equal
;; elements in the same order (given two arrays x and y, for each i from 0 until length(x), x[i] == y[i]).
;;
;; ## Ordering Operators
;;
;; Ordering operators `>`, `>=`, `<`, `<=` are only valid for numbers. Evaluating any other type with a comparison operator
;; will yield a `null` value, which will result in the element being excluded from the result list. For example, given:
;;
;; ```
;; search('foo[?a<b]', {"foo": [{"a": "char", "b": "char"},
;; {"a": 2, "b": 1},
;; {"a": 1, "b": 2}]})
;; ```
;;
;; The three elements in the foo list are evaluated against a < b. The first element resolves to the comparison "char" <
;; "bar", and because these types are string, the expression results in null, so the first element is not included in the
;; result list. The second element resolves to 2 < 1, which is false, so the second element is excluded from the result
;; list. The third expression resolves to 1 < 2 which evaluates to true, so the third element is included in the list.
;; The final result of that expression is `[{"a": 1, "b": 2}]`.
;;
;; ## Examples
;;
;; ```
;; search(foo[?bar==`10`], {"foo": [{"bar": 1}, {"bar": 10}]}) -> [{"bar": 10}]
;; search([?bar==`10`], [{"bar": 1}, {"bar": 10}]}) -> [{"bar": 10}]
;; search(foo[?a==b], {"foo": [{"a": 1, "b": 2}, {"a": 2, "b": 2}]}) -> [{"a": 2, "b": 2}]
;; ```
function-expression = unquoted-string ( no-args / one-or-more-args ) ;; # Function Expressions \
no-args = "(" ")" ;; \
one-or-more-args = "(" ( function-arg *( "," function-arg ) ) ")" ;; \
function-arg = expression / expression-type ;; \
current-node = "@" ;; \
expression-type = "&" expression
;; Functions allow users to easily transform and filter data in JMESPath expressions.
;;
;; ## Data Types
;;
;; In order to support functions, a type system is needed. The JSON types are used:
;;
;; - number (integers and double-precision floating-point format in JSON)
;; - string
;; - boolean (true or false)
;; - array (an ordered, sequence of values)
;; - object (an unordered collection of key value pairs)
;; - null
;;
;; There is also an additional type that is not a JSON type that's used in JMESPath functions:
;;
;; - expression (denoted by `&expression`)
;;
;; ## current-node
;;
;; The `current-node` token can be used to represent the current node being evaluated. The current-node token is useful
;; for functions that require the current node being evaluated as an argument. For example, the following expression
;; creates an array containing the total number of elements in the foo object followed by the value of `foo["bar"]`.
;;
;; ```
;; foo[].[count(@), bar]
;; ```
;;
;; JMESPath assumes that all function arguments operate on the current node unless the argument is a literal or number
;; token. Because of this, an expression such as @.bar would be equivalent to just bar, so the current node is only
;; allowed as a bare expression.
;;
;; ### current-node state
;;
;; At the start of an expression, the value of the current node is the data being evaluated by the JMESPath expression.
;; As an expression is evaluated, the value the the current node represents MUST change to reflect the node currently
;; being evaluated. When in a projection, the current node value must be changed to the node currently being evaluated
;; by the projection.
;;
;; # Function Evaluation
;;
;; Functions are evaluated in applicative order. Each argument must be an expression, each argument expression must be
;; evaluated before evaluating the function. The function is then called with the evaluated function arguments. The result
;; of the `function-expression` is the result returned by the function call. If a `function-expression` is evaluated for a
;; function that does not exist, the JMESPath implementation must indicate to the caller that an `unknown-function` error
;; occurred. How and when this error is raised is implementation specific, but implementations should indicate to the caller
;; that this specific error occurred.
;;
;; Functions can either have a specific arity or be variadic with a minimum number of arguments. If a `function-expression`
;; is encountered where the arity does not match or the minimum number of arguments for a variadic function is not provided,
;; then implementations must indicate to the caller than an `invalid-arity` error occurred. How and when this error is raised
;; is implementation specific.
;;
;; Each function signature declares the types of its input parameters. If any type constraints are not met, implementations
;; must indicate that an `invalid-type` error occurred.
;;
;; In order to accommodate type constraints, functions are provided to convert types to other types (to_string, to_number)
;; which are defined below. No explicit type conversion happens unless a user specifically uses one of these type conversion
;; functions.
;;
;; Function expressions are also allowed as the child element of a sub expression. This allows functions to be used with
;; projections, which can enable functions to be applied to every element in a projection. For example, given the input
;; data of `["1", "2", "3", "notanumber", true]`, the following expression can be used to convert (and filter) all elements
;; to numbers:
;;
;; ```
;; search([].to_number(@), ``["1", "2", "3", "notanumber", true]``) -> [1, 2, 3]
;; ```
;;
;; This provides a simple mechanism to explicitly convert types when needed.
raw-string = "'" *raw-string-char "'" ;; # Raw String Literals \
raw-string-char = (%x20-26 / %x28-5B / %x5D-10FFFF) / preserved-escape / raw-string-escape ;; \
preserved-escape = escape (%x20-26 / %x28-5B / %x5D-10FFFF) ;; \
raw-string-escape = escape ("'" / escape)
;; A raw string is an expression that allows for a literal string value to be specified. The result of evaluating the raw
;; string literal expression is the literal string value. It is a simpler form of a literal expression that is special
;; cased for strings. For example, the following expressions both evaluate to the same value: "foo":
;;
;; ```
;; search(`"foo"`, "") -> "foo"
;; search('foo', "") -> "foo"
;; ```
;;
;; As you can see in the examples above, it is meant as a more succinct form of the common scenario of specifying a literal
;; string value.
;;
;; In addition, it does not perform any of the additional processing that JSON strings supports including:
;;
;; - Not expanding unicode escape sequences
;; - Not expanding newline characters
;; - Not expanding tab characters or any other escape sequences documented in RFC 4627 section 2.5.
;;
;; ## Examples
;; ```
;; search('foo', "") -> "foo"
;; search(' bar ', "") -> " bar "
;; search('[baz]', "") -> "[baz]"
;; search('[baz]', "") -> "[baz]"
;; search('\u03a6', "") -> "\u03a6"
;; search('\\', "") -> "\\"
;; ```
literal = "`" json-value "`" ;; # Literal Expressions
;; A literal expression is an expression that allows arbitrary JSON objects to be specified. This is useful in filter
;; expressions as well as multi select hashes (to create arbitrary key value pairs), but is allowed anywhere an expression
;; is allowed. The specification includes the ABNF for JSON, implementations should use an existing JSON parser to parse
;; literal values. Note that the \` character must now be escaped in a json-value which means implementations need to
;; handle this case before passing the resulting string to a JSON parser.
;;
;; ## Examples
;; ```
;; search(`"foo"`, "anything") -> "foo"
;; search(`"foo\`bar"`, "anything") -> "foo`bar"
;; search(`[1, 2]`, "anything") -> [1, 2]
;; search(`true`, "anything") -> true
;; search(`{"a": "b"}`.a, "anything") -> "b"
;; search({first: a, type: `"mytype"`}, {"a": "b", "c": "d"}) -> {"first": "b", "type": "mytype"}
;; ```
unescaped-literal = %x20-21 / ; space !
%x23-5B / ; # - [
%x5D-5F / ; ] ^ _
%x61-7A / ; a-z
%x7C-10FFFF ; |}~ ...
escaped-literal = escaped-char / (escape "`")
number = ["-"] 1*digit
digit = %x30-39
identifier = unquoted-string / quoted-string ;; # Identifiers
;; An identifier is the most basic expression and can be used to extract a single element from a JSON document.
;; The return value for an identifier is the value associated with the identifier. If the identifier does not exist in the
;; JSON document, than a null value is returned.
;; From the grammar rule listed above identifiers can be one or more characters, and must start with A-Za-z_.
;; An identifier can also be quoted. This is necessary when an identifier has characters not specified in the
;; unquoted-string grammar rule. In this situation, an identifier is specified with a double quote, followed by any number
;; of unescaped-char or escaped-char characters, followed by a double quote. The quoted-string rule is the same grammar
;; rule as a JSON string, so any valid string can be used between double quoted, include JSON supported escape sequences,
;; and six character unicode escape sequences.
;; Note that any identifier that does not start with A-Za-z_ must be quoted.
;; ## Examples:
;; ```
;; search(foo, {"foo": "value"}) -> "value"
;; search(bar, {"foo": "value"}) -> null
;; search(foo, {"foo": [0, 1, 2]}) -> [0, 1, 2]
;; search("with space", {"with space": "value"}) -> "value"
;; search("special chars: !@#", {"special chars: !@#": "value"}) -> "value"
;; search("quote\"char", {"quote\"char": "value"}) -> "value"
;; search("\u2713", {"\u2713": "value"}) -> "value"
;; ```
unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; A-Za-z_
%x30-39 / ; 0-9
%x41-5A / ; A-Z
%x5F / ; _
%x61-7A) ; a-z
quoted-string = quote 1*(unescaped-char / escaped-char) quote
unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF
escape = "\"
quote = %x22 ; Double quote: '"'
escaped-char = escape (
%x22 / ; " quotation mark U+0022
%x5C / ; \ reverse solidus U+005C
%x2F / ; / solidus U+002F
%x62 / ; b backspace U+0008
%x66 / ; f form feed U+000C
%x6E / ; n line feed U+000A
%x72 / ; r carriage return U+000D
%x74 / ; t tab U+0009
%x75 4HEXDIG ) ; uXXXX U+XXXX
; The ``json-value`` is any valid JSON value with the one exception that the
; ``%x60`` character must be escaped. While it's encouraged that implementations
; use any existing JSON parser for this grammar rule (after handling the escaped
; literal characters), the grammar rule is shown below for completeness::
json-value = false / null / true / json-object / json-array /
json-number / json-quoted-string
false = %x66.61.6c.73.65 ; false
null = %x6e.75.6c.6c ; null
true = %x74.72.75.65 ; true
json-quoted-string = %x22 1*(unescaped-literal / escaped-literal) %x22
begin-array = ws %x5B ws ; [ left square bracket
begin-object = ws %x7B ws ; { left curly bracket
end-array = ws %x5D ws ; ] right square bracket
end-object = ws %x7D ws ; } right curly bracket
name-separator = ws %x3A ws ; : colon
value-separator = ws %x2C ws ; , comma
ws = *(%x20 / ; Space
%x09 / ; Horizontal tab
%x0A / ; Line feed or New line
%x0D ; Carriage return
)
json-object = begin-object [ member *( value-separator member ) ] end-object
member = quoted-string name-separator json-value
json-array = begin-array [ json-value *( value-separator json-value ) ] end-array
json-number = [ minus ] int [ frac ] [ exp ]
decimal-point = %x2E ; .
digit1-9 = %x31-39 ; 1-9
e = %x65 / %x45 ; e E
exp = e [ minus / plus ] 1*digit
frac = decimal-point 1*digit
int = zero / ( digit1-9 *digit )
minus = %x2D ; -
plus = %x2B ; +
zero = %x30 ; 0