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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_local.yml +52 -0
- data/AGENTS.md +171 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +189 -0
- data/Rakefile +41 -0
- data/lib/jsonata/datetime.rb +1237 -0
- data/lib/jsonata/error.rb +113 -0
- data/lib/jsonata/evaluator.rb +1669 -0
- data/lib/jsonata/functions.rb +1505 -0
- data/lib/jsonata/parser.rb +1206 -0
- data/lib/jsonata/signature.rb +235 -0
- data/lib/jsonata/utils.rb +600 -0
- data/lib/jsonata/version.rb +5 -0
- data/lib/jsonata.rb +47 -0
- metadata +108 -0
|
@@ -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
|