jsonata 2.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1206 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright IBM Corp. 2016, 2018 All Rights Reserved
4
+ # Project name: JSONata
5
+ # This project is licensed under the MIT License, see LICENSE
6
+
7
+ # Port of jsonata-js src/parser.js
8
+ #
9
+ # AST nodes are represented as Hashes with Symbol keys. In jsonata-js, the nodes created by the
10
+ # Pratt parser inherit (via the prototype chain) from their entry in the symbol table; here that
11
+ # relationship is maintained in a separate identity map (see #symbol_of).
12
+
13
+ module Jsonata
14
+ class Parser
15
+ OPERATORS = {
16
+ '.' => 75,
17
+ '[' => 80,
18
+ ']' => 0,
19
+ '{' => 70,
20
+ '}' => 0,
21
+ '(' => 80,
22
+ ')' => 0,
23
+ ',' => 0,
24
+ '@' => 80,
25
+ '#' => 80,
26
+ ';' => 80,
27
+ ':' => 80,
28
+ '?' => 20,
29
+ '+' => 50,
30
+ '-' => 50,
31
+ '*' => 60,
32
+ '/' => 60,
33
+ '%' => 60,
34
+ '|' => 20,
35
+ '=' => 40,
36
+ '<' => 40,
37
+ '>' => 40,
38
+ '^' => 40,
39
+ '**' => 60,
40
+ '..' => 20,
41
+ ':=' => 10,
42
+ '!=' => 40,
43
+ '<=' => 40,
44
+ '>=' => 40,
45
+ '~>' => 40,
46
+ '?:' => 40,
47
+ '??' => 40,
48
+ 'and' => 30,
49
+ 'or' => 25,
50
+ 'in' => 40,
51
+ '&' => 50,
52
+ '!' => 0, # not an operator, but needed as a stop character for name tokens
53
+ '~' => 0 # not an operator, but needed as a stop character for name tokens
54
+ }.freeze
55
+
56
+ ESCAPES = { # JSON string escape sequences - see json.org
57
+ '"' => '"',
58
+ '\\' => '\\',
59
+ '/' => '/',
60
+ 'b' => "\b",
61
+ 'f' => "\f",
62
+ 'n' => "\n",
63
+ 'r' => "\r",
64
+ 't' => "\t"
65
+ }.freeze
66
+
67
+ WHITESPACE = " \t\n\r\v"
68
+
69
+ # The value of a regex literal token
70
+ RegexLiteral = Struct.new(:source, :flags) do
71
+ def regexp
72
+ @regexp ||= Utils.js_regexp(source, flags)
73
+ end
74
+ end
75
+
76
+ # Tokenizer (lexer) - invoked by the parser to return one token at a time
77
+ class Tokenizer
78
+ def initialize(path)
79
+ @path = path
80
+ @position = 0
81
+ @length = path.length
82
+ end
83
+
84
+ def call(prefix = false)
85
+ self.next(prefix)
86
+ end
87
+
88
+ def next(prefix = false)
89
+ return nil if @position >= @length
90
+
91
+ current_char = char_at(@position)
92
+ # skip whitespace
93
+ while @position < @length && WHITESPACE.include?(current_char)
94
+ @position += 1
95
+ current_char = char_at(@position)
96
+ end
97
+ # skip comments
98
+ if current_char == '/' && char_at(@position + 1) == '*'
99
+ comment_start = @position
100
+ @position += 2
101
+ current_char = char_at(@position)
102
+ until current_char == '*' && char_at(@position + 1) == '/'
103
+ @position += 1
104
+ current_char = char_at(@position)
105
+ if @position >= @length
106
+ # no closing tag
107
+ raise Jsonata::Error.new('S0106', :position => comment_start)
108
+ end
109
+ end
110
+ @position += 2
111
+ return self.next(prefix) # need this to swallow any following whitespace
112
+ end
113
+ # test for regex
114
+ if prefix != true && current_char == '/'
115
+ @position += 1
116
+ return create('regex', scan_regex)
117
+ end
118
+ # handle double-char operators
119
+ two_chars = @path[@position, 2]
120
+ if %w[.. := != >= <= ** ~> ?: ??].include?(two_chars)
121
+ @position += 2
122
+ return create('operator', two_chars)
123
+ end
124
+ # test for single char operators
125
+ if OPERATORS.key?(current_char)
126
+ @position += 1
127
+ return create('operator', current_char)
128
+ end
129
+ # test for string literals
130
+ if current_char == '"' || current_char == "'"
131
+ quote_type = current_char
132
+ # double quoted string literal - find end of string
133
+ @position += 1
134
+ qstr = +''
135
+ while @position < @length
136
+ current_char = char_at(@position)
137
+ if current_char == '\\' # escape sequence
138
+ @position += 1
139
+ current_char = char_at(@position)
140
+ if ESCAPES.key?(current_char)
141
+ qstr << ESCAPES[current_char]
142
+ elsif current_char == 'u'
143
+ # \u should be followed by 4 hex digits
144
+ octets = @path[@position + 1, 4] || ''
145
+ if /\A[0-9a-fA-F]+\z/.match?(octets)
146
+ codepoint = octets.to_i(16)
147
+ @position += 4
148
+ # Ruby strings cannot hold UTF-16 surrogate pairs, so combine them here
149
+ low = @path[@position + 1, 6]
150
+ if (0xD800..0xDBFF).cover?(codepoint) && low&.match?(/\A\\u[dD][c-fC-F][0-9a-fA-F]{2}\z/)
151
+ codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low[2..].to_i(16) - 0xDC00)
152
+ @position += 6
153
+ end
154
+ qstr << Utils.from_char_code(codepoint)
155
+ else
156
+ raise Jsonata::Error.new('S0104', :position => @position)
157
+ end
158
+ else
159
+ # illegal escape sequence
160
+ raise Jsonata::Error.new('S0103', :position => @position, :token => current_char)
161
+ end
162
+ elsif current_char == quote_type
163
+ @position += 1
164
+ return create('string', qstr)
165
+ else
166
+ qstr << current_char
167
+ end
168
+ @position += 1
169
+ end
170
+ raise Jsonata::Error.new('S0101', :position => @position)
171
+ end
172
+ # test for numbers
173
+ match = /\A-?(0|([1-9][0-9]*))(\.[0-9]+)?([Ee][-+]?[0-9]+)?/.match(@path[@position..])
174
+ unless match.nil?
175
+ num = Float(match[0])
176
+ if !num.nan? && num.finite?
177
+ @position += match[0].length
178
+ return create('number', Utils.normalize_number(num))
179
+ else
180
+ raise Jsonata::Error.new('S0102', :position => @position, :token => match[0])
181
+ end
182
+ end
183
+ # test for quoted names (backticks)
184
+ if current_char == '`'
185
+ # scan for closing quote
186
+ @position += 1
187
+ end_pos = @path.index('`', @position)
188
+ unless end_pos.nil?
189
+ name = @path[@position...end_pos]
190
+ @position = end_pos + 1
191
+ return create('name', name)
192
+ end
193
+ @position = @length
194
+ raise Jsonata::Error.new('S0105', :position => @position)
195
+ end
196
+ # test for names
197
+ i = @position
198
+ loop do
199
+ ch = char_at(i)
200
+ if i == @length || WHITESPACE.include?(ch) || OPERATORS.key?(ch)
201
+ if char_at(@position) == '$'
202
+ # variable reference
203
+ name = @path[(@position + 1)...i]
204
+ @position = i
205
+ return create('variable', name)
206
+ else
207
+ name = @path[@position...i]
208
+ @position = i
209
+ case name
210
+ when 'or', 'in', 'and'
211
+ return create('operator', name)
212
+ when 'true'
213
+ return create('value', true)
214
+ when 'false'
215
+ return create('value', false)
216
+ when 'null'
217
+ return create('value', NULL)
218
+ else
219
+ if @position == @length && name == ''
220
+ # whitespace at end of input
221
+ return nil
222
+ end
223
+
224
+ return create('name', name)
225
+ end
226
+ end
227
+ else
228
+ i += 1
229
+ end
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ # equivalent of String.prototype.charAt
236
+ def char_at(index)
237
+ (index >= 0 && @path[index]) || ''
238
+ end
239
+
240
+ def create(type, value)
241
+ {:type => type, :value => value, :position => @position}
242
+ end
243
+
244
+ def scan_regex
245
+ # the prefix '/' will have been previously scanned. Find the end of the regex.
246
+ # search for closing '/' ignoring any that are escaped, or within brackets
247
+ start = @position
248
+ depth = 0
249
+
250
+ closing_slash = lambda do |position|
251
+ if char_at(position) == '/' && depth.zero?
252
+ backslash_count = 0
253
+ backslash_count += 1 while char_at(position - (backslash_count + 1)) == '\\'
254
+ return true if backslash_count.even?
255
+ end
256
+ false
257
+ end
258
+
259
+ while @position < @length
260
+ current_char = char_at(@position)
261
+ if closing_slash.call(@position)
262
+ # end of regex found
263
+ pattern = @path[start...@position]
264
+ raise Jsonata::Error.new('S0301', :position => @position) if pattern == ''
265
+
266
+ @position += 1
267
+ current_char = char_at(@position)
268
+ # flags
269
+ start = @position
270
+ while current_char == 'i' || current_char == 'm'
271
+ @position += 1
272
+ current_char = char_at(@position)
273
+ end
274
+ flags = "#{@path[start...@position]}g"
275
+ return RegexLiteral.new(pattern, flags)
276
+ end
277
+ if (current_char == '(' || current_char == '[' || current_char == '{') && char_at(@position - 1) != '\\'
278
+ depth += 1
279
+ end
280
+ if (current_char == ')' || current_char == ']' || current_char == '}') && char_at(@position - 1) != '\\'
281
+ depth -= 1
282
+ end
283
+
284
+ @position += 1
285
+ end
286
+ raise Jsonata::Error.new('S0302', :position => @position)
287
+ end
288
+ end
289
+
290
+ # An entry in the symbol table
291
+ Sym = Struct.new(:id, :value, :lbp, :nud, :led)
292
+
293
+ # This parser implements the 'Top down operator precedence' algorithm developed by Vaughan R Pratt; http://dl.acm.org/citation.cfm?id=512931.
294
+ # and builds on the Javascript framework described by Douglas Crockford at http://javascript.crockford.com/tdop/tdop.html
295
+ # and in 'Beautiful Code', edited by Andy Oram and Greg Wilson, Copyright 2007 O'Reilly Media, Inc. 798-0-596-51004-6
296
+ def self.parse(source, recover = false)
297
+ new(source, recover).parse
298
+ end
299
+
300
+ def initialize(source, recover = false)
301
+ @source = source
302
+ @recover = recover
303
+ @node = nil
304
+ @lexer = nil
305
+ @symbol_table = {}
306
+ @node_symbols = {}.compare_by_identity
307
+ @errors = []
308
+ @ancestor_label = 0
309
+ @ancestor_index = 0
310
+ @ancestry = []
311
+ define_symbols
312
+ end
313
+
314
+ def parse
315
+ # now invoke the tokenizer and the parser and return the syntax tree
316
+ @lexer = Tokenizer.new(@source)
317
+ advance
318
+ # parse the tokens
319
+ expr = expression(0)
320
+ if node_id(@node) != '(end)'
321
+ err = Jsonata::Error.new('S0201', :position => @node[:position], :token => @node[:value])
322
+ handle_error(err)
323
+ end
324
+ expr = process_ast(expr)
325
+
326
+ if expr[:type] == 'parent' || expr.key?(:seeking_parent)
327
+ # error - trying to derive ancestor at top level
328
+ raise Jsonata::Error.new('S0217', :token => expr[:type], :position => expr[:position])
329
+ end
330
+
331
+ expr[:errors] = @errors unless @errors.empty?
332
+
333
+ expr
334
+ end
335
+
336
+ private
337
+
338
+ def symbol_of(node)
339
+ @node_symbols[node]
340
+ end
341
+
342
+ def node_id(node)
343
+ symbol_of(node)&.id
344
+ end
345
+
346
+ def node_lbp(node)
347
+ symbol_of(node)&.lbp || 0
348
+ end
349
+
350
+ def remaining_tokens
351
+ remaining = []
352
+ if node_id(@node) != '(end)'
353
+ remaining.push({:type => @node[:type], :value => @node[:value], :position => @node[:position]})
354
+ end
355
+ nxt = @lexer.call
356
+ until nxt.nil?
357
+ remaining.push(nxt)
358
+ nxt = @lexer.call
359
+ end
360
+ remaining
361
+ end
362
+
363
+ def base_nud
364
+ lambda do |this|
365
+ # error - symbol has been invoked as a unary operator
366
+ err = Jsonata::Error.new('S0211', :token => this[:value], :position => this[:position])
367
+
368
+ raise err unless @recover
369
+
370
+ err.remaining = remaining_tokens
371
+ @errors.push(err)
372
+ {:type => 'error', :error => err, :code => err.code, :token => err.token, :position => err.position, :remaining => err.remaining}
373
+ end
374
+ end
375
+
376
+ def symbol(id, bp = 0)
377
+ s = @symbol_table[id]
378
+ bp ||= 0
379
+ if s
380
+ s.lbp = bp if bp >= s.lbp
381
+ else
382
+ s = Sym.new(id, id, bp, base_nud, nil)
383
+ @symbol_table[id] = s
384
+ end
385
+ s
386
+ end
387
+
388
+ def handle_error(err)
389
+ raise err unless @recover
390
+
391
+ # tokenize the rest of the buffer and add it to an error token
392
+ err.remaining = remaining_tokens
393
+ @errors.push(err)
394
+ sym = @symbol_table['(error)']
395
+ @node = {:value => sym.value, :error => err, :type => '(error)'}
396
+ @node_symbols[@node] = sym
397
+ @node
398
+ end
399
+
400
+ def advance(id = nil, infix = false)
401
+ if id && node_id(@node) != id
402
+ code = if node_id(@node) == '(end)'
403
+ # unexpected end of buffer
404
+ 'S0203'
405
+ else
406
+ 'S0202'
407
+ end
408
+ err = Jsonata::Error.new(code, :position => @node[:position], :token => @node[:value], :value => id)
409
+ return handle_error(err)
410
+ end
411
+ next_token = @lexer.call(infix)
412
+ if next_token.nil?
413
+ @node = @end_node
414
+ @node[:position] = @source.length
415
+ return @node
416
+ end
417
+ value = next_token[:value]
418
+ type = next_token[:type]
419
+ case type
420
+ when 'name', 'variable'
421
+ sym = @symbol_table['(name)']
422
+ when 'operator'
423
+ sym = @symbol_table[value]
424
+ unless sym
425
+ return handle_error(Jsonata::Error.new('S0204', :position => next_token[:position], :token => value))
426
+ end
427
+ when 'string', 'number', 'value'
428
+ sym = @symbol_table['(literal)']
429
+ when 'regex'
430
+ type = 'regex'
431
+ sym = @symbol_table['(regex)']
432
+ else
433
+ return handle_error(Jsonata::Error.new('S0205', :position => next_token[:position], :token => value))
434
+ end
435
+
436
+ @node = {:value => value, :type => type, :position => next_token[:position]}
437
+ @node_symbols[@node] = sym
438
+ @node
439
+ end
440
+
441
+ # Pratt's algorithm
442
+ def expression(rbp)
443
+ t = @node
444
+ advance(nil, true)
445
+ left = symbol_of(t).nud.call(t)
446
+ while rbp < node_lbp(@node)
447
+ t = @node
448
+ advance
449
+ left = symbol_of(t).led.call(t, left)
450
+ end
451
+ left
452
+ end
453
+
454
+ def terminal(id)
455
+ s = symbol(id, 0)
456
+ s.nud = ->(this) { this }
457
+ end
458
+
459
+ # match infix operators
460
+ # <expression> <operator> <expression>
461
+ # left associative
462
+ def infix(id, bp = nil, &led)
463
+ binding_power = bp || OPERATORS[id]
464
+ s = symbol(id, binding_power)
465
+ s.led = led || lambda do |this, left|
466
+ this[:lhs] = left
467
+ this[:rhs] = expression(binding_power)
468
+ this[:type] = 'binary'
469
+ this
470
+ end
471
+ s
472
+ end
473
+
474
+ # match infix operators
475
+ # <expression> <operator> <expression>
476
+ # right associative
477
+ def infixr(id, bp, &led)
478
+ s = symbol(id, bp)
479
+ s.led = led
480
+ s
481
+ end
482
+
483
+ # match prefix operators
484
+ # <operator> <expression>
485
+ def prefix(id, &nud)
486
+ s = symbol(id)
487
+ s.nud = nud || lambda do |this|
488
+ this[:expression] = expression(70)
489
+ this[:type] = 'unary'
490
+ this
491
+ end
492
+ s
493
+ end
494
+
495
+ def deep_clone(obj)
496
+ case obj
497
+ when Hash then obj.to_h { |k, v| [k, deep_clone(v)] }
498
+ when Array then obj.map { |v| deep_clone(v) }
499
+ else obj
500
+ end
501
+ end
502
+
503
+ def define_symbols
504
+ terminal('(end)')
505
+ @end_node = {:value => '(end)'}
506
+ @node_symbols[@end_node] = @symbol_table['(end)']
507
+ terminal('(name)')
508
+ terminal('(literal)')
509
+ terminal('(regex)')
510
+ symbol(':')
511
+ symbol(';')
512
+ symbol(',')
513
+ symbol(')')
514
+ symbol(']')
515
+ symbol('}')
516
+ symbol('..') # range operator
517
+ infix('.') # map operator
518
+ infix('+') # numeric addition
519
+ infix('-') # numeric subtraction
520
+ infix('*') # numeric multiplication
521
+ infix('/') # numeric division
522
+ infix('%') # numeric modulus
523
+ infix('=') # equality
524
+ infix('<') # less than
525
+ infix('>') # greater than
526
+ infix('!=') # not equal to
527
+ infix('<=') # less than or equal
528
+ infix('>=') # greater than or equal
529
+ infix('&') # string concatenation
530
+ infix('and') # Boolean AND
531
+ infix('or') # Boolean OR
532
+ infix('in') # is member of array
533
+ terminal('and') # the 'keywords' can also be used as terminals (field names)
534
+ terminal('or')
535
+ terminal('in')
536
+ prefix('-') # unary numeric negation
537
+ infix('~>') # function application
538
+
539
+ # coalescing operator
540
+ infix('??', OPERATORS['??']) do |this, left|
541
+ this[:type] = 'condition'
542
+ this[:condition] = {
543
+ :type => 'function',
544
+ :value => '(',
545
+ :procedure => {:type => 'variable', :value => 'exists'},
546
+ :arguments => [deep_clone(left)]
547
+ }
548
+ this[:then] = left
549
+ this[:else] = expression(0)
550
+ this
551
+ end
552
+
553
+ infixr('(error)', 10) do |this, left|
554
+ this[:lhs] = left
555
+
556
+ this[:error] = @node[:error]
557
+ this[:remaining] = remaining_tokens
558
+ this[:type] = 'error'
559
+ this
560
+ end
561
+
562
+ # field wildcard (single level)
563
+ prefix('*') do |this|
564
+ this[:type] = 'wildcard'
565
+ this
566
+ end
567
+
568
+ # descendant wildcard (multi-level)
569
+ prefix('**') do |this|
570
+ this[:type] = 'descendant'
571
+ this
572
+ end
573
+
574
+ # parent operator
575
+ prefix('%') do |this|
576
+ this[:type] = 'parent'
577
+ this
578
+ end
579
+
580
+ # function invocation
581
+ infix('(', OPERATORS['(']) do |this, left|
582
+ # left is is what we are trying to invoke
583
+ this[:procedure] = left
584
+ this[:type] = 'function'
585
+ this[:arguments] = []
586
+ if node_id(@node) != ')'
587
+ loop do
588
+ if @node[:type] == 'operator' && node_id(@node) == '?'
589
+ # partial function application
590
+ this[:type] = 'partial'
591
+ this[:arguments].push(@node)
592
+ advance('?')
593
+ else
594
+ this[:arguments].push(expression(0))
595
+ end
596
+ break if node_id(@node) != ','
597
+
598
+ advance(',')
599
+ end
600
+ end
601
+ advance(')', true)
602
+ # if the name of the function is 'function' or lambda (U+03BB), then this is function definition (lambda function)
603
+ if left[:type] == 'name' && (left[:value] == 'function' || left[:value] == "\u03BB")
604
+ # all of the args must be VARIABLE tokens
605
+ this[:arguments].each_with_index do |arg, index|
606
+ next if arg[:type] == 'variable'
607
+
608
+ handle_error(Jsonata::Error.new('S0208', :position => arg[:position], :token => arg[:value], :value => index + 1))
609
+ end
610
+ this[:type] = 'lambda'
611
+ # is the next token a '<' - if so, parse the function signature
612
+ if node_id(@node) == '<'
613
+ sig_pos = @node[:position]
614
+ depth = 1
615
+ sig = +'<'
616
+ while depth.positive? && node_id(@node) != '{' && node_id(@node) != '(end)'
617
+ tok = advance
618
+ if node_id(tok) == '>'
619
+ depth -= 1
620
+ elsif node_id(tok) == '<'
621
+ depth += 1
622
+ end
623
+ sig << tok[:value].to_s
624
+ end
625
+ advance('>')
626
+ begin
627
+ this[:signature] = Signature.new(sig)
628
+ rescue Jsonata::Error => e
629
+ # insert the position into this error
630
+ e.position = sig_pos + e.offset
631
+ next handle_error(e)
632
+ end
633
+ end
634
+ # parse the function body
635
+ advance('{')
636
+ this[:body] = expression(0)
637
+ advance('}')
638
+ end
639
+ this
640
+ end
641
+
642
+ # parenthesis - block expression
643
+ prefix('(') do |this|
644
+ expressions = []
645
+ while node_id(@node) != ')'
646
+ expressions.push(expression(0))
647
+ break if node_id(@node) != ';'
648
+
649
+ advance(';')
650
+ end
651
+ advance(')', true)
652
+ this[:type] = 'block'
653
+ this[:expressions] = expressions
654
+ this
655
+ end
656
+
657
+ # array constructor
658
+ prefix('[') do |this|
659
+ a = []
660
+ if node_id(@node) != ']'
661
+ loop do
662
+ item = expression(0)
663
+ if node_id(@node) == '..'
664
+ # range operator
665
+ range = {:type => 'binary', :value => '..', :position => @node[:position], :lhs => item}
666
+ advance('..')
667
+ range[:rhs] = expression(0)
668
+ item = range
669
+ end
670
+ a.push(item)
671
+ break if node_id(@node) != ','
672
+
673
+ advance(',')
674
+ end
675
+ end
676
+ advance(']', true)
677
+ this[:expressions] = a
678
+ this[:type] = 'unary'
679
+ this
680
+ end
681
+
682
+ # filter - predicate or array index
683
+ infix('[', OPERATORS['[']) do |this, left|
684
+ if node_id(@node) == ']'
685
+ # empty predicate means maintain singleton arrays in the output
686
+ step = left
687
+ step = step[:lhs] while step && step[:type] == 'binary' && step[:value] == '['
688
+ step[:keep_array] = true
689
+ advance(']')
690
+ left
691
+ else
692
+ this[:lhs] = left
693
+ this[:rhs] = expression(OPERATORS[']'])
694
+ this[:type] = 'binary'
695
+ advance(']', true)
696
+ this
697
+ end
698
+ end
699
+
700
+ # order-by
701
+ infix('^', OPERATORS['^']) do |this, left|
702
+ advance('(')
703
+ terms = []
704
+ loop do
705
+ term = {
706
+ :descending => false
707
+ }
708
+ if node_id(@node) == '<'
709
+ # ascending sort
710
+ advance('<')
711
+ elsif node_id(@node) == '>'
712
+ # descending sort
713
+ term[:descending] = true
714
+ advance('>')
715
+ else
716
+ # unspecified - default to ascending
717
+ end
718
+ term[:expression] = expression(0)
719
+ terms.push(term)
720
+ break if node_id(@node) != ','
721
+
722
+ advance(',')
723
+ end
724
+ advance(')')
725
+ this[:lhs] = left
726
+ this[:rhs] = terms
727
+ this[:type] = 'binary'
728
+ this
729
+ end
730
+
731
+ object_parser = lambda do |this, left = nil|
732
+ a = []
733
+ if node_id(@node) != '}'
734
+ loop do
735
+ n = expression(0)
736
+ advance(':')
737
+ v = expression(0)
738
+ a.push([n, v]) # holds an array of name/value expression pairs
739
+ break if node_id(@node) != ','
740
+
741
+ advance(',')
742
+ end
743
+ end
744
+ advance('}', true)
745
+ if left.nil?
746
+ # NUD - unary prefix form
747
+ this[:lhs] = a
748
+ this[:type] = 'unary'
749
+ else
750
+ # LED - binary infix form
751
+ this[:lhs] = left
752
+ this[:rhs] = a
753
+ this[:type] = 'binary'
754
+ end
755
+ this
756
+ end
757
+
758
+ # object constructor
759
+ prefix('{', &object_parser)
760
+
761
+ # object grouping
762
+ infix('{', OPERATORS['{'], &object_parser)
763
+
764
+ # bind variable
765
+ infixr(':=', OPERATORS[':=']) do |this, left|
766
+ if left[:type] != 'variable'
767
+ next handle_error(Jsonata::Error.new('S0212', :position => left[:position], :token => left[:value]))
768
+ end
769
+
770
+ this[:lhs] = left
771
+ this[:rhs] = expression(OPERATORS[':='] - 1) # subtract 1 from bindingPower for right associative operators
772
+ this[:type] = 'binary'
773
+ this
774
+ end
775
+
776
+ # focus variable bind
777
+ infix('@', OPERATORS['@']) do |this, left|
778
+ this[:lhs] = left
779
+ this[:rhs] = expression(OPERATORS['@'])
780
+ if this[:rhs][:type] != 'variable'
781
+ next handle_error(Jsonata::Error.new('S0214', :position => this[:rhs][:position], :token => '@'))
782
+ end
783
+
784
+ this[:type] = 'binary'
785
+ this
786
+ end
787
+
788
+ # index (position) variable bind
789
+ infix('#', OPERATORS['#']) do |this, left|
790
+ this[:lhs] = left
791
+ this[:rhs] = expression(OPERATORS['#'])
792
+ if this[:rhs][:type] != 'variable'
793
+ next handle_error(Jsonata::Error.new('S0214', :position => this[:rhs][:position], :token => '#'))
794
+ end
795
+
796
+ this[:type] = 'binary'
797
+ this
798
+ end
799
+
800
+ # if/then/else ternary operator ?:
801
+ infix('?', OPERATORS['?']) do |this, left|
802
+ this[:type] = 'condition'
803
+ this[:condition] = left
804
+ this[:then] = expression(0)
805
+ if node_id(@node) == ':'
806
+ # else condition
807
+ advance(':')
808
+ this[:else] = expression(0)
809
+ end
810
+ this
811
+ end
812
+
813
+ # elvis/default operator
814
+ infix('?:', OPERATORS['?:']) do |this, left|
815
+ this[:type] = 'condition'
816
+ # Deep-clone `left` so the condition and then branches have
817
+ # independent AST nodes. Sharing the same reference causes
818
+ # post-parse processing (e.g. predicate stages, unary minus
819
+ # folding on number literals) to mutate the same node twice,
820
+ # producing wrong results (see #773 for the equivalent ?? fix).
821
+ this[:condition] = deep_clone(left)
822
+ this[:then] = left
823
+ this[:else] = expression(0)
824
+ this
825
+ end
826
+
827
+ # object transformer
828
+ prefix('|') do |this|
829
+ this[:type] = 'transform'
830
+ this[:pattern] = expression(0)
831
+ advance('|')
832
+ this[:update] = expression(0)
833
+ if node_id(@node) == ','
834
+ advance(',')
835
+ this[:delete] = expression(0)
836
+ end
837
+ advance('|')
838
+ this
839
+ end
840
+ end
841
+
842
+ # tail call optimization
843
+ # this is invoked by the post parser to analyse lambda functions to see
844
+ # if they make a tail call. If so, it is replaced by a thunk which will
845
+ # be invoked by the trampoline loop during function application.
846
+ # This enables tail-recursive functions to be written without growing the stack
847
+ def tail_call_optimize(expr)
848
+ if expr[:type] == 'function' && !expr[:predicate]
849
+ thunk = {:type => 'lambda', :thunk => true, :arguments => [], :position => expr[:position]}
850
+ thunk[:body] = expr
851
+ thunk
852
+ elsif expr[:type] == 'condition'
853
+ # analyse both branches
854
+ expr[:then] = tail_call_optimize(expr[:then])
855
+ expr[:else] = tail_call_optimize(expr[:else]) if expr.key?(:else)
856
+ expr
857
+ elsif expr[:type] == 'block'
858
+ # only the last expression in the block
859
+ length = expr[:expressions].length
860
+ expr[:expressions][length - 1] = tail_call_optimize(expr[:expressions][length - 1]) if length.positive?
861
+ expr
862
+ else
863
+ expr
864
+ end
865
+ end
866
+
867
+ def seek_parent(node, slot)
868
+ case node[:type]
869
+ when 'name', 'wildcard'
870
+ slot[:level] -= 1
871
+ if slot[:level].zero?
872
+ if node[:ancestor].nil?
873
+ node[:ancestor] = slot
874
+ else
875
+ # reuse the existing label
876
+ @ancestry[slot[:index]][:slot][:label] = node[:ancestor][:label]
877
+ node[:ancestor] = slot
878
+ end
879
+ node[:tuple] = true
880
+ end
881
+ when 'parent'
882
+ slot[:level] += 1
883
+ when 'block'
884
+ # look in last expression in the block
885
+ unless node[:expressions].empty?
886
+ node[:tuple] = true
887
+ slot = seek_parent(node[:expressions][-1], slot)
888
+ end
889
+ when 'path'
890
+ # last step in path
891
+ node[:tuple] = true
892
+ index = node[:steps].length - 1
893
+ slot = seek_parent(node[:steps][index], slot)
894
+ index -= 1
895
+ while slot[:level].positive? && index >= 0
896
+ # check previous steps
897
+ slot = seek_parent(node[:steps][index], slot)
898
+ index -= 1
899
+ end
900
+ else
901
+ # error - can't derive ancestor
902
+ raise Jsonata::Error.new('S0217', :token => node[:type], :position => node[:position])
903
+ end
904
+ slot
905
+ end
906
+
907
+ def push_ancestry(result, value)
908
+ return unless value.key?(:seeking_parent) || value[:type] == 'parent'
909
+
910
+ slots = value.key?(:seeking_parent) ? value[:seeking_parent] : []
911
+ slots.push(value[:slot]) if value[:type] == 'parent'
912
+ if result.key?(:seeking_parent)
913
+ result[:seeking_parent].concat(slots)
914
+ else
915
+ result[:seeking_parent] = slots
916
+ end
917
+ end
918
+
919
+ def resolve_ancestry(path)
920
+ index = path[:steps].length - 1
921
+ laststep = path[:steps][index]
922
+ slots = laststep.key?(:seeking_parent) ? laststep[:seeking_parent] : []
923
+ slots.push(laststep[:slot]) if laststep[:type] == 'parent'
924
+ slots.each do |slot|
925
+ index = path[:steps].length - 2
926
+ while slot[:level].positive?
927
+ if index.negative?
928
+ if path.key?(:seeking_parent)
929
+ path[:seeking_parent].push(slot)
930
+ else
931
+ path[:seeking_parent] = [slot]
932
+ end
933
+ break
934
+ end
935
+ # try previous step
936
+ step = path[:steps][index]
937
+ index -= 1
938
+ # multiple contiguous steps that bind the focus should be skipped
939
+ while index >= 0 && step[:focus] && path[:steps][index][:focus]
940
+ step = path[:steps][index]
941
+ index -= 1
942
+ end
943
+ slot = seek_parent(step, slot)
944
+ end
945
+ end
946
+ end
947
+
948
+ # post-parse stage
949
+ # the purpose of this is to add as much semantic value to the parse tree as possible
950
+ # in order to simplify the work of the evaluator.
951
+ # This includes flattening the parts of the AST representing location paths,
952
+ # converting them to arrays of steps which in turn may contain arrays of predicates.
953
+ # following this, nodes containing '.' and '[' should be eliminated from the AST.
954
+ def process_ast(expr)
955
+ result = nil
956
+ case expr[:type]
957
+ when 'binary'
958
+ case expr[:value]
959
+ when '.'
960
+ lstep = process_ast(expr[:lhs])
961
+
962
+ result = if lstep[:type] == 'path'
963
+ lstep
964
+ else
965
+ {:type => 'path', :steps => [lstep]}
966
+ end
967
+ result[:seeking_parent] = [lstep[:slot]] if lstep[:type] == 'parent'
968
+ rest = process_ast(expr[:rhs])
969
+ if rest[:type] == 'path'
970
+ result[:steps].concat(rest[:steps])
971
+ else
972
+ rest[:stages] = rest.delete(:predicate) if rest.key?(:predicate)
973
+ result[:steps].push(rest)
974
+ end
975
+ # any steps within a path that are string literals, should be changed to 'name'
976
+ result[:steps].select do |step|
977
+ if step[:type] == 'number' || step[:type] == 'value'
978
+ # don't allow steps to be numbers or the values true/false/null
979
+ raise Jsonata::Error.new('S0213', :position => step[:position], :value => step[:value])
980
+ end
981
+
982
+ step[:type] == 'string'
983
+ end.each do |lit|
984
+ lit[:type] = 'name'
985
+ end
986
+ # any step that signals keeping a singleton array, should be flagged on the path
987
+ result[:keep_singleton_array] = true if result[:steps].any? { |step| step[:keep_array] == true }
988
+ # if first step is a path constructor, flag it for special handling
989
+ firststep = result[:steps][0]
990
+ firststep[:consarray] = true if firststep[:type] == 'unary' && firststep[:value] == '['
991
+ # if the last step is an array constructor, flag it so it doesn't flatten
992
+ laststep = result[:steps][-1]
993
+ laststep[:consarray] = true if laststep[:type] == 'unary' && laststep[:value] == '['
994
+ resolve_ancestry(result)
995
+ when '['
996
+ # predicated step
997
+ # LHS is a step or a predicated step
998
+ # RHS is the predicate expr
999
+ result = process_ast(expr[:lhs])
1000
+ step = result
1001
+ type = :predicate
1002
+ if result[:type] == 'path'
1003
+ step = result[:steps][-1]
1004
+ type = :stages
1005
+ end
1006
+ raise Jsonata::Error.new('S0209', :position => expr[:position]) if step.key?(:group)
1007
+
1008
+ step[type] = [] unless step.key?(type)
1009
+ predicate = process_ast(expr[:rhs])
1010
+ if predicate.key?(:seeking_parent)
1011
+ predicate[:seeking_parent].each do |slot|
1012
+ if slot[:level] == 1
1013
+ seek_parent(step, slot)
1014
+ else
1015
+ slot[:level] -= 1
1016
+ end
1017
+ end
1018
+ push_ancestry(step, predicate)
1019
+ end
1020
+ step[type].push({:type => 'filter', :expr => predicate, :position => expr[:position]})
1021
+ when '{'
1022
+ # group-by
1023
+ # LHS is a step or a predicated step
1024
+ # RHS is the object constructor expr
1025
+ result = process_ast(expr[:lhs])
1026
+ raise Jsonata::Error.new('S0210', :position => expr[:position]) if result.key?(:group)
1027
+
1028
+ # object constructor - process each pair
1029
+ result[:group] = {
1030
+ :lhs => expr[:rhs].map { |pair| [process_ast(pair[0]), process_ast(pair[1])] },
1031
+ :position => expr[:position]
1032
+ }
1033
+ when '^'
1034
+ # order-by
1035
+ # LHS is the array to be ordered
1036
+ # RHS defines the terms
1037
+ result = process_ast(expr[:lhs])
1038
+ result = {:type => 'path', :steps => [result]} if result[:type] != 'path'
1039
+ sort_step = {:type => 'sort', :position => expr[:position]}
1040
+ sort_step[:terms] = expr[:rhs].map do |terms|
1041
+ expression = process_ast(terms[:expression])
1042
+ push_ancestry(sort_step, expression)
1043
+ {
1044
+ :descending => terms[:descending],
1045
+ :expression => expression
1046
+ }
1047
+ end
1048
+ result[:steps].push(sort_step)
1049
+ resolve_ancestry(result)
1050
+ when ':='
1051
+ result = {:type => 'bind', :value => expr[:value], :position => expr[:position]}
1052
+ result[:lhs] = process_ast(expr[:lhs])
1053
+ result[:rhs] = process_ast(expr[:rhs])
1054
+ push_ancestry(result, result[:rhs])
1055
+ when '@'
1056
+ result = process_ast(expr[:lhs])
1057
+ step = result
1058
+ step = result[:steps][-1] if result[:type] == 'path'
1059
+ # throw error if there are any predicates defined at this point
1060
+ # at this point the only type of stages can be predicates
1061
+ if step.key?(:stages) || step.key?(:predicate)
1062
+ raise Jsonata::Error.new('S0215', :position => expr[:position])
1063
+ end
1064
+ # also throw if this is applied after an 'order-by' clause
1065
+ raise Jsonata::Error.new('S0216', :position => expr[:position]) if step[:type] == 'sort'
1066
+
1067
+ step[:keep_array] = true if expr[:keep_array]
1068
+ step[:focus] = expr[:rhs][:value]
1069
+ step[:tuple] = true
1070
+ when '#'
1071
+ result = process_ast(expr[:lhs])
1072
+ step = result
1073
+ if result[:type] == 'path'
1074
+ step = result[:steps][-1]
1075
+ else
1076
+ result = {:type => 'path', :steps => [result]}
1077
+ step[:stages] = step.delete(:predicate) if step.key?(:predicate)
1078
+ end
1079
+ if step.key?(:stages)
1080
+ step[:stages].push({:type => 'index', :value => expr[:rhs][:value], :position => expr[:position]})
1081
+ else
1082
+ step[:index] = expr[:rhs][:value]
1083
+ end
1084
+ step[:tuple] = true
1085
+ when '~>'
1086
+ result = {:type => 'apply', :value => expr[:value], :position => expr[:position]}
1087
+ result[:lhs] = process_ast(expr[:lhs])
1088
+ result[:rhs] = process_ast(expr[:rhs])
1089
+ result[:keep_array] = result[:lhs][:keep_array] || result[:rhs][:keep_array]
1090
+ else
1091
+ result = {:type => expr[:type], :value => expr[:value], :position => expr[:position]}
1092
+ result[:lhs] = process_ast(expr[:lhs])
1093
+ result[:rhs] = process_ast(expr[:rhs])
1094
+ push_ancestry(result, result[:lhs])
1095
+ push_ancestry(result, result[:rhs])
1096
+ end
1097
+ when 'unary'
1098
+ result = {:type => expr[:type], :value => expr[:value], :position => expr[:position]}
1099
+ if expr[:value] == '['
1100
+ # array constructor - process each item
1101
+ result[:expressions] = expr[:expressions].map do |item|
1102
+ value = process_ast(item)
1103
+ push_ancestry(result, value)
1104
+ value
1105
+ end
1106
+ elsif expr[:value] == '{'
1107
+ # object constructor - process each pair
1108
+ result[:lhs] = expr[:lhs].map do |pair|
1109
+ key = process_ast(pair[0])
1110
+ push_ancestry(result, key)
1111
+ value = process_ast(pair[1])
1112
+ push_ancestry(result, value)
1113
+ [key, value]
1114
+ end
1115
+ else
1116
+ # all other unary expressions - just process the expression
1117
+ result[:expression] = process_ast(expr[:expression])
1118
+ # if unary minus on a number, then pre-process
1119
+ if expr[:value] == '-' && result[:expression][:type] == 'number'
1120
+ result = result[:expression]
1121
+ result[:value] = -result[:value]
1122
+ else
1123
+ push_ancestry(result, result[:expression])
1124
+ end
1125
+ end
1126
+ when 'function', 'partial'
1127
+ result = {:type => expr[:type], :name => expr[:name], :value => expr[:value], :position => expr[:position]}
1128
+ result[:arguments] = expr[:arguments].map do |arg|
1129
+ arg_ast = process_ast(arg)
1130
+ push_ancestry(result, arg_ast)
1131
+ arg_ast
1132
+ end
1133
+ result[:procedure] = process_ast(expr[:procedure])
1134
+ when 'lambda'
1135
+ result = {
1136
+ :type => expr[:type],
1137
+ :arguments => expr[:arguments],
1138
+ :signature => expr[:signature],
1139
+ :position => expr[:position]
1140
+ }
1141
+ body = process_ast(expr[:body])
1142
+ result[:body] = tail_call_optimize(body)
1143
+ when 'condition'
1144
+ result = {:type => expr[:type], :position => expr[:position]}
1145
+ result[:condition] = process_ast(expr[:condition])
1146
+ push_ancestry(result, result[:condition])
1147
+ result[:then] = process_ast(expr[:then])
1148
+ push_ancestry(result, result[:then])
1149
+ if expr.key?(:else)
1150
+ result[:else] = process_ast(expr[:else])
1151
+ push_ancestry(result, result[:else])
1152
+ end
1153
+ when 'transform'
1154
+ result = {:type => expr[:type], :position => expr[:position]}
1155
+ result[:pattern] = process_ast(expr[:pattern])
1156
+ result[:update] = process_ast(expr[:update])
1157
+ result[:delete] = process_ast(expr[:delete]) if expr.key?(:delete)
1158
+ when 'block'
1159
+ result = {:type => expr[:type], :position => expr[:position]}
1160
+ # array of expressions - process each one
1161
+ result[:expressions] = expr[:expressions].map do |item|
1162
+ part = process_ast(item)
1163
+ push_ancestry(result, part)
1164
+ result[:consarray] = true if part[:consarray] || (part[:type] == 'path' && part[:steps][0][:consarray])
1165
+ part
1166
+ end
1167
+ # TODO scan the array of expressions to see if any of them assign variables
1168
+ # if so, need to mark the block as one that needs to create a new frame
1169
+ when 'name'
1170
+ result = {:type => 'path', :steps => [expr]}
1171
+ result[:keep_singleton_array] = true if expr[:keep_array]
1172
+ when 'parent'
1173
+ result = {:type => 'parent', :slot => {:label => "!#{@ancestor_label}", :level => 1, :index => @ancestor_index}}
1174
+ @ancestor_label += 1
1175
+ @ancestor_index += 1
1176
+ @ancestry.push(result)
1177
+ when 'string', 'number', 'value', 'wildcard', 'descendant', 'variable', 'regex'
1178
+ result = expr
1179
+ when 'operator'
1180
+ # the tokens 'and' and 'or' might have been used as a name rather than an operator
1181
+ if %w[and or in].include?(expr[:value])
1182
+ expr[:type] = 'name'
1183
+ result = process_ast(expr)
1184
+ elsif expr[:value] == '?'
1185
+ # partial application
1186
+ result = expr
1187
+ else
1188
+ raise Jsonata::Error.new('S0201', :position => expr[:position], :token => expr[:value])
1189
+ end
1190
+ when 'error'
1191
+ result = expr
1192
+ result = process_ast(expr[:lhs]) if expr[:lhs]
1193
+ else
1194
+ code = 'S0206'
1195
+ code = 'S0207' if node_id(expr) == '(end)'
1196
+ err = Jsonata::Error.new(code, :position => expr[:position], :token => expr[:value])
1197
+ raise err unless @recover
1198
+
1199
+ @errors.push(err)
1200
+ return {:type => 'error', :error => err}
1201
+ end
1202
+ result[:keep_array] = true if expr[:keep_array]
1203
+ result
1204
+ end
1205
+ end
1206
+ end