jsonata-python 0.5.2__py3-none-any.whl
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.
- jsonata/__init__.py +10 -0
- jsonata/cli/__init__.py +0 -0
- jsonata/cli/__main__.py +242 -0
- jsonata/constants.py +68 -0
- jsonata/datetimeutils.py +1133 -0
- jsonata/functions.py +2234 -0
- jsonata/jexception.py +232 -0
- jsonata/jsonata.py +2004 -0
- jsonata/parser.py +1393 -0
- jsonata/signature.py +433 -0
- jsonata/timebox.py +89 -0
- jsonata/tokenizer.py +309 -0
- jsonata/utils.py +178 -0
- jsonata_python-0.5.2.dist-info/METADATA +339 -0
- jsonata_python-0.5.2.dist-info/RECORD +17 -0
- jsonata_python-0.5.2.dist-info/WHEEL +4 -0
- jsonata_python-0.5.2.dist-info/licenses/LICENSE +202 -0
jsonata/jsonata.py
ADDED
|
@@ -0,0 +1,2004 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright Robert Yokota
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Derived from the following code:
|
|
17
|
+
#
|
|
18
|
+
# Project name: jsonata-java
|
|
19
|
+
# Copyright Dashjoin GmbH. https://dashjoin.com
|
|
20
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
21
|
+
#
|
|
22
|
+
# Project name: JSONata
|
|
23
|
+
# © Copyright IBM Corp. 2016, 2017 All Rights Reserved
|
|
24
|
+
# This project is licensed under the MIT License, see LICENSE
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
import copy
|
|
28
|
+
import inspect
|
|
29
|
+
import math
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
import threading
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
from typing import Any, Callable, Mapping, MutableSequence, Optional, Sequence, Type, MutableMapping
|
|
35
|
+
|
|
36
|
+
from jsonata import functions, jexception, parser, signature as sig, timebox, utils
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# @module JSONata
|
|
41
|
+
# @description JSON query and transformation language
|
|
42
|
+
#
|
|
43
|
+
class Jsonata:
|
|
44
|
+
class Frame:
|
|
45
|
+
bindings: MutableMapping[str, Any]
|
|
46
|
+
parent: 'Jsonata.Optional[Frame]'
|
|
47
|
+
is_parallel_call: bool
|
|
48
|
+
|
|
49
|
+
def __init__(self, parent):
|
|
50
|
+
self.bindings = {}
|
|
51
|
+
self.parent = parent
|
|
52
|
+
self.is_parallel_call = False
|
|
53
|
+
|
|
54
|
+
def bind(self, name: str, val: Optional[Any]) -> None:
|
|
55
|
+
self.bindings[name] = val
|
|
56
|
+
if getattr(val, "signature", None) is not None:
|
|
57
|
+
val.signature.set_function_name(name)
|
|
58
|
+
|
|
59
|
+
def lookup(self, name: str) -> Optional[Any]:
|
|
60
|
+
# Important: if we have a null value,
|
|
61
|
+
# return it
|
|
62
|
+
val = self.bindings.get(name, utils.Utils.NONE)
|
|
63
|
+
if val is not utils.Utils.NONE:
|
|
64
|
+
return val
|
|
65
|
+
if self.parent is not None:
|
|
66
|
+
return self.parent.lookup(name)
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Sets the runtime bounds for this environment
|
|
71
|
+
#
|
|
72
|
+
# @param timeout Timeout in millis
|
|
73
|
+
# @param maxRecursionDepth Max recursion depth
|
|
74
|
+
#
|
|
75
|
+
def set_runtime_bounds(self, timeout: int, max_recursion_depth: int) -> None:
|
|
76
|
+
timebox.Timebox(self, timeout, max_recursion_depth)
|
|
77
|
+
|
|
78
|
+
def set_evaluate_entry_callback(self, cb: Callable) -> None:
|
|
79
|
+
self.bind("__evaluate_entry", cb)
|
|
80
|
+
|
|
81
|
+
def set_evaluate_exit_callback(self, cb: Callable) -> None:
|
|
82
|
+
self.bind("__evaluate_exit", cb)
|
|
83
|
+
|
|
84
|
+
static_frame = None # = createFrame(null);
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# JFunction callable Lambda interface
|
|
88
|
+
#
|
|
89
|
+
class JFunctionCallable:
|
|
90
|
+
def call(self, input: Optional[Any], args: Optional[Sequence]) -> Optional[Any]:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
class JFunctionSignatureValidation:
|
|
94
|
+
def validate(self, args: Optional[Any], context: Optional[Any]) -> Optional[Any]:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
class JLambda(JFunctionCallable, JFunctionSignatureValidation):
|
|
98
|
+
function: Callable
|
|
99
|
+
|
|
100
|
+
def __init__(self, function):
|
|
101
|
+
self.function = function
|
|
102
|
+
|
|
103
|
+
def call(self, input: Optional[Any], args: Optional[Sequence]) -> Optional[Any]:
|
|
104
|
+
if isinstance(args, list):
|
|
105
|
+
return self.function(*args)
|
|
106
|
+
else:
|
|
107
|
+
return self.function()
|
|
108
|
+
|
|
109
|
+
def validate(self, args: Optional[Any], context: Optional[Any]) -> Optional[Any]:
|
|
110
|
+
return args
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
# JFunction definition class
|
|
114
|
+
#
|
|
115
|
+
class JFunction(JFunctionCallable, JFunctionSignatureValidation):
|
|
116
|
+
function: 'Jsonata.JFunctionCallable'
|
|
117
|
+
signature: Optional[sig.Signature]
|
|
118
|
+
function_name: Optional[str]
|
|
119
|
+
|
|
120
|
+
def __init__(self, function, signature):
|
|
121
|
+
self.function = function
|
|
122
|
+
if signature is not None:
|
|
123
|
+
# use classname as default, gets overwritten once the function is registered
|
|
124
|
+
self.signature = sig.Signature(signature, str(type(function)))
|
|
125
|
+
else:
|
|
126
|
+
self.signature = None
|
|
127
|
+
|
|
128
|
+
self.function_name = None
|
|
129
|
+
|
|
130
|
+
def call(self, input: Optional[Any], args: Optional[Sequence]) -> Optional[Any]:
|
|
131
|
+
return self.function.call(input, args)
|
|
132
|
+
|
|
133
|
+
def validate(self, args: Optional[Any], context: Optional[Any]) -> Optional[Any]:
|
|
134
|
+
if self.signature is not None:
|
|
135
|
+
return self.signature.validate(args, context)
|
|
136
|
+
else:
|
|
137
|
+
return args
|
|
138
|
+
|
|
139
|
+
def get_number_of_args(self) -> int:
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
class JNativeFunction(JFunction):
|
|
143
|
+
function_name: str
|
|
144
|
+
signature: Optional[sig.Signature]
|
|
145
|
+
clz: Type[functions.Functions]
|
|
146
|
+
method: Optional[Any]
|
|
147
|
+
nargs: int
|
|
148
|
+
|
|
149
|
+
def __init__(self, function_name, signature, clz, impl_method_name):
|
|
150
|
+
super().__init__(None, None)
|
|
151
|
+
self.function_name = function_name
|
|
152
|
+
self.signature = sig.Signature(signature, function_name)
|
|
153
|
+
if impl_method_name is None:
|
|
154
|
+
impl_method_name = self.function_name
|
|
155
|
+
self.method = functions.Functions.get_function(clz, impl_method_name)
|
|
156
|
+
self.nargs = len(inspect.signature(self.method).parameters) if self.method is not None else 0
|
|
157
|
+
if self.method is None:
|
|
158
|
+
print("Function not implemented: " + function_name + " impl=" + impl_method_name)
|
|
159
|
+
|
|
160
|
+
def call(self, input: Optional[Any], args: Optional[Sequence]) -> Optional[Any]:
|
|
161
|
+
return functions.Functions._call(self.method, self.nargs, args)
|
|
162
|
+
|
|
163
|
+
def get_number_of_args(self) -> int:
|
|
164
|
+
return self.nargs
|
|
165
|
+
|
|
166
|
+
class Transformer(JFunctionCallable):
|
|
167
|
+
_jsonata: 'Jsonata'
|
|
168
|
+
_expr: Optional[parser.Parser.Symbol]
|
|
169
|
+
_environment: 'Jsonata.Optional[Frame]'
|
|
170
|
+
|
|
171
|
+
def __init__(self, jsonata, expr, environment):
|
|
172
|
+
self._jsonata = jsonata
|
|
173
|
+
self._expr = expr
|
|
174
|
+
self._environment = environment
|
|
175
|
+
|
|
176
|
+
def call(self, input: Optional[Any], args: Optional[Sequence]) -> Optional[Any]:
|
|
177
|
+
# /* async */ Object (obj) { // signature <(oa):o>
|
|
178
|
+
|
|
179
|
+
obj = args[0]
|
|
180
|
+
|
|
181
|
+
# undefined inputs always return undefined
|
|
182
|
+
if obj is None:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
# this Object returns a copy of obj with changes specified by the pattern/operation
|
|
186
|
+
result = functions.Functions.function_clone(obj)
|
|
187
|
+
|
|
188
|
+
matches = self._jsonata.eval(self._expr.pattern, result, self._environment)
|
|
189
|
+
if matches is not None:
|
|
190
|
+
if not (isinstance(matches, list)):
|
|
191
|
+
matches = [matches]
|
|
192
|
+
for match_ in matches:
|
|
193
|
+
# evaluate the update value for each match
|
|
194
|
+
update = self._jsonata.eval(self._expr.update, match_, self._environment)
|
|
195
|
+
# update must be an object
|
|
196
|
+
# var updateType = typeof update
|
|
197
|
+
# if(updateType != null)
|
|
198
|
+
|
|
199
|
+
if update is not None:
|
|
200
|
+
if not (isinstance(update, dict)):
|
|
201
|
+
# throw type error
|
|
202
|
+
raise jexception.JException("T2011", self._expr.update.position, update)
|
|
203
|
+
# merge the update
|
|
204
|
+
for k in update.keys():
|
|
205
|
+
match_[k] = update[k]
|
|
206
|
+
|
|
207
|
+
# delete, if specified, must be an array of strings (or single string)
|
|
208
|
+
if self._expr.delete is not None:
|
|
209
|
+
deletions = self._jsonata.eval(self._expr.delete, match_, self._environment)
|
|
210
|
+
if deletions is not None:
|
|
211
|
+
val = deletions
|
|
212
|
+
if not (isinstance(deletions, list)):
|
|
213
|
+
deletions = [deletions]
|
|
214
|
+
if not utils.Utils.is_array_of_strings(deletions):
|
|
215
|
+
# throw type error
|
|
216
|
+
raise jexception.JException("T2012", self._expr.delete.position, val)
|
|
217
|
+
for item in deletions:
|
|
218
|
+
if isinstance(match_, dict):
|
|
219
|
+
match_.pop(item, None)
|
|
220
|
+
# delete match[deletions[jj]]
|
|
221
|
+
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
#
|
|
225
|
+
# Evaluate expression against input data
|
|
226
|
+
# @param {Object} expr - JSONata expression
|
|
227
|
+
# @param {Object} input - Input data to evaluate against
|
|
228
|
+
# @param {Object} environment - Environment
|
|
229
|
+
# @returns {*} Evaluated input data
|
|
230
|
+
#
|
|
231
|
+
def eval(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any], environment: Optional[Frame]) -> Optional[Any]:
|
|
232
|
+
# Thread safety:
|
|
233
|
+
# Make sure each evaluate is executed on an instance per thread
|
|
234
|
+
return self.get_per_thread_instance()._eval(expr, input, environment)
|
|
235
|
+
|
|
236
|
+
def _eval(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any], environment: Optional[Frame]) -> Optional[Any]:
|
|
237
|
+
result = None
|
|
238
|
+
|
|
239
|
+
# Store the current input
|
|
240
|
+
# This is required by Functions.functionEval for current $eval() input context
|
|
241
|
+
self.input = input
|
|
242
|
+
|
|
243
|
+
if self.parser.dbg:
|
|
244
|
+
print("eval expr=" + str(expr) + " type=" + expr.type) # +" input="+input);
|
|
245
|
+
|
|
246
|
+
entry_callback = environment.lookup("__evaluate_entry")
|
|
247
|
+
if entry_callback is not None:
|
|
248
|
+
entry_callback(expr, input, environment)
|
|
249
|
+
|
|
250
|
+
if getattr(expr, "type", None) is not None:
|
|
251
|
+
if expr.type == "path":
|
|
252
|
+
result = self.evaluate_path(expr, input, environment)
|
|
253
|
+
elif expr.type == "binary":
|
|
254
|
+
result = self.evaluate_binary(expr, input, environment)
|
|
255
|
+
elif expr.type == "unary":
|
|
256
|
+
result = self.evaluate_unary(expr, input, environment)
|
|
257
|
+
elif expr.type == "name":
|
|
258
|
+
result = self.evaluate_name(expr, input, environment)
|
|
259
|
+
if self.parser.dbg:
|
|
260
|
+
print("evalName " + result)
|
|
261
|
+
elif expr.type == "string" or expr.type == "number" or expr.type == "value":
|
|
262
|
+
result = self.evaluate_literal(expr) # , input, environment);
|
|
263
|
+
elif expr.type == "wildcard":
|
|
264
|
+
result = self.evaluate_wildcard(expr, input) # , environment);
|
|
265
|
+
elif expr.type == "descendant":
|
|
266
|
+
result = self.evaluate_descendants(expr, input) # , environment);
|
|
267
|
+
elif expr.type == "parent":
|
|
268
|
+
result = environment.lookup(expr.slot.label)
|
|
269
|
+
elif expr.type == "condition":
|
|
270
|
+
result = self.evaluate_condition(expr, input, environment)
|
|
271
|
+
elif expr.type == "block":
|
|
272
|
+
result = self.evaluate_block(expr, input, environment)
|
|
273
|
+
elif expr.type == "bind":
|
|
274
|
+
result = self.evaluate_bind_expression(expr, input, environment)
|
|
275
|
+
elif expr.type == "regex":
|
|
276
|
+
result = self.evaluate_regex(expr) # , input, environment);
|
|
277
|
+
elif expr.type == "function":
|
|
278
|
+
result = self.evaluate_function(expr, input, environment, utils.Utils.NONE)
|
|
279
|
+
elif expr.type == "variable":
|
|
280
|
+
result = self.evaluate_variable(expr, input, environment)
|
|
281
|
+
elif expr.type == "lambda":
|
|
282
|
+
result = self.evaluate_lambda(expr, input, environment)
|
|
283
|
+
elif expr.type == "partial":
|
|
284
|
+
result = self.evaluate_partial_application(expr, input, environment)
|
|
285
|
+
elif expr.type == "apply":
|
|
286
|
+
result = self.evaluate_apply_expression(expr, input, environment)
|
|
287
|
+
elif expr.type == "transform":
|
|
288
|
+
result = self.evaluate_transform_expression(expr, input, environment)
|
|
289
|
+
|
|
290
|
+
if getattr(expr, "predicate", None) is not None:
|
|
291
|
+
for item in expr.predicate:
|
|
292
|
+
result = self.evaluate_filter(item.expr, result, environment)
|
|
293
|
+
|
|
294
|
+
if getattr(expr, "type", None) is not None and expr.type != "path" and getattr(expr, "group", None) is not None:
|
|
295
|
+
result = self.evaluate_group_expression(expr.group, result, environment)
|
|
296
|
+
|
|
297
|
+
exit_callback = environment.lookup("__evaluate_exit")
|
|
298
|
+
if exit_callback is not None:
|
|
299
|
+
exit_callback(expr, input, environment, result)
|
|
300
|
+
|
|
301
|
+
# mangle result (list of 1 element -> 1 element, empty list -> null)
|
|
302
|
+
if result is not None and utils.Utils.is_sequence(result) and not result.tuple_stream:
|
|
303
|
+
if expr.keep_array:
|
|
304
|
+
result.keep_singleton = True
|
|
305
|
+
if not result:
|
|
306
|
+
result = None
|
|
307
|
+
elif len(result) == 1:
|
|
308
|
+
result = result if result.keep_singleton else result[0]
|
|
309
|
+
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
#
|
|
313
|
+
# Evaluate path expression against input data
|
|
314
|
+
# @param {Object} expr - JSONata expression
|
|
315
|
+
# @param {Object} input - Input data to evaluate against
|
|
316
|
+
# @param {Object} environment - Environment
|
|
317
|
+
# @returns {*} Evaluated input data
|
|
318
|
+
#
|
|
319
|
+
# async
|
|
320
|
+
def evaluate_path(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
321
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
322
|
+
# expr is an array of steps
|
|
323
|
+
# if the first step is a variable reference ($...), including root reference ($$),
|
|
324
|
+
# then the path is absolute rather than relative
|
|
325
|
+
if isinstance(input, list) and expr.steps[0].type != "variable":
|
|
326
|
+
input_sequence = input
|
|
327
|
+
else:
|
|
328
|
+
# if input is not an array, make it so
|
|
329
|
+
input_sequence = utils.Utils.create_sequence(input)
|
|
330
|
+
|
|
331
|
+
result_sequence = None
|
|
332
|
+
is_tuple_stream = False
|
|
333
|
+
tuple_bindings = None
|
|
334
|
+
|
|
335
|
+
# evaluate each step in turn
|
|
336
|
+
for ii, step in enumerate(expr.steps):
|
|
337
|
+
|
|
338
|
+
if step.tuple is not None:
|
|
339
|
+
is_tuple_stream = True
|
|
340
|
+
|
|
341
|
+
# if the first step is an explicit array constructor, then just evaluate that (i.e. don"t iterate over a context array)
|
|
342
|
+
if ii == 0 and step.consarray:
|
|
343
|
+
result_sequence = self.eval(step, input_sequence, environment)
|
|
344
|
+
else:
|
|
345
|
+
if is_tuple_stream:
|
|
346
|
+
tuple_bindings = self.evaluate_tuple_step(step, input_sequence, tuple_bindings, environment)
|
|
347
|
+
else:
|
|
348
|
+
result_sequence = self.evaluate_step(step, input_sequence, environment, ii == len(expr.steps) - 1)
|
|
349
|
+
|
|
350
|
+
if not is_tuple_stream and (result_sequence is None or not result_sequence):
|
|
351
|
+
break
|
|
352
|
+
|
|
353
|
+
if step.focus is None:
|
|
354
|
+
input_sequence = result_sequence
|
|
355
|
+
|
|
356
|
+
if is_tuple_stream:
|
|
357
|
+
if expr.tuple is not None:
|
|
358
|
+
# tuple stream is carrying ancestry information - keep this
|
|
359
|
+
result_sequence = tuple_bindings
|
|
360
|
+
else:
|
|
361
|
+
result_sequence = utils.Utils.create_sequence_from_iter(b["@"] for b in tuple_bindings)
|
|
362
|
+
|
|
363
|
+
if expr.keep_singleton_array:
|
|
364
|
+
|
|
365
|
+
# If we only got an ArrayList, convert it so we can set the keepSingleton flag
|
|
366
|
+
if not (isinstance(result_sequence, utils.Utils.JList)):
|
|
367
|
+
result_sequence = utils.Utils.JList(result_sequence)
|
|
368
|
+
|
|
369
|
+
# if the array is explicitly constructed in the expression and marked to promote singleton sequences to array
|
|
370
|
+
if (isinstance(result_sequence, utils.Utils.JList)) and result_sequence.cons and not result_sequence.sequence:
|
|
371
|
+
result_sequence = utils.Utils.create_sequence(result_sequence)
|
|
372
|
+
result_sequence.keep_singleton = True
|
|
373
|
+
|
|
374
|
+
if expr.group is not None:
|
|
375
|
+
result_sequence = self.evaluate_group_expression(expr.group,
|
|
376
|
+
tuple_bindings if is_tuple_stream else result_sequence,
|
|
377
|
+
environment)
|
|
378
|
+
|
|
379
|
+
return result_sequence
|
|
380
|
+
|
|
381
|
+
def create_frame_from_tuple(self, environment: Optional[Frame], tuple: Optional[Mapping[str, Any]]) -> Frame:
|
|
382
|
+
frame = self.create_frame(environment)
|
|
383
|
+
if tuple is not None:
|
|
384
|
+
for prop, val in tuple.items():
|
|
385
|
+
frame.bind(prop, val)
|
|
386
|
+
return frame
|
|
387
|
+
|
|
388
|
+
#
|
|
389
|
+
# Evaluate a step within a path
|
|
390
|
+
# @param {Object} expr - JSONata expression
|
|
391
|
+
# @param {Object} input - Input data to evaluate against
|
|
392
|
+
# @param {Object} environment - Environment
|
|
393
|
+
# @param {boolean} lastStep - flag the last step in a path
|
|
394
|
+
# @returns {*} Evaluated input data
|
|
395
|
+
#
|
|
396
|
+
# async
|
|
397
|
+
def evaluate_step(self, expr: parser.Parser.Symbol, input: Optional[Any], environment: Optional[Frame],
|
|
398
|
+
last_step: bool) -> Optional[Any]:
|
|
399
|
+
if expr.type == "sort":
|
|
400
|
+
result = self.evaluate_sort_expression(expr, input, environment)
|
|
401
|
+
if expr.stages is not None:
|
|
402
|
+
result = self.evaluate_stages(expr.stages, result, environment)
|
|
403
|
+
return result
|
|
404
|
+
|
|
405
|
+
result = utils.Utils.create_sequence()
|
|
406
|
+
|
|
407
|
+
for inp in input:
|
|
408
|
+
res = self.eval(expr, inp, environment)
|
|
409
|
+
if expr.stages is not None:
|
|
410
|
+
for stage in expr.stages:
|
|
411
|
+
res = self.evaluate_filter(stage.expr, res, environment)
|
|
412
|
+
if res is not None:
|
|
413
|
+
result.append(res)
|
|
414
|
+
|
|
415
|
+
result_sequence = utils.Utils.create_sequence()
|
|
416
|
+
if last_step and len(result) == 1 and (isinstance(result[0], list)) and not utils.Utils.is_sequence(
|
|
417
|
+
result[0]):
|
|
418
|
+
result_sequence = result[0]
|
|
419
|
+
else:
|
|
420
|
+
# flatten the sequence
|
|
421
|
+
for res in result:
|
|
422
|
+
if not (isinstance(res, list)) or (isinstance(res, utils.Utils.JList) and res.cons):
|
|
423
|
+
# it's not an array - just push into the result sequence
|
|
424
|
+
result_sequence.append(res)
|
|
425
|
+
else:
|
|
426
|
+
# res is a sequence - flatten it into the parent sequence
|
|
427
|
+
result_sequence.extend(res)
|
|
428
|
+
|
|
429
|
+
return result_sequence
|
|
430
|
+
|
|
431
|
+
# async
|
|
432
|
+
def evaluate_stages(self, stages: Optional[Sequence[parser.Parser.Symbol]], input: Any,
|
|
433
|
+
environment: Optional[Frame]) -> Any:
|
|
434
|
+
result = input
|
|
435
|
+
for stage in stages:
|
|
436
|
+
if stage.type == "filter":
|
|
437
|
+
result = self.evaluate_filter(stage.expr, result, environment)
|
|
438
|
+
elif stage.type == "index":
|
|
439
|
+
for ee, tuple in enumerate(result):
|
|
440
|
+
tuple[str(stage.value)] = ee
|
|
441
|
+
return result
|
|
442
|
+
|
|
443
|
+
#
|
|
444
|
+
# Evaluate a step within a path
|
|
445
|
+
# @param {Object} expr - JSONata expression
|
|
446
|
+
# @param {Object} input - Input data to evaluate against
|
|
447
|
+
# @param {Object} tupleBindings - The tuple stream
|
|
448
|
+
# @param {Object} environment - Environment
|
|
449
|
+
# @returns {*} Evaluated input data
|
|
450
|
+
#
|
|
451
|
+
# async
|
|
452
|
+
def evaluate_tuple_step(self, expr: parser.Parser.Symbol, input: Optional[Sequence],
|
|
453
|
+
tuple_bindings: Optional[Sequence[Mapping[str, Any]]],
|
|
454
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
455
|
+
result = None
|
|
456
|
+
if expr.type == "sort":
|
|
457
|
+
if tuple_bindings is not None:
|
|
458
|
+
result = self.evaluate_sort_expression(expr, tuple_bindings, environment)
|
|
459
|
+
else:
|
|
460
|
+
sorted = self.evaluate_sort_expression(expr, input, environment)
|
|
461
|
+
result = utils.Utils.create_sequence_from_iter({"@": item, expr.index: ss}
|
|
462
|
+
for ss, item in enumerate(sorted))
|
|
463
|
+
result.tuple_stream = True
|
|
464
|
+
if expr.stages is not None:
|
|
465
|
+
result = self.evaluate_stages(expr.stages, result, environment)
|
|
466
|
+
return result
|
|
467
|
+
|
|
468
|
+
result = utils.Utils.create_sequence()
|
|
469
|
+
result.tuple_stream = True
|
|
470
|
+
step_env = environment
|
|
471
|
+
if tuple_bindings is None:
|
|
472
|
+
tuple_bindings = [{"@": item} for item in input if item is not None]
|
|
473
|
+
|
|
474
|
+
for tuple_binding in tuple_bindings:
|
|
475
|
+
step_env = self.create_frame_from_tuple(environment, tuple_binding)
|
|
476
|
+
res = self.eval(expr, tuple_binding["@"], step_env)
|
|
477
|
+
# res is the binding sequence for the output tuple stream
|
|
478
|
+
if res is not None:
|
|
479
|
+
if not (isinstance(res, list)):
|
|
480
|
+
res = [res]
|
|
481
|
+
for bb, item in enumerate(res):
|
|
482
|
+
tuple = dict(tuple_binding)
|
|
483
|
+
# Object.assign(tuple, tupleBindings[ee])
|
|
484
|
+
if (isinstance(res, utils.Utils.JList)) and res.tuple_stream:
|
|
485
|
+
tuple.update(item)
|
|
486
|
+
else:
|
|
487
|
+
if expr.focus is not None:
|
|
488
|
+
tuple[expr.focus] = item
|
|
489
|
+
tuple["@"] = tuple_binding["@"]
|
|
490
|
+
else:
|
|
491
|
+
tuple["@"] = item
|
|
492
|
+
if expr.index is not None:
|
|
493
|
+
tuple[expr.index] = bb
|
|
494
|
+
if expr.ancestor is not None:
|
|
495
|
+
tuple[expr.ancestor.label] = tuple_binding["@"]
|
|
496
|
+
result.append(tuple)
|
|
497
|
+
|
|
498
|
+
if expr.stages is not None:
|
|
499
|
+
result = self.evaluate_stages(expr.stages, result, environment)
|
|
500
|
+
|
|
501
|
+
return result
|
|
502
|
+
|
|
503
|
+
#
|
|
504
|
+
# Apply filter predicate to input data
|
|
505
|
+
# @param {Object} predicate - filter expression
|
|
506
|
+
# @param {Object} input - Input data to apply predicates against
|
|
507
|
+
# @param {Object} environment - Environment
|
|
508
|
+
# @returns {*} Result after applying predicates
|
|
509
|
+
#
|
|
510
|
+
# async
|
|
511
|
+
def evaluate_filter(self, predicate: Optional[Any], input: Optional[Any], environment: Optional[Frame]) -> Any:
|
|
512
|
+
results = utils.Utils.create_sequence()
|
|
513
|
+
if isinstance(input, utils.Utils.JList) and input.tuple_stream:
|
|
514
|
+
results.tuple_stream = True
|
|
515
|
+
if not (isinstance(input, list)):
|
|
516
|
+
input = utils.Utils.create_sequence(input)
|
|
517
|
+
if predicate.type == "number":
|
|
518
|
+
index = int(predicate.value) # round it down - was Math.floor
|
|
519
|
+
if index < 0:
|
|
520
|
+
# count in from end of array
|
|
521
|
+
index = len(input) + index
|
|
522
|
+
item = input[index] if index < len(input) else None
|
|
523
|
+
if item is not None:
|
|
524
|
+
if isinstance(item, list):
|
|
525
|
+
results = item
|
|
526
|
+
else:
|
|
527
|
+
results.append(item)
|
|
528
|
+
else:
|
|
529
|
+
for index, item in enumerate(input):
|
|
530
|
+
context = item
|
|
531
|
+
env = environment
|
|
532
|
+
if isinstance(input, utils.Utils.JList) and input.tuple_stream:
|
|
533
|
+
context = item["@"]
|
|
534
|
+
env = self.create_frame_from_tuple(environment, item)
|
|
535
|
+
res = self.eval(predicate, context, env)
|
|
536
|
+
if utils.Utils.is_numeric(res):
|
|
537
|
+
res = utils.Utils.create_sequence(res)
|
|
538
|
+
if utils.Utils.is_array_of_numbers(res):
|
|
539
|
+
for ires in res:
|
|
540
|
+
# round it down
|
|
541
|
+
ii = int(ires) # Math.floor(ires);
|
|
542
|
+
if ii < 0:
|
|
543
|
+
# count in from end of array
|
|
544
|
+
ii = len(input) + ii
|
|
545
|
+
if ii == index:
|
|
546
|
+
results.append(item)
|
|
547
|
+
elif Jsonata.boolize(res):
|
|
548
|
+
results.append(item)
|
|
549
|
+
return results
|
|
550
|
+
|
|
551
|
+
#
|
|
552
|
+
# Evaluate binary expression against input data
|
|
553
|
+
# @param {Object} expr - JSONata expression
|
|
554
|
+
# @param {Object} input - Input data to evaluate against
|
|
555
|
+
# @param {Object} environment - Environment
|
|
556
|
+
# @returns {*} Evaluated input data
|
|
557
|
+
#
|
|
558
|
+
# async
|
|
559
|
+
def evaluate_binary(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
560
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
561
|
+
lhs = self.eval(expr.lhs, input, environment)
|
|
562
|
+
op = str(expr.value)
|
|
563
|
+
|
|
564
|
+
if op == "and" or op == "or":
|
|
565
|
+
|
|
566
|
+
# defer evaluation of RHS to allow short-circuiting
|
|
567
|
+
evalrhs = lambda: self.eval(expr.rhs, input, environment)
|
|
568
|
+
try:
|
|
569
|
+
return self.evaluate_boolean_expression(lhs, evalrhs, op)
|
|
570
|
+
except Exception as err:
|
|
571
|
+
if not (isinstance(err, jexception.JException)):
|
|
572
|
+
raise jexception.JException("Unexpected", expr.position)
|
|
573
|
+
# err.position = expr.position
|
|
574
|
+
# err.token = op
|
|
575
|
+
raise err
|
|
576
|
+
|
|
577
|
+
rhs = self.eval(expr.rhs, input, environment) # evalrhs();
|
|
578
|
+
try:
|
|
579
|
+
if op == "+" or op == "-" or op == "*" or op == "/" or op == "%":
|
|
580
|
+
result = self.evaluate_numeric_expression(lhs, rhs, op)
|
|
581
|
+
elif op == "=" or op == "!=":
|
|
582
|
+
result = self.evaluate_equality_expression(lhs, rhs, op)
|
|
583
|
+
elif op == "<" or op == "<=" or op == ">" or op == ">=":
|
|
584
|
+
result = self.evaluate_comparison_expression(lhs, rhs, op)
|
|
585
|
+
elif op == "&":
|
|
586
|
+
result = self.evaluate_string_concat(lhs, rhs)
|
|
587
|
+
elif op == "..":
|
|
588
|
+
result = self.evaluate_range_expression(lhs, rhs)
|
|
589
|
+
elif op == "in":
|
|
590
|
+
result = self.evaluate_includes_expression(lhs, rhs)
|
|
591
|
+
else:
|
|
592
|
+
raise jexception.JException("Unexpected operator " + op, expr.position)
|
|
593
|
+
except Exception as err:
|
|
594
|
+
# err.position = expr.position
|
|
595
|
+
# err.token = op
|
|
596
|
+
raise err
|
|
597
|
+
return result
|
|
598
|
+
|
|
599
|
+
#
|
|
600
|
+
# Evaluate unary expression against input data
|
|
601
|
+
# @param {Object} expr - JSONata expression
|
|
602
|
+
# @param {Object} input - Input data to evaluate against
|
|
603
|
+
# @param {Object} environment - Environment
|
|
604
|
+
# @returns {*} Evaluated input data
|
|
605
|
+
#
|
|
606
|
+
# async
|
|
607
|
+
def evaluate_unary(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
608
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
609
|
+
result = None
|
|
610
|
+
|
|
611
|
+
value = str(expr.value)
|
|
612
|
+
if value == "-":
|
|
613
|
+
result = self.eval(expr.expression, input, environment)
|
|
614
|
+
if result is None:
|
|
615
|
+
result = None
|
|
616
|
+
elif utils.Utils.is_numeric(result):
|
|
617
|
+
result = utils.Utils.convert_number(-float(result))
|
|
618
|
+
else:
|
|
619
|
+
raise jexception.JException("D1002", expr.position, expr.value, result)
|
|
620
|
+
elif value == "[":
|
|
621
|
+
# array constructor - evaluate each item
|
|
622
|
+
result = utils.Utils.JList() # [];
|
|
623
|
+
idx = 0
|
|
624
|
+
for item in expr.expressions:
|
|
625
|
+
environment.is_parallel_call = idx > 0
|
|
626
|
+
value = self.eval(item, input, environment)
|
|
627
|
+
if value is not None:
|
|
628
|
+
if str(item.value) == "[":
|
|
629
|
+
result.append(value)
|
|
630
|
+
else:
|
|
631
|
+
result = functions.Functions.append(result, value)
|
|
632
|
+
idx += 1
|
|
633
|
+
if expr.consarray:
|
|
634
|
+
if not (isinstance(result, utils.Utils.JList)):
|
|
635
|
+
result = utils.Utils.JList(result)
|
|
636
|
+
# System.out.println("const "+result)
|
|
637
|
+
result.cons = True
|
|
638
|
+
elif value == "{":
|
|
639
|
+
# object constructor - apply grouping
|
|
640
|
+
result = self.evaluate_group_expression(expr, input, environment)
|
|
641
|
+
|
|
642
|
+
return result
|
|
643
|
+
|
|
644
|
+
#
|
|
645
|
+
# Evaluate name object against input data
|
|
646
|
+
# @param {Object} expr - JSONata expression
|
|
647
|
+
# @param {Object} input - Input data to evaluate against
|
|
648
|
+
# @param {Object} environment - Environment
|
|
649
|
+
# @returns {*} Evaluated input data
|
|
650
|
+
#
|
|
651
|
+
def evaluate_name(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
652
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
653
|
+
# lookup the "name" item in the input
|
|
654
|
+
return functions.Functions.lookup(input, str(expr.value))
|
|
655
|
+
|
|
656
|
+
#
|
|
657
|
+
# Evaluate literal against input data
|
|
658
|
+
# @param {Object} expr - JSONata expression
|
|
659
|
+
# @returns {*} Evaluated input data
|
|
660
|
+
#
|
|
661
|
+
def evaluate_literal(self, expr: Optional[parser.Parser.Symbol]) -> Optional[Any]:
|
|
662
|
+
return expr.value if expr.value is not None else utils.Utils.NULL_VALUE
|
|
663
|
+
|
|
664
|
+
#
|
|
665
|
+
# Evaluate wildcard against input data
|
|
666
|
+
# @param {Object} expr - JSONata expression
|
|
667
|
+
# @param {Object} input - Input data to evaluate against
|
|
668
|
+
# @returns {*} Evaluated input data
|
|
669
|
+
#
|
|
670
|
+
def evaluate_wildcard(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any]) -> Optional[Any]:
|
|
671
|
+
results = utils.Utils.create_sequence()
|
|
672
|
+
if (isinstance(input, utils.Utils.JList)) and input.outer_wrapper and input:
|
|
673
|
+
input = input[0]
|
|
674
|
+
if input is not None and isinstance(input, dict):
|
|
675
|
+
for value in input.values():
|
|
676
|
+
if isinstance(value, list):
|
|
677
|
+
value = self.flatten(value, None)
|
|
678
|
+
results = functions.Functions.append(results, value)
|
|
679
|
+
else:
|
|
680
|
+
results.append(value)
|
|
681
|
+
elif isinstance(input, list):
|
|
682
|
+
# Java: need to handle List separately
|
|
683
|
+
for value in input:
|
|
684
|
+
if isinstance(value, list):
|
|
685
|
+
value = self.flatten(value, None)
|
|
686
|
+
results = functions.Functions.append(results, value)
|
|
687
|
+
elif isinstance(value, dict):
|
|
688
|
+
# Call recursively do decompose the map
|
|
689
|
+
results.extend(self.evaluate_wildcard(expr, value))
|
|
690
|
+
else:
|
|
691
|
+
results.append(value)
|
|
692
|
+
|
|
693
|
+
# result = normalizeSequence(results)
|
|
694
|
+
return results
|
|
695
|
+
|
|
696
|
+
#
|
|
697
|
+
# Returns a flattened array
|
|
698
|
+
# @param {Array} arg - the array to be flatten
|
|
699
|
+
# @param {Array} flattened - carries the flattened array - if not defined, will initialize to []
|
|
700
|
+
# @returns {Array} - the flattened array
|
|
701
|
+
#
|
|
702
|
+
def flatten(self, arg: Any, flattened: Optional[MutableSequence]) -> Any:
|
|
703
|
+
if flattened is None:
|
|
704
|
+
flattened = []
|
|
705
|
+
if isinstance(arg, list):
|
|
706
|
+
for item in arg:
|
|
707
|
+
self.flatten(item, flattened)
|
|
708
|
+
else:
|
|
709
|
+
flattened.append(arg)
|
|
710
|
+
return flattened
|
|
711
|
+
|
|
712
|
+
#
|
|
713
|
+
# Evaluate descendants against input data
|
|
714
|
+
# @param {Object} expr - JSONata expression
|
|
715
|
+
# @param {Object} input - Input data to evaluate against
|
|
716
|
+
# @returns {*} Evaluated input data
|
|
717
|
+
#
|
|
718
|
+
def evaluate_descendants(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any]) -> Optional[Any]:
|
|
719
|
+
result = None
|
|
720
|
+
result_sequence = utils.Utils.create_sequence()
|
|
721
|
+
if input is not None:
|
|
722
|
+
# traverse all descendants of this object/array
|
|
723
|
+
self.recurse_descendants(input, result_sequence)
|
|
724
|
+
if len(result_sequence) == 1:
|
|
725
|
+
result = result_sequence[0]
|
|
726
|
+
else:
|
|
727
|
+
result = result_sequence
|
|
728
|
+
return result
|
|
729
|
+
|
|
730
|
+
#
|
|
731
|
+
# Recurse through descendants
|
|
732
|
+
# @param {Object} input - Input data
|
|
733
|
+
# @param {Object} results - Results
|
|
734
|
+
#
|
|
735
|
+
def recurse_descendants(self, input: Optional[Any], results: MutableSequence) -> None:
|
|
736
|
+
# this is the equivalent of //* in XPath
|
|
737
|
+
if not (isinstance(input, list)):
|
|
738
|
+
results.append(input)
|
|
739
|
+
if isinstance(input, list):
|
|
740
|
+
for member in input:
|
|
741
|
+
self.recurse_descendants(member, results)
|
|
742
|
+
elif input is not None and isinstance(input, dict):
|
|
743
|
+
for value in input.values():
|
|
744
|
+
self.recurse_descendants(value, results)
|
|
745
|
+
|
|
746
|
+
#
|
|
747
|
+
# Evaluate numeric expression against input data
|
|
748
|
+
# @param {Object} lhs - LHS value
|
|
749
|
+
# @param {Object} rhs - RHS value
|
|
750
|
+
# @param {Object} op - opcode
|
|
751
|
+
# @returns {*} Result
|
|
752
|
+
#
|
|
753
|
+
def evaluate_numeric_expression(self, lhs: Optional[Any], rhs: Optional[Any], op: Optional[str]) -> Optional[Any]:
|
|
754
|
+
result = 0
|
|
755
|
+
|
|
756
|
+
if lhs is not None and not utils.Utils.is_numeric(lhs):
|
|
757
|
+
raise jexception.JException("T2001", -1, op, lhs)
|
|
758
|
+
if rhs is not None and not utils.Utils.is_numeric(rhs):
|
|
759
|
+
raise jexception.JException("T2002", -1, op, rhs)
|
|
760
|
+
|
|
761
|
+
if lhs is None or rhs is None:
|
|
762
|
+
# if either side is undefined, the result is undefined
|
|
763
|
+
return None
|
|
764
|
+
|
|
765
|
+
# System.out.println("op22 "+op+" "+_lhs+" "+_rhs)
|
|
766
|
+
lhs = float(lhs)
|
|
767
|
+
rhs = float(rhs)
|
|
768
|
+
|
|
769
|
+
if op == "+":
|
|
770
|
+
result = lhs + rhs
|
|
771
|
+
elif op == "-":
|
|
772
|
+
result = lhs - rhs
|
|
773
|
+
elif op == "*":
|
|
774
|
+
result = lhs * rhs
|
|
775
|
+
elif op == "/":
|
|
776
|
+
result = lhs / rhs
|
|
777
|
+
elif op == "%":
|
|
778
|
+
result = int(math.fmod(lhs, rhs))
|
|
779
|
+
return utils.Utils.convert_number(result)
|
|
780
|
+
|
|
781
|
+
#
|
|
782
|
+
# Evaluate equality expression against input data
|
|
783
|
+
# @param {Object} lhs - LHS value
|
|
784
|
+
# @param {Object} rhs - RHS value
|
|
785
|
+
# @param {Object} op - opcode
|
|
786
|
+
# @returns {*} Result
|
|
787
|
+
#
|
|
788
|
+
def evaluate_equality_expression(self, lhs: Optional[Any], rhs: Optional[Any], op: Optional[str]) -> Optional[Any]:
|
|
789
|
+
if lhs is None or rhs is None:
|
|
790
|
+
# if either side is undefined, the result is false
|
|
791
|
+
return False
|
|
792
|
+
|
|
793
|
+
# JSON might come with integers,
|
|
794
|
+
# convert all to double...
|
|
795
|
+
# FIXME: semantically OK?
|
|
796
|
+
if not isinstance(lhs, bool) and isinstance(lhs, (int, float)):
|
|
797
|
+
lhs = float(lhs)
|
|
798
|
+
if not isinstance(rhs, bool) and isinstance(rhs, (int, float)):
|
|
799
|
+
rhs = float(rhs)
|
|
800
|
+
|
|
801
|
+
result = None
|
|
802
|
+
if op == "=":
|
|
803
|
+
result = lhs == rhs # isDeepEqual(lhs, rhs);
|
|
804
|
+
elif op == "!=":
|
|
805
|
+
result = lhs != rhs # !isDeepEqual(lhs, rhs);
|
|
806
|
+
return result
|
|
807
|
+
|
|
808
|
+
#
|
|
809
|
+
# Evaluate comparison expression against input data
|
|
810
|
+
# @param {Object} lhs - LHS value
|
|
811
|
+
# @param {Object} rhs - RHS value
|
|
812
|
+
# @param {Object} op - opcode
|
|
813
|
+
# @returns {*} Result
|
|
814
|
+
#
|
|
815
|
+
def evaluate_comparison_expression(self, lhs: Optional[Any], rhs: Optional[Any], op: Optional[str]) -> Optional[Any]:
|
|
816
|
+
result = None
|
|
817
|
+
|
|
818
|
+
# type checks
|
|
819
|
+
lcomparable = (lhs is None or isinstance(lhs, str) or
|
|
820
|
+
(not isinstance(lhs, bool) and isinstance(lhs, (int, float))))
|
|
821
|
+
rcomparable = (rhs is None or isinstance(rhs, str) or
|
|
822
|
+
(not isinstance(rhs, bool) and isinstance(rhs, (int, float))))
|
|
823
|
+
|
|
824
|
+
# if either aa or bb are not comparable (string or numeric) values, then throw an error
|
|
825
|
+
if not lcomparable or not rcomparable:
|
|
826
|
+
raise jexception.JException("T2010", 0, op, lhs if lhs is not None else rhs)
|
|
827
|
+
|
|
828
|
+
# if either side is undefined, the result is undefined
|
|
829
|
+
if lhs is None or rhs is None:
|
|
830
|
+
return None
|
|
831
|
+
|
|
832
|
+
# if aa and bb are not of the same type
|
|
833
|
+
if type(lhs) is not type(rhs):
|
|
834
|
+
|
|
835
|
+
if (not isinstance(lhs, bool) and isinstance(lhs, (int, float)) and
|
|
836
|
+
not isinstance(rhs, bool) and isinstance(rhs, (int, float))):
|
|
837
|
+
# Java : handle Double / Integer / Long comparisons
|
|
838
|
+
# convert all to double -> loss of precision (64-bit long to double) be a problem here?
|
|
839
|
+
lhs = float(lhs)
|
|
840
|
+
rhs = float(rhs)
|
|
841
|
+
|
|
842
|
+
else:
|
|
843
|
+
|
|
844
|
+
raise jexception.JException("T2009", 0, lhs, rhs)
|
|
845
|
+
|
|
846
|
+
if op == "<":
|
|
847
|
+
result = lhs < rhs
|
|
848
|
+
elif op == "<=":
|
|
849
|
+
result = lhs <= rhs
|
|
850
|
+
elif op == ">":
|
|
851
|
+
result = lhs > rhs
|
|
852
|
+
elif op == ">=":
|
|
853
|
+
result = lhs >= rhs
|
|
854
|
+
return result
|
|
855
|
+
|
|
856
|
+
#
|
|
857
|
+
# Inclusion operator - in
|
|
858
|
+
#
|
|
859
|
+
# @param {Object} lhs - LHS value
|
|
860
|
+
# @param {Object} rhs - RHS value
|
|
861
|
+
# @returns {boolean} - true if lhs is a member of rhs
|
|
862
|
+
#
|
|
863
|
+
def evaluate_includes_expression(self, lhs: Optional[Any], rhs: Optional[Any]) -> Any:
|
|
864
|
+
result = False
|
|
865
|
+
|
|
866
|
+
if lhs is None or rhs is None:
|
|
867
|
+
# if either side is undefined, the result is false
|
|
868
|
+
return False
|
|
869
|
+
|
|
870
|
+
if not (isinstance(rhs, list)):
|
|
871
|
+
rhs = [rhs]
|
|
872
|
+
|
|
873
|
+
for item in rhs:
|
|
874
|
+
if item == lhs:
|
|
875
|
+
result = True
|
|
876
|
+
break
|
|
877
|
+
|
|
878
|
+
return result
|
|
879
|
+
|
|
880
|
+
#
|
|
881
|
+
# Evaluate boolean expression against input data
|
|
882
|
+
# @param {Object} lhs - LHS value
|
|
883
|
+
# @param {functions.Function} evalrhs - Object to evaluate RHS value
|
|
884
|
+
# @param {Object} op - opcode
|
|
885
|
+
# @returns {*} Result
|
|
886
|
+
#
|
|
887
|
+
# async
|
|
888
|
+
def evaluate_boolean_expression(self, lhs: Optional[Any], evalrhs: Callable[[], Optional[Any]],
|
|
889
|
+
op: Optional[str]) -> Optional[Any]:
|
|
890
|
+
result = None
|
|
891
|
+
|
|
892
|
+
l_bool = Jsonata.boolize(lhs)
|
|
893
|
+
|
|
894
|
+
if op == "and":
|
|
895
|
+
result = l_bool and Jsonata.boolize(evalrhs())
|
|
896
|
+
elif op == "or":
|
|
897
|
+
result = l_bool or Jsonata.boolize(evalrhs())
|
|
898
|
+
return result
|
|
899
|
+
|
|
900
|
+
@staticmethod
|
|
901
|
+
def boolize(value: Optional[Any]) -> bool:
|
|
902
|
+
booled_value = functions.Functions.to_boolean(value)
|
|
903
|
+
return False if booled_value is None else booled_value
|
|
904
|
+
|
|
905
|
+
#
|
|
906
|
+
# Evaluate string concatenation against input data
|
|
907
|
+
# @param {Object} lhs - LHS value
|
|
908
|
+
# @param {Object} rhs - RHS value
|
|
909
|
+
# @returns {string|*} Concatenated string
|
|
910
|
+
#
|
|
911
|
+
def evaluate_string_concat(self, lhs: Optional[Any], rhs: Optional[Any]) -> str:
|
|
912
|
+
lstr = ""
|
|
913
|
+
rstr = ""
|
|
914
|
+
if lhs is not None:
|
|
915
|
+
lstr = functions.Functions.string(lhs, None)
|
|
916
|
+
if rhs is not None:
|
|
917
|
+
rstr = functions.Functions.string(rhs, None)
|
|
918
|
+
|
|
919
|
+
result = lstr + rstr
|
|
920
|
+
return result
|
|
921
|
+
|
|
922
|
+
@dataclass
|
|
923
|
+
class GroupEntry:
|
|
924
|
+
data: Optional[Any]
|
|
925
|
+
exprIndex: int
|
|
926
|
+
|
|
927
|
+
#
|
|
928
|
+
# Evaluate group expression against input data
|
|
929
|
+
# @param {Object} expr - JSONata expression
|
|
930
|
+
# @param {Object} input - Input data to evaluate against
|
|
931
|
+
# @param {Object} environment - Environment
|
|
932
|
+
# @returns {{}} Evaluated input data
|
|
933
|
+
#
|
|
934
|
+
# async
|
|
935
|
+
def evaluate_group_expression(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
936
|
+
environment: Optional[Frame]) -> Any:
|
|
937
|
+
result = {}
|
|
938
|
+
groups = {}
|
|
939
|
+
reduce = True if (isinstance(input, utils.Utils.JList)) and input.tuple_stream else False
|
|
940
|
+
# group the input sequence by "key" expression
|
|
941
|
+
if not (isinstance(input, list)):
|
|
942
|
+
input = utils.Utils.create_sequence(input)
|
|
943
|
+
|
|
944
|
+
# if the array is empty, add an undefined entry to enable literal JSON object to be generated
|
|
945
|
+
if not input:
|
|
946
|
+
input.append(None)
|
|
947
|
+
|
|
948
|
+
for itemIndex, item in enumerate(input):
|
|
949
|
+
env = self.create_frame_from_tuple(environment, item) if reduce else environment
|
|
950
|
+
for pairIndex, pair in enumerate(expr.lhs_object):
|
|
951
|
+
key = self.eval(pair[0], item["@"] if reduce else item, env)
|
|
952
|
+
# key has to be a string
|
|
953
|
+
if key is not None and not (isinstance(key, str)):
|
|
954
|
+
raise jexception.JException("T1003", expr.position, key)
|
|
955
|
+
|
|
956
|
+
if key is not None:
|
|
957
|
+
entry = Jsonata.GroupEntry(item, pairIndex)
|
|
958
|
+
if groups.get(key) is not None:
|
|
959
|
+
# a value already exists in this slot
|
|
960
|
+
if groups[key].exprIndex != pairIndex:
|
|
961
|
+
# this key has been generated by another expression in this group
|
|
962
|
+
# when multiple key expressions evaluate to the same key, then error D1009 must be thrown
|
|
963
|
+
raise jexception.JException("D1009", expr.position, key)
|
|
964
|
+
|
|
965
|
+
# append it as an array
|
|
966
|
+
groups[key].data = functions.Functions.append(groups[key].data, item)
|
|
967
|
+
else:
|
|
968
|
+
groups[key] = entry
|
|
969
|
+
|
|
970
|
+
# iterate over the groups to evaluate the "value" expression
|
|
971
|
+
# let generators = /* await */ Promise.all(Object.keys(groups).map(/* async */ (key, idx) => {
|
|
972
|
+
idx = 0
|
|
973
|
+
for k, v in groups.items():
|
|
974
|
+
entry = v
|
|
975
|
+
context = entry.data
|
|
976
|
+
env = environment
|
|
977
|
+
if reduce:
|
|
978
|
+
tuple = self.reduce_tuple_stream(entry.data)
|
|
979
|
+
context = tuple["@"]
|
|
980
|
+
tuple.pop("@", None)
|
|
981
|
+
env = self.create_frame_from_tuple(environment, tuple)
|
|
982
|
+
env.is_parallel_call = idx > 0
|
|
983
|
+
# return [key, /* await */ eval(expr.lhs[entry.exprIndex][1], context, env)]
|
|
984
|
+
res = self.eval(expr.lhs_object[entry.exprIndex][1], context, env)
|
|
985
|
+
if res is not None:
|
|
986
|
+
result[k] = res
|
|
987
|
+
|
|
988
|
+
idx += 1
|
|
989
|
+
|
|
990
|
+
# for (let generator of generators) {
|
|
991
|
+
# var [key, value] = /* await */ generator
|
|
992
|
+
# if(typeof value !== "undefined") {
|
|
993
|
+
# result[key] = value
|
|
994
|
+
# }
|
|
995
|
+
# }
|
|
996
|
+
|
|
997
|
+
return result
|
|
998
|
+
|
|
999
|
+
def reduce_tuple_stream(self, tuple_stream: Optional[Any]) -> Optional[Any]:
|
|
1000
|
+
if not (isinstance(tuple_stream, list)):
|
|
1001
|
+
return tuple_stream
|
|
1002
|
+
|
|
1003
|
+
result = dict(tuple_stream[0])
|
|
1004
|
+
|
|
1005
|
+
# Object.assign(result, tuple_stream[0])
|
|
1006
|
+
for ii in range(1, len(tuple_stream)):
|
|
1007
|
+
el = tuple_stream[ii]
|
|
1008
|
+
for k, v in el.items():
|
|
1009
|
+
result[k] = functions.Functions.append(result[k], v)
|
|
1010
|
+
return result
|
|
1011
|
+
|
|
1012
|
+
#
|
|
1013
|
+
# Evaluate range expression against input data
|
|
1014
|
+
# @param {Object} lhs - LHS value
|
|
1015
|
+
# @param {Object} rhs - RHS value
|
|
1016
|
+
# @returns {Array} Resultant array
|
|
1017
|
+
#
|
|
1018
|
+
def evaluate_range_expression(self, lhs: Optional[Any], rhs: Optional[Any]) -> Optional[Any]:
|
|
1019
|
+
result = None
|
|
1020
|
+
|
|
1021
|
+
if lhs is not None and (isinstance(lhs, bool) or not (isinstance(lhs, int))):
|
|
1022
|
+
raise jexception.JException("T2003", -1, lhs)
|
|
1023
|
+
if rhs is not None and (isinstance(rhs, bool) or not (isinstance(rhs, int))):
|
|
1024
|
+
raise jexception.JException("T2004", -1, rhs)
|
|
1025
|
+
|
|
1026
|
+
if rhs is None or lhs is None:
|
|
1027
|
+
# if either side is undefined, the result is undefined
|
|
1028
|
+
return result
|
|
1029
|
+
|
|
1030
|
+
lhs = int(lhs)
|
|
1031
|
+
rhs = int(rhs)
|
|
1032
|
+
|
|
1033
|
+
if lhs > rhs:
|
|
1034
|
+
# if the lhs is greater than the rhs, return undefined
|
|
1035
|
+
return result
|
|
1036
|
+
|
|
1037
|
+
# limit the size of the array to ten million entries (1e7)
|
|
1038
|
+
# this is an implementation defined limit to protect against
|
|
1039
|
+
# memory and performance issues. This value may increase in the future.
|
|
1040
|
+
size = rhs - lhs + 1
|
|
1041
|
+
if size > 1e7:
|
|
1042
|
+
raise jexception.JException("D2014", -1, size)
|
|
1043
|
+
|
|
1044
|
+
return utils.Utils.RangeList(lhs, rhs + 1)
|
|
1045
|
+
|
|
1046
|
+
#
|
|
1047
|
+
# Evaluate bind expression against input data
|
|
1048
|
+
# @param {Object} expr - JSONata expression
|
|
1049
|
+
# @param {Object} input - Input data to evaluate against
|
|
1050
|
+
# @param {Object} environment - Environment
|
|
1051
|
+
# @returns {*} Evaluated input data
|
|
1052
|
+
#
|
|
1053
|
+
# async
|
|
1054
|
+
def evaluate_bind_expression(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1055
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1056
|
+
# The RHS is the expression to evaluate
|
|
1057
|
+
# The LHS is the name of the variable to bind to - should be a VARIABLE token (enforced by parser)
|
|
1058
|
+
value = self.eval(expr.rhs, input, environment)
|
|
1059
|
+
environment.bind(str(expr.lhs.value), value)
|
|
1060
|
+
return value
|
|
1061
|
+
|
|
1062
|
+
#
|
|
1063
|
+
# Evaluate condition against input data
|
|
1064
|
+
# @param {Object} expr - JSONata expression
|
|
1065
|
+
# @param {Object} input - Input data to evaluate against
|
|
1066
|
+
# @param {Object} environment - Environment
|
|
1067
|
+
# @returns {*} Evaluated input data
|
|
1068
|
+
#
|
|
1069
|
+
# async
|
|
1070
|
+
def evaluate_condition(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1071
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1072
|
+
result = None
|
|
1073
|
+
condition = self.eval(expr.condition, input, environment)
|
|
1074
|
+
if Jsonata.boolize(condition):
|
|
1075
|
+
result = self.eval(expr.then, input, environment)
|
|
1076
|
+
elif expr._else is not None:
|
|
1077
|
+
result = self.eval(expr._else, input, environment)
|
|
1078
|
+
return result
|
|
1079
|
+
|
|
1080
|
+
#
|
|
1081
|
+
# Evaluate block against input data
|
|
1082
|
+
# @param {Object} expr - JSONata expression
|
|
1083
|
+
# @param {Object} input - Input data to evaluate against
|
|
1084
|
+
# @param {Object} environment - Environment
|
|
1085
|
+
# @returns {*} Evaluated input data
|
|
1086
|
+
#
|
|
1087
|
+
# async
|
|
1088
|
+
def evaluate_block(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1089
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1090
|
+
result = None
|
|
1091
|
+
# create a new frame to limit the scope of variable assignments
|
|
1092
|
+
# TODO, only do this if the post-parse stage has flagged this as required
|
|
1093
|
+
frame = self.create_frame(environment)
|
|
1094
|
+
# invoke each expression in turn
|
|
1095
|
+
# only return the result of the last one
|
|
1096
|
+
for ex in expr.expressions:
|
|
1097
|
+
result = self.eval(ex, input, frame)
|
|
1098
|
+
|
|
1099
|
+
return result
|
|
1100
|
+
|
|
1101
|
+
#
|
|
1102
|
+
# Prepare a regex
|
|
1103
|
+
# @param {Object} expr - expression containing regex
|
|
1104
|
+
# @returns {functions.Function} Higher order Object representing prepared regex
|
|
1105
|
+
#
|
|
1106
|
+
def evaluate_regex(self, expr: Optional[parser.Parser.Symbol]) -> Optional[Any]:
|
|
1107
|
+
# Note: in Java we just use the compiled regex Pattern
|
|
1108
|
+
# The apply functions need to take care to evaluate
|
|
1109
|
+
return expr.value
|
|
1110
|
+
|
|
1111
|
+
#
|
|
1112
|
+
# Evaluate variable against input data
|
|
1113
|
+
# @param {Object} expr - JSONata expression
|
|
1114
|
+
# @param {Object} input - Input data to evaluate against
|
|
1115
|
+
# @param {Object} environment - Environment
|
|
1116
|
+
# @returns {*} Evaluated input data
|
|
1117
|
+
#
|
|
1118
|
+
def evaluate_variable(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1119
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1120
|
+
# lookup the variable value in the environment
|
|
1121
|
+
result = None
|
|
1122
|
+
# if the variable name is empty string, then it refers to context value
|
|
1123
|
+
if expr.value == "":
|
|
1124
|
+
# Empty string == "$" !
|
|
1125
|
+
result = input[0] if isinstance(input, utils.Utils.JList) and input.outer_wrapper else input
|
|
1126
|
+
else:
|
|
1127
|
+
result = environment.lookup(str(expr.value))
|
|
1128
|
+
if self.parser.dbg:
|
|
1129
|
+
print("variable name=" + expr.value + " val=" + result)
|
|
1130
|
+
return result
|
|
1131
|
+
|
|
1132
|
+
#
|
|
1133
|
+
# sort / order-by operator
|
|
1134
|
+
# @param {Object} expr - AST for operator
|
|
1135
|
+
# @param {Object} input - Input data to evaluate against
|
|
1136
|
+
# @param {Object} environment - Environment
|
|
1137
|
+
# @returns {*} Ordered sequence
|
|
1138
|
+
#
|
|
1139
|
+
# async
|
|
1140
|
+
def evaluate_sort_expression(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1141
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1142
|
+
result = None
|
|
1143
|
+
|
|
1144
|
+
# evaluate the lhs, then sort the results in order according to rhs expression
|
|
1145
|
+
lhs = input
|
|
1146
|
+
is_tuple_sort = True if (isinstance(input, utils.Utils.JList) and input.tuple_stream) else False
|
|
1147
|
+
|
|
1148
|
+
# sort the lhs array
|
|
1149
|
+
# use comparator function
|
|
1150
|
+
comparator = Jsonata.ComparatorWrapper(self, expr, environment, is_tuple_sort).compare
|
|
1151
|
+
|
|
1152
|
+
# var focus = {
|
|
1153
|
+
# environment: environment,
|
|
1154
|
+
# input: input
|
|
1155
|
+
# }
|
|
1156
|
+
# // the `focus` is passed in as the `this` for the invoked function
|
|
1157
|
+
# result = /* await */ fn.sort.apply(focus, [lhs, comparator])
|
|
1158
|
+
|
|
1159
|
+
result = functions.Functions.sort(lhs, comparator)
|
|
1160
|
+
return result
|
|
1161
|
+
|
|
1162
|
+
class ComparatorWrapper:
|
|
1163
|
+
_outer_instance: 'Jsonata'
|
|
1164
|
+
_expr: Optional[parser.Parser.Symbol]
|
|
1165
|
+
_environment: 'Jsonata.Optional[Frame]'
|
|
1166
|
+
_is_tuple_sort: bool
|
|
1167
|
+
|
|
1168
|
+
def __init__(self, outer_instance, expr, environment, is_tuple_sort):
|
|
1169
|
+
self._outer_instance = outer_instance
|
|
1170
|
+
self._expr = expr
|
|
1171
|
+
self._environment = environment
|
|
1172
|
+
self._is_tuple_sort = is_tuple_sort
|
|
1173
|
+
|
|
1174
|
+
def compare(self, a, b):
|
|
1175
|
+
|
|
1176
|
+
# expr.terms is an array of order-by in priority order
|
|
1177
|
+
comp = 0
|
|
1178
|
+
index = 0
|
|
1179
|
+
while comp == 0 and index < len(self._expr.terms):
|
|
1180
|
+
term = self._expr.terms[index]
|
|
1181
|
+
# evaluate the sort term in the context of a
|
|
1182
|
+
context = a
|
|
1183
|
+
env = self._environment
|
|
1184
|
+
if self._is_tuple_sort:
|
|
1185
|
+
context = a["@"]
|
|
1186
|
+
env = self._outer_instance.create_frame_from_tuple(self._environment, a)
|
|
1187
|
+
aa = self._outer_instance.eval(term.expression, context, env)
|
|
1188
|
+
|
|
1189
|
+
# evaluate the sort term in the context of b
|
|
1190
|
+
context = b
|
|
1191
|
+
env = self._environment
|
|
1192
|
+
if self._is_tuple_sort:
|
|
1193
|
+
context = b["@"]
|
|
1194
|
+
env = self._outer_instance.create_frame_from_tuple(self._environment, b)
|
|
1195
|
+
bb = self._outer_instance.eval(term.expression, context, env)
|
|
1196
|
+
|
|
1197
|
+
# type checks
|
|
1198
|
+
# var atype = typeof aa
|
|
1199
|
+
# var btype = typeof bb
|
|
1200
|
+
# undefined should be last in sort order
|
|
1201
|
+
if aa is None:
|
|
1202
|
+
# swap them, unless btype is also undefined
|
|
1203
|
+
comp = 0 if (bb is None) else 1
|
|
1204
|
+
index += 1
|
|
1205
|
+
continue
|
|
1206
|
+
if bb is None:
|
|
1207
|
+
comp = -1
|
|
1208
|
+
index += 1
|
|
1209
|
+
continue
|
|
1210
|
+
|
|
1211
|
+
# if aa or bb are not string or numeric values, then throw an error
|
|
1212
|
+
if (not ((not isinstance(aa, bool) and isinstance(aa, (int, float))) or isinstance(aa, str)) or
|
|
1213
|
+
not ((not isinstance(bb, bool) and isinstance(bb, (int, float))) or isinstance(bb, str))):
|
|
1214
|
+
raise jexception.JException("T2008", self._expr.position, aa, bb)
|
|
1215
|
+
|
|
1216
|
+
# if aa and bb are not of the same type
|
|
1217
|
+
same_type = False
|
|
1218
|
+
if (not isinstance(aa, bool) and isinstance(aa, (int, float)) and
|
|
1219
|
+
not isinstance(bb, bool) and isinstance(bb, (int, float))):
|
|
1220
|
+
same_type = True
|
|
1221
|
+
elif issubclass(type(bb), type(aa)) or issubclass(type(aa), type(bb)):
|
|
1222
|
+
same_type = True
|
|
1223
|
+
|
|
1224
|
+
if not same_type:
|
|
1225
|
+
raise jexception.JException("T2007", self._expr.position, aa, bb)
|
|
1226
|
+
if aa == bb:
|
|
1227
|
+
# both the same - move on to next term
|
|
1228
|
+
index += 1
|
|
1229
|
+
continue
|
|
1230
|
+
elif aa < bb:
|
|
1231
|
+
comp = -1
|
|
1232
|
+
else:
|
|
1233
|
+
comp = 1
|
|
1234
|
+
if term.descending:
|
|
1235
|
+
comp = -comp
|
|
1236
|
+
index += 1
|
|
1237
|
+
# only swap a & b if comp equals 1
|
|
1238
|
+
# return comp == 1
|
|
1239
|
+
return comp
|
|
1240
|
+
|
|
1241
|
+
#
|
|
1242
|
+
# create a transformer function
|
|
1243
|
+
# @param {Object} expr - AST for operator
|
|
1244
|
+
# @param {Object} input - Input data to evaluate against
|
|
1245
|
+
# @param {Object} environment - Environment
|
|
1246
|
+
# @returns {*} tranformer function
|
|
1247
|
+
#
|
|
1248
|
+
def evaluate_transform_expression(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1249
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1250
|
+
# create a Object to implement the transform definition
|
|
1251
|
+
transformer = Jsonata.Transformer(self, expr, environment)
|
|
1252
|
+
return Jsonata.JFunction(transformer, "<(oa):o>")
|
|
1253
|
+
|
|
1254
|
+
_chain_ast = None # = new Parser().parse("function($f, $g) { function($x){ $g($f($x)) } }");
|
|
1255
|
+
|
|
1256
|
+
@staticmethod
|
|
1257
|
+
def chain_ast() -> Optional[parser.Parser.Symbol]:
|
|
1258
|
+
if Jsonata._chain_ast is None:
|
|
1259
|
+
# only create on demand
|
|
1260
|
+
Jsonata._chain_ast = (parser.Parser()).parse("function($f, $g) { function($x){ $g($f($x)) } }")
|
|
1261
|
+
return Jsonata._chain_ast
|
|
1262
|
+
|
|
1263
|
+
#
|
|
1264
|
+
# Apply the Object on the RHS using the sequence on the LHS as the first argument
|
|
1265
|
+
# @param {Object} expr - JSONata expression
|
|
1266
|
+
# @param {Object} input - Input data to evaluate against
|
|
1267
|
+
# @param {Object} environment - Environment
|
|
1268
|
+
# @returns {*} Evaluated input data
|
|
1269
|
+
#
|
|
1270
|
+
# async
|
|
1271
|
+
def evaluate_apply_expression(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1272
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1273
|
+
result = None
|
|
1274
|
+
|
|
1275
|
+
lhs = self.eval(expr.lhs, input, environment)
|
|
1276
|
+
|
|
1277
|
+
if expr.rhs.type == "function":
|
|
1278
|
+
# Symbol applyTo = new Symbol(); applyTo.context = lhs
|
|
1279
|
+
# this is a Object _invocation_; invoke it with lhs expression as the first argument
|
|
1280
|
+
result = self.evaluate_function(expr.rhs, input, environment, lhs)
|
|
1281
|
+
else:
|
|
1282
|
+
func = self.eval(expr.rhs, input, environment)
|
|
1283
|
+
|
|
1284
|
+
if not self.is_function_like(func) and not self.is_function_like(lhs):
|
|
1285
|
+
raise jexception.JException("T2006", expr.position, func)
|
|
1286
|
+
|
|
1287
|
+
if self.is_function_like(lhs):
|
|
1288
|
+
# this is Object chaining (func1 ~> func2)
|
|
1289
|
+
# λ($f, $g) { λ($x){ $g($f($x)) } }
|
|
1290
|
+
chain = self.eval(Jsonata.chain_ast(), None, environment)
|
|
1291
|
+
args = [lhs, func]
|
|
1292
|
+
result = self.apply(chain, args, None, environment)
|
|
1293
|
+
else:
|
|
1294
|
+
args = [lhs]
|
|
1295
|
+
result = self.apply(func, args, None, environment)
|
|
1296
|
+
|
|
1297
|
+
return result
|
|
1298
|
+
|
|
1299
|
+
def is_function_like(self, o: Optional[Any]) -> bool:
|
|
1300
|
+
return utils.Utils.is_function(o) or functions.Functions.is_lambda(o) or (isinstance(o, re.Pattern))
|
|
1301
|
+
|
|
1302
|
+
CURRENT = threading.local()
|
|
1303
|
+
MUTEX = threading.Lock()
|
|
1304
|
+
|
|
1305
|
+
#
|
|
1306
|
+
# Returns a per thread instance of this parsed expression.
|
|
1307
|
+
#
|
|
1308
|
+
# @return
|
|
1309
|
+
#
|
|
1310
|
+
def get_per_thread_instance(self):
|
|
1311
|
+
if hasattr(Jsonata.CURRENT, "jsonata"):
|
|
1312
|
+
return Jsonata.CURRENT.jsonata
|
|
1313
|
+
|
|
1314
|
+
with Jsonata.MUTEX:
|
|
1315
|
+
if hasattr(Jsonata.CURRENT, "jsonata"):
|
|
1316
|
+
return Jsonata.CURRENT.jsonata
|
|
1317
|
+
thread_inst = copy.copy(self)
|
|
1318
|
+
Jsonata.CURRENT.jsonata = thread_inst
|
|
1319
|
+
return thread_inst
|
|
1320
|
+
|
|
1321
|
+
#
|
|
1322
|
+
# Evaluate Object against input data
|
|
1323
|
+
# @param {Object} expr - JSONata expression
|
|
1324
|
+
# @param {Object} input - Input data to evaluate against
|
|
1325
|
+
# @param {Object} environment - Environment
|
|
1326
|
+
# @returns {*} Evaluated input data
|
|
1327
|
+
#
|
|
1328
|
+
# async
|
|
1329
|
+
def evaluate_function(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any], environment: Optional[Frame],
|
|
1330
|
+
applyto_context: Optional[Any]) -> Optional[Any]:
|
|
1331
|
+
# this.current is set by getPerThreadInstance() at this point
|
|
1332
|
+
|
|
1333
|
+
# create the procedure
|
|
1334
|
+
# can"t assume that expr.procedure is a lambda type directly
|
|
1335
|
+
# could be an expression that evaluates to a Object (e.g. variable reference, parens expr etc.
|
|
1336
|
+
# evaluate it generically first, then check that it is a function. Throw error if not.
|
|
1337
|
+
proc = self.eval(expr.procedure, input, environment)
|
|
1338
|
+
|
|
1339
|
+
if (proc is None and getattr(expr.procedure, "type", None) is not None and
|
|
1340
|
+
expr.procedure.type == "path" and environment.lookup(str(expr.procedure.steps[0].value)) is not None):
|
|
1341
|
+
# help the user out here if they simply forgot the leading $
|
|
1342
|
+
raise jexception.JException("T1005", expr.position, expr.procedure.steps[0].value)
|
|
1343
|
+
|
|
1344
|
+
evaluated_args = []
|
|
1345
|
+
|
|
1346
|
+
if applyto_context is not utils.Utils.NONE:
|
|
1347
|
+
evaluated_args.append(applyto_context)
|
|
1348
|
+
# eager evaluation - evaluate the arguments
|
|
1349
|
+
args = expr.arguments if expr.arguments is not None else []
|
|
1350
|
+
for val in args:
|
|
1351
|
+
arg = self.eval(val, input, environment)
|
|
1352
|
+
if utils.Utils.is_function(arg) or functions.Functions.is_lambda(arg):
|
|
1353
|
+
# wrap this in a closure
|
|
1354
|
+
# Java: not required, already a JFunction
|
|
1355
|
+
# const closure = /* async */ Object (...params) {
|
|
1356
|
+
# // invoke func
|
|
1357
|
+
# return /* await */ apply(arg, params, null, environment)
|
|
1358
|
+
# }
|
|
1359
|
+
# closure.arity = getFunctionArity(arg)
|
|
1360
|
+
|
|
1361
|
+
# JFunctionCallable fc = (ctx,params) ->
|
|
1362
|
+
# apply(arg, params, null, environment)
|
|
1363
|
+
|
|
1364
|
+
# JFunction cl = new JFunction(fc, "<o:o>")
|
|
1365
|
+
|
|
1366
|
+
# Object cl = apply(arg, params, null, environment)
|
|
1367
|
+
evaluated_args.append(arg)
|
|
1368
|
+
else:
|
|
1369
|
+
evaluated_args.append(arg)
|
|
1370
|
+
# apply the procedure
|
|
1371
|
+
proc_val = expr.procedure.value if expr.procedure is not None else None
|
|
1372
|
+
proc_name = expr.procedure.steps[0].value if getattr(expr.procedure, "type",
|
|
1373
|
+
None) is not None and expr.procedure.type == "path" else proc_val
|
|
1374
|
+
|
|
1375
|
+
# Error if proc is null
|
|
1376
|
+
if proc is None:
|
|
1377
|
+
raise jexception.JException("T1006", expr.position, proc_name)
|
|
1378
|
+
|
|
1379
|
+
try:
|
|
1380
|
+
if isinstance(proc, parser.Parser.Symbol):
|
|
1381
|
+
proc.token = proc_name
|
|
1382
|
+
proc.position = expr.position
|
|
1383
|
+
result = self.apply(proc, evaluated_args, input, environment)
|
|
1384
|
+
except jexception.JException as jex:
|
|
1385
|
+
if jex.location < 0:
|
|
1386
|
+
# add the position field to the error
|
|
1387
|
+
jex.location = expr.position
|
|
1388
|
+
if jex.current is None:
|
|
1389
|
+
# and the Object identifier
|
|
1390
|
+
jex.current = expr.token
|
|
1391
|
+
raise jex
|
|
1392
|
+
except Exception as err:
|
|
1393
|
+
raise err
|
|
1394
|
+
return result
|
|
1395
|
+
|
|
1396
|
+
#
|
|
1397
|
+
# Apply procedure or function
|
|
1398
|
+
# @param {Object} proc - Procedure
|
|
1399
|
+
# @param {Array} args - Arguments
|
|
1400
|
+
# @param {Object} input - input
|
|
1401
|
+
# @param {Object} environment - environment
|
|
1402
|
+
# @returns {*} Result of procedure
|
|
1403
|
+
#
|
|
1404
|
+
# async
|
|
1405
|
+
def apply(self, proc: Optional[Any], args: Optional[Any], input: Optional[Any], environment: Optional[Frame]) -> Optional[Any]:
|
|
1406
|
+
result = self.apply_inner(proc, args, input, environment)
|
|
1407
|
+
while functions.Functions.is_lambda(result) and result.thunk:
|
|
1408
|
+
# trampoline loop - this gets invoked as a result of tail-call optimization
|
|
1409
|
+
# the Object returned a tail-call thunk
|
|
1410
|
+
# unpack it, evaluate its arguments, and apply the tail call
|
|
1411
|
+
next = self.eval(result.body.procedure, result.input, result.environment)
|
|
1412
|
+
if result.body.procedure.type == "variable":
|
|
1413
|
+
if isinstance(next, parser.Parser.Symbol): # Java: not if JFunction
|
|
1414
|
+
next.token = result.body.procedure.value
|
|
1415
|
+
if isinstance(next, parser.Parser.Symbol): # Java: not if JFunction
|
|
1416
|
+
next.position = result.body.procedure.position
|
|
1417
|
+
evaluated_args = []
|
|
1418
|
+
for arg in result.body.arguments:
|
|
1419
|
+
evaluated_args.append(self.eval(arg, result.input, result.environment))
|
|
1420
|
+
|
|
1421
|
+
result = self.apply_inner(next, evaluated_args, input, environment)
|
|
1422
|
+
return result
|
|
1423
|
+
|
|
1424
|
+
#
|
|
1425
|
+
# Apply procedure or function
|
|
1426
|
+
# @param {Object} proc - Procedure
|
|
1427
|
+
# @param {Array} args - Arguments
|
|
1428
|
+
# @param {Object} input - input
|
|
1429
|
+
# @param {Object} environment - environment
|
|
1430
|
+
# @returns {*} Result of procedure
|
|
1431
|
+
#
|
|
1432
|
+
# async
|
|
1433
|
+
def apply_inner(self, proc: Optional[Any], args: Optional[Any], input: Optional[Any],
|
|
1434
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1435
|
+
try:
|
|
1436
|
+
validated_args = args
|
|
1437
|
+
if proc is not None:
|
|
1438
|
+
validated_args = self.validate_arguments(proc, args, input)
|
|
1439
|
+
|
|
1440
|
+
if functions.Functions.is_lambda(proc):
|
|
1441
|
+
result = self.apply_procedure(proc,
|
|
1442
|
+
validated_args) # FIXME: need in Java??? else if (proc && proc._jsonata_Object == true) {
|
|
1443
|
+
# var focus = {
|
|
1444
|
+
# environment: environment,
|
|
1445
|
+
# input: input
|
|
1446
|
+
# }
|
|
1447
|
+
# // the `focus` is passed in as the `this` for the invoked function
|
|
1448
|
+
# result = proc.implementation.apply(focus, validated_args)
|
|
1449
|
+
# // `proc.implementation` might be a generator function
|
|
1450
|
+
# // and `result` might be a generator - if so, yield
|
|
1451
|
+
# if (isIterable(result)) {
|
|
1452
|
+
# result = result.next().value
|
|
1453
|
+
# }
|
|
1454
|
+
# if (isPromise(result)) {
|
|
1455
|
+
# result = /await/ result
|
|
1456
|
+
# }
|
|
1457
|
+
# }
|
|
1458
|
+
elif isinstance(proc, Jsonata.JFunction):
|
|
1459
|
+
# typically these are functions that are returned by the invocation of plugin functions
|
|
1460
|
+
# the `input` is being passed in as the `this` for the invoked function
|
|
1461
|
+
# this is so that functions that return objects containing functions can chain
|
|
1462
|
+
# e.g. /* await */ (/* await */ $func())
|
|
1463
|
+
|
|
1464
|
+
# handling special case of Javascript:
|
|
1465
|
+
# when calling a function with fn.apply(ctx, args) and args = [undefined]
|
|
1466
|
+
# Javascript will convert to undefined (without array)
|
|
1467
|
+
if isinstance(validated_args, list) and len(validated_args) == 1 and validated_args[0] is None:
|
|
1468
|
+
# validated_args = null
|
|
1469
|
+
pass
|
|
1470
|
+
|
|
1471
|
+
result = proc.call(input, validated_args)
|
|
1472
|
+
# if (isPromise(result)) {
|
|
1473
|
+
# result = /* await */ result
|
|
1474
|
+
# }
|
|
1475
|
+
elif isinstance(proc, Jsonata.JLambda):
|
|
1476
|
+
result = proc.call(input, validated_args)
|
|
1477
|
+
elif isinstance(proc, re.Pattern):
|
|
1478
|
+
result = [s for s in validated_args if proc.search(s) is not None]
|
|
1479
|
+
else:
|
|
1480
|
+
print("Proc not found " + str(proc))
|
|
1481
|
+
raise jexception.JException("T1006", 0)
|
|
1482
|
+
except jexception.JException as err:
|
|
1483
|
+
# if(proc) {
|
|
1484
|
+
# if (typeof err.token == "undefined" && typeof proc.token !== "undefined") {
|
|
1485
|
+
# err.token = proc.token
|
|
1486
|
+
# }
|
|
1487
|
+
# err.position = proc.position
|
|
1488
|
+
# }
|
|
1489
|
+
raise err
|
|
1490
|
+
return result
|
|
1491
|
+
|
|
1492
|
+
#
|
|
1493
|
+
# Evaluate lambda against input data
|
|
1494
|
+
# @param {Object} expr - JSONata expression
|
|
1495
|
+
# @param {Object} input - Input data to evaluate against
|
|
1496
|
+
# @param {Object} environment - Environment
|
|
1497
|
+
# @returns {{lambda: boolean, input: *, environment: *, arguments: *, body: *}} Evaluated input data
|
|
1498
|
+
#
|
|
1499
|
+
def evaluate_lambda(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1500
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1501
|
+
# make a Object (closure)
|
|
1502
|
+
procedure = parser.Parser.Symbol(self.parser)
|
|
1503
|
+
|
|
1504
|
+
procedure._jsonata_lambda = True
|
|
1505
|
+
procedure.input = input
|
|
1506
|
+
procedure.environment = environment
|
|
1507
|
+
procedure.arguments = expr.arguments
|
|
1508
|
+
procedure.signature = expr.signature
|
|
1509
|
+
procedure.body = expr.body
|
|
1510
|
+
|
|
1511
|
+
if expr.thunk:
|
|
1512
|
+
procedure.thunk = True
|
|
1513
|
+
|
|
1514
|
+
# procedure.apply = /* async */ function(self, args) {
|
|
1515
|
+
# return /* await */ apply(procedure, args, input, !!self ? self.environment : environment)
|
|
1516
|
+
# }
|
|
1517
|
+
return procedure
|
|
1518
|
+
|
|
1519
|
+
#
|
|
1520
|
+
# Evaluate partial application
|
|
1521
|
+
# @param {Object} expr - JSONata expression
|
|
1522
|
+
# @param {Object} input - Input data to evaluate against
|
|
1523
|
+
# @param {Object} environment - Environment
|
|
1524
|
+
# @returns {*} Evaluated input data
|
|
1525
|
+
#
|
|
1526
|
+
# async
|
|
1527
|
+
def evaluate_partial_application(self, expr: Optional[parser.Parser.Symbol], input: Optional[Any],
|
|
1528
|
+
environment: Optional[Frame]) -> Optional[Any]:
|
|
1529
|
+
# partially apply a function
|
|
1530
|
+
result = None
|
|
1531
|
+
# evaluate the arguments
|
|
1532
|
+
evaluated_args = []
|
|
1533
|
+
for arg in expr.arguments:
|
|
1534
|
+
if arg.type == "operator" and (arg.value == "?"):
|
|
1535
|
+
evaluated_args.append(arg)
|
|
1536
|
+
else:
|
|
1537
|
+
evaluated_args.append(self.eval(arg, input, environment))
|
|
1538
|
+
# lookup the procedure
|
|
1539
|
+
proc = self.eval(expr.procedure, input, environment)
|
|
1540
|
+
if proc is not None and expr.procedure.type == "path" and environment.lookup(
|
|
1541
|
+
str(expr.procedure.steps[0].value)) is not None:
|
|
1542
|
+
# help the user out here if they simply forgot the leading $
|
|
1543
|
+
raise jexception.JException("T1007", expr.position, expr.procedure.steps[0].value)
|
|
1544
|
+
if functions.Functions.is_lambda(proc):
|
|
1545
|
+
result = self.partial_apply_procedure(proc, evaluated_args)
|
|
1546
|
+
elif utils.Utils.is_function(proc):
|
|
1547
|
+
result = self.partial_apply_native_function(proc, evaluated_args)
|
|
1548
|
+
# } else if (typeof proc === "function") {
|
|
1549
|
+
# result = partialApplyNativeFunction(proc, evaluated_args)
|
|
1550
|
+
else:
|
|
1551
|
+
raise jexception.JException("T1008", expr.position, expr.procedure.steps[
|
|
1552
|
+
0].value if expr.procedure.type == "path" else expr.procedure.value)
|
|
1553
|
+
return result
|
|
1554
|
+
|
|
1555
|
+
#
|
|
1556
|
+
# Validate the arguments against the signature validator (if it exists)
|
|
1557
|
+
# @param {Function} signature - validator function
|
|
1558
|
+
# @param {Array} args - Object arguments
|
|
1559
|
+
# @param {*} context - context value
|
|
1560
|
+
# @returns {Array} - validated arguments
|
|
1561
|
+
#
|
|
1562
|
+
def validate_arguments(self, signature: Any, args: Optional[Any], context: Optional[Any]) -> Optional[Any]:
|
|
1563
|
+
validated_args = args
|
|
1564
|
+
if utils.Utils.is_function(signature):
|
|
1565
|
+
validated_args = signature.validate(args, context)
|
|
1566
|
+
elif functions.Functions.is_lambda(signature):
|
|
1567
|
+
sig = signature.signature
|
|
1568
|
+
if sig is not None:
|
|
1569
|
+
validated_args = sig.validate(args, context)
|
|
1570
|
+
return validated_args
|
|
1571
|
+
|
|
1572
|
+
#
|
|
1573
|
+
# Apply procedure
|
|
1574
|
+
# @param {Object} proc - Procedure
|
|
1575
|
+
# @param {Array} args - Arguments
|
|
1576
|
+
# @returns {*} Result of procedure
|
|
1577
|
+
#
|
|
1578
|
+
# async
|
|
1579
|
+
def apply_procedure(self, proc: Optional[Any], args: Optional[Any]) -> Optional[Any]:
|
|
1580
|
+
result = None
|
|
1581
|
+
env = self.create_frame(proc.environment)
|
|
1582
|
+
for i, arg in enumerate(proc.arguments):
|
|
1583
|
+
if i >= len(args):
|
|
1584
|
+
break
|
|
1585
|
+
env.bind(str(arg.value), args[i])
|
|
1586
|
+
if isinstance(proc.body, parser.Parser.Symbol):
|
|
1587
|
+
result = self.eval(proc.body, proc.input, env)
|
|
1588
|
+
else:
|
|
1589
|
+
raise RuntimeError("Cannot execute procedure: " + proc + " " + proc.body)
|
|
1590
|
+
# if (typeof proc.body === "function") {
|
|
1591
|
+
# // this is a lambda that wraps a native Object - generated by partially evaluating a native
|
|
1592
|
+
# result = /* await */ applyNativeFunction(proc.body, env)
|
|
1593
|
+
return result
|
|
1594
|
+
|
|
1595
|
+
#
|
|
1596
|
+
# Partially apply procedure
|
|
1597
|
+
# @param {Object} proc - Procedure
|
|
1598
|
+
# @param {Array} args - Arguments
|
|
1599
|
+
# @returns {{lambda: boolean, input: *, environment: {bind, lookup}, arguments: Array, body: *}} Result of partially applied procedure
|
|
1600
|
+
#
|
|
1601
|
+
def partial_apply_procedure(self, proc: Optional[parser.Parser.Symbol], args: Sequence) -> parser.Parser.Symbol:
|
|
1602
|
+
# create a closure, bind the supplied parameters and return a Object that takes the remaining (?) parameters
|
|
1603
|
+
# Note Uli: if no env, bind to default env so the native functions can be found
|
|
1604
|
+
env = self.create_frame(proc.environment if proc.environment is not None else self.environment)
|
|
1605
|
+
unbound_args = []
|
|
1606
|
+
index = 0
|
|
1607
|
+
for param in proc.arguments:
|
|
1608
|
+
# proc.arguments.forEach(Object (param, index) {
|
|
1609
|
+
arg = args[index] if index < len(args) else None
|
|
1610
|
+
if (arg is None) or (
|
|
1611
|
+
isinstance(arg, parser.Parser.Symbol) and ("operator" == arg.type and "?" == arg.value)):
|
|
1612
|
+
unbound_args.append(param)
|
|
1613
|
+
else:
|
|
1614
|
+
env.bind(str(param.value), arg)
|
|
1615
|
+
index += 1
|
|
1616
|
+
procedure = parser.Parser.Symbol(self.parser)
|
|
1617
|
+
procedure._jsonata_lambda = True
|
|
1618
|
+
procedure.input = proc.input
|
|
1619
|
+
procedure.environment = env
|
|
1620
|
+
procedure.arguments = unbound_args
|
|
1621
|
+
procedure.body = proc.body
|
|
1622
|
+
|
|
1623
|
+
return procedure
|
|
1624
|
+
|
|
1625
|
+
#
|
|
1626
|
+
# Partially apply native function
|
|
1627
|
+
# @param {Function} native - Native function
|
|
1628
|
+
# @param {Array} args - Arguments
|
|
1629
|
+
# @returns {{lambda: boolean, input: *, environment: {bind, lookup}, arguments: Array, body: *}} Result of partially applying native function
|
|
1630
|
+
#
|
|
1631
|
+
def partial_apply_native_function(self, native: Optional[JFunction], args: Sequence) -> parser.Parser.Symbol:
|
|
1632
|
+
# create a lambda Object that wraps and invokes the native function
|
|
1633
|
+
# get the list of declared arguments from the native function
|
|
1634
|
+
# this has to be picked out from the toString() value
|
|
1635
|
+
|
|
1636
|
+
# var body = "function($a,$c) { $substring($a,0,$c) }"
|
|
1637
|
+
|
|
1638
|
+
sig_args = []
|
|
1639
|
+
part_args = []
|
|
1640
|
+
i = 0
|
|
1641
|
+
while i < native.get_number_of_args():
|
|
1642
|
+
arg_name = "$" + chr(ord('a') + i)
|
|
1643
|
+
sig_args.append(arg_name)
|
|
1644
|
+
if i >= len(args) or args[i] is None:
|
|
1645
|
+
part_args.append(arg_name)
|
|
1646
|
+
else:
|
|
1647
|
+
part_args.append(args[i])
|
|
1648
|
+
i += 1
|
|
1649
|
+
|
|
1650
|
+
body = "function(" + ", ".join(sig_args) + "){"
|
|
1651
|
+
body += "$" + native.function_name + "(" + ", ".join(sig_args) + ") }"
|
|
1652
|
+
|
|
1653
|
+
if self.parser.dbg:
|
|
1654
|
+
print("partial trampoline = " + body)
|
|
1655
|
+
|
|
1656
|
+
# var sig_args = getNativeFunctionArguments(_native)
|
|
1657
|
+
# sig_args = sig_args.stream().map(sigArg -> {
|
|
1658
|
+
# return "$" + sigArg
|
|
1659
|
+
# }).toList()
|
|
1660
|
+
# var body = "function(" + String.join(", ", sig_args) + "){ _ }"
|
|
1661
|
+
|
|
1662
|
+
body_ast = self.parser.parse(body)
|
|
1663
|
+
# body_ast.body = _native
|
|
1664
|
+
|
|
1665
|
+
partial = self.partial_apply_procedure(body_ast, args)
|
|
1666
|
+
return partial
|
|
1667
|
+
|
|
1668
|
+
#
|
|
1669
|
+
# Apply native function
|
|
1670
|
+
# @param {Object} proc - Procedure
|
|
1671
|
+
# @param {Object} env - Environment
|
|
1672
|
+
# @returns {*} Result of applying native function
|
|
1673
|
+
#
|
|
1674
|
+
# async
|
|
1675
|
+
def apply_native_function(self, proc: Optional[JFunction], env: Optional[Frame]) -> Optional[Any]:
|
|
1676
|
+
# Not called in Java - JFunction call directly calls native function
|
|
1677
|
+
return None
|
|
1678
|
+
|
|
1679
|
+
#
|
|
1680
|
+
# Get native Object arguments
|
|
1681
|
+
# @param {Function} func - Function
|
|
1682
|
+
# @returns {*|Array} Native Object arguments
|
|
1683
|
+
#
|
|
1684
|
+
def get_native_function_arguments(self, func: Optional[JFunction]) -> Optional[list]:
|
|
1685
|
+
# Not called in Java
|
|
1686
|
+
return None
|
|
1687
|
+
|
|
1688
|
+
#
|
|
1689
|
+
# Creates a Object definition
|
|
1690
|
+
# @param {Function} func - Object implementation in Javascript
|
|
1691
|
+
# @param {string} signature - JSONata Object signature definition
|
|
1692
|
+
# @returns {{implementation: *, signature: *}} Object definition
|
|
1693
|
+
#
|
|
1694
|
+
@staticmethod
|
|
1695
|
+
def define_function(func: str, signature: Optional[str], func_impl_method: Optional[str] = None) -> JFunction:
|
|
1696
|
+
fn = Jsonata.JNativeFunction(func, signature, functions.Functions, func_impl_method)
|
|
1697
|
+
Jsonata.static_frame.bind(func, fn)
|
|
1698
|
+
return fn
|
|
1699
|
+
|
|
1700
|
+
@staticmethod
|
|
1701
|
+
def function(name: str, signature: Optional[str], clazz: Optional[Any], method_name: str) -> JFunction:
|
|
1702
|
+
return Jsonata.JNativeFunction(name, signature, clazz, method_name)
|
|
1703
|
+
|
|
1704
|
+
#
|
|
1705
|
+
# parses and evaluates the supplied expression
|
|
1706
|
+
# @param {string} expr - expression to evaluate
|
|
1707
|
+
# @returns {*} - result of evaluating the expression
|
|
1708
|
+
#
|
|
1709
|
+
# async
|
|
1710
|
+
# Object functionEval(String expr, Object focus) {
|
|
1711
|
+
# moved to functions.Functions !
|
|
1712
|
+
# }
|
|
1713
|
+
|
|
1714
|
+
#
|
|
1715
|
+
# Clones an object
|
|
1716
|
+
# @param {Object} arg - object to clone (deep copy)
|
|
1717
|
+
# @returns {*} - the cloned object
|
|
1718
|
+
#
|
|
1719
|
+
# Object functionClone(Object arg) {
|
|
1720
|
+
# moved to functions.Functions !
|
|
1721
|
+
# }
|
|
1722
|
+
|
|
1723
|
+
#
|
|
1724
|
+
# Create frame
|
|
1725
|
+
# @param {Object} enclosingEnvironment - Enclosing environment
|
|
1726
|
+
# @returns {{bind: bind, lookup: lookup}} Created frame
|
|
1727
|
+
#
|
|
1728
|
+
def create_frame(self, enclosing_environment: Optional[Frame] = None) -> Frame:
|
|
1729
|
+
return Jsonata.Frame(enclosing_environment)
|
|
1730
|
+
|
|
1731
|
+
# The following logic is in class Frame:
|
|
1732
|
+
# var bindings = {}
|
|
1733
|
+
# return {
|
|
1734
|
+
# bind: Object (name, value) {
|
|
1735
|
+
# bindings[name] = value
|
|
1736
|
+
# },
|
|
1737
|
+
# lookup: Object (name) {
|
|
1738
|
+
# var value
|
|
1739
|
+
# if(bindings.hasOwnProperty(name)) {
|
|
1740
|
+
# value = bindings[name]
|
|
1741
|
+
# } else if (enclosingEnvironment) {
|
|
1742
|
+
# value = enclosingEnvironment.lookup(name)
|
|
1743
|
+
# }
|
|
1744
|
+
# return value
|
|
1745
|
+
# },
|
|
1746
|
+
# timestamp: enclosingEnvironment ? enclosingEnvironment.timestamp : null,
|
|
1747
|
+
# async: enclosingEnvironment ? enclosingEnvironment./* async */ : false,
|
|
1748
|
+
# isParallelCall: enclosingEnvironment ? enclosingEnvironment.isParallelCall : false,
|
|
1749
|
+
# global: enclosingEnvironment ? enclosingEnvironment.global : {
|
|
1750
|
+
# ancestry: [ null ]
|
|
1751
|
+
# }
|
|
1752
|
+
# }
|
|
1753
|
+
|
|
1754
|
+
# Function registration
|
|
1755
|
+
@staticmethod
|
|
1756
|
+
def register_functions() -> None:
|
|
1757
|
+
Jsonata.define_function("sum", "<a<n>:n>")
|
|
1758
|
+
Jsonata.define_function("count", "<a:n>")
|
|
1759
|
+
Jsonata.define_function("max", "<a<n>:n>")
|
|
1760
|
+
Jsonata.define_function("min", "<a<n>:n>")
|
|
1761
|
+
Jsonata.define_function("average", "<a<n>:n>")
|
|
1762
|
+
Jsonata.define_function("string", "<x-b?:s>")
|
|
1763
|
+
Jsonata.define_function("substring", "<s-nn?:s>")
|
|
1764
|
+
Jsonata.define_function("substringBefore", "<s-s:s>", "substring_before")
|
|
1765
|
+
Jsonata.define_function("substringAfter", "<s-s:s>", "substring_after")
|
|
1766
|
+
Jsonata.define_function("lowercase", "<s-:s>")
|
|
1767
|
+
Jsonata.define_function("uppercase", "<s-:s>")
|
|
1768
|
+
Jsonata.define_function("length", "<s-:n>")
|
|
1769
|
+
Jsonata.define_function("trim", "<s-:s>")
|
|
1770
|
+
Jsonata.define_function("pad", "<s-ns?:s>")
|
|
1771
|
+
Jsonata.define_function("match", "<s-f<s:o>n?:a<o>>", "match_")
|
|
1772
|
+
Jsonata.define_function("contains", "<s-(sf):b>") # TODO <s-(sf<s:o>):b>
|
|
1773
|
+
Jsonata.define_function("replace", "<s-(sf)(sf)n?:s>") # TODO <s-(sf<s:o>)(sf<o:s>)n?:s>
|
|
1774
|
+
Jsonata.define_function("split", "<s-(sf)n?:a<s>>") # TODO <s-(sf<s:o>)n?:a<s>>
|
|
1775
|
+
Jsonata.define_function("join", "<a<s>s?:s>")
|
|
1776
|
+
Jsonata.define_function("formatNumber", "<n-so?:s>", "format_number")
|
|
1777
|
+
Jsonata.define_function("formatBase", "<n-n?:s>", "format_base")
|
|
1778
|
+
Jsonata.define_function("formatInteger", "<n-s:s>", "format_integer")
|
|
1779
|
+
Jsonata.define_function("parseInteger", "<s-s:n>", "parse_integer")
|
|
1780
|
+
Jsonata.define_function("number", "<(nsb)-:n>")
|
|
1781
|
+
Jsonata.define_function("floor", "<n-:n>")
|
|
1782
|
+
Jsonata.define_function("ceil", "<n-:n>")
|
|
1783
|
+
Jsonata.define_function("round", "<n-n?:n>")
|
|
1784
|
+
Jsonata.define_function("abs", "<n-:n>")
|
|
1785
|
+
Jsonata.define_function("sqrt", "<n-:n>")
|
|
1786
|
+
Jsonata.define_function("power", "<n-n:n>")
|
|
1787
|
+
Jsonata.define_function("random", "<:n>")
|
|
1788
|
+
Jsonata.define_function("boolean", "<x-:b>", "to_boolean")
|
|
1789
|
+
Jsonata.define_function("not", "<x-:b>", "not_")
|
|
1790
|
+
Jsonata.define_function("map", "<af>")
|
|
1791
|
+
Jsonata.define_function("zip", "<a+>")
|
|
1792
|
+
Jsonata.define_function("filter", "<af>")
|
|
1793
|
+
Jsonata.define_function("single", "<af?>")
|
|
1794
|
+
Jsonata.define_function("reduce", "<afj?:j>", "fold_left") # TODO <f<jj:j>a<j>j?:j>
|
|
1795
|
+
Jsonata.define_function("sift", "<o-f?:o>")
|
|
1796
|
+
Jsonata.define_function("keys", "<x-:a<s>>")
|
|
1797
|
+
Jsonata.define_function("lookup", "<x-s:x>")
|
|
1798
|
+
Jsonata.define_function("append", "<xx:a>")
|
|
1799
|
+
Jsonata.define_function("exists", "<x:b>")
|
|
1800
|
+
Jsonata.define_function("spread", "<x-:a<o>>")
|
|
1801
|
+
Jsonata.define_function("merge", "<a<o>:o>")
|
|
1802
|
+
Jsonata.define_function("reverse", "<a:a>")
|
|
1803
|
+
Jsonata.define_function("each", "<o-f:a>")
|
|
1804
|
+
Jsonata.define_function("error", "<s?:x>")
|
|
1805
|
+
Jsonata.define_function("assert", "<bs?:x>", "assert_fn")
|
|
1806
|
+
Jsonata.define_function("type", "<x:s>")
|
|
1807
|
+
Jsonata.define_function("sort", "<af?:a>")
|
|
1808
|
+
Jsonata.define_function("shuffle", "<a:a>")
|
|
1809
|
+
Jsonata.define_function("distinct", "<x:x>")
|
|
1810
|
+
Jsonata.define_function("base64encode", "<s-:s>")
|
|
1811
|
+
Jsonata.define_function("base64decode", "<s-:s>")
|
|
1812
|
+
Jsonata.define_function("encodeUrlComponent", "<s-:s>", "encode_url_component")
|
|
1813
|
+
Jsonata.define_function("encodeUrl", "<s-:s>", "encode_url")
|
|
1814
|
+
Jsonata.define_function("decodeUrlComponent", "<s-:s>", "decode_url_component")
|
|
1815
|
+
Jsonata.define_function("decodeUrl", "<s-:s>", "decode_url")
|
|
1816
|
+
Jsonata.define_function("eval", "<sx?:x>", "function_eval")
|
|
1817
|
+
Jsonata.define_function("toMillis", "<s-s?:n>", "datetime_to_millis")
|
|
1818
|
+
Jsonata.define_function("fromMillis", "<n-s?s?:s>", "datetime_from_millis")
|
|
1819
|
+
Jsonata.define_function("clone", "<(oa)-:o>", "function_clone")
|
|
1820
|
+
|
|
1821
|
+
Jsonata.define_function("now", "<s?s?:s>")
|
|
1822
|
+
Jsonata.define_function("millis", "<:n>")
|
|
1823
|
+
|
|
1824
|
+
# environment.bind("now", defineFunction(function(picture, timezone) {
|
|
1825
|
+
# return datetime.fromMillis(timestamp.getTime(), picture, timezone)
|
|
1826
|
+
# }, "<s?s?:s>"))
|
|
1827
|
+
# environment.bind("millis", defineFunction(function() {
|
|
1828
|
+
# return timestamp.getTime()
|
|
1829
|
+
# }, "<:n>"))
|
|
1830
|
+
|
|
1831
|
+
#
|
|
1832
|
+
# lookup a message template from the catalog and substitute the inserts.
|
|
1833
|
+
# Populates `err.message` with the substituted message. Leaves `err.message`
|
|
1834
|
+
# untouched if code lookup fails.
|
|
1835
|
+
# @param {string} err - error code to lookup
|
|
1836
|
+
# @returns {undefined} - `err` is modified in place
|
|
1837
|
+
#
|
|
1838
|
+
def populate_message(self, err: Exception) -> Exception:
|
|
1839
|
+
# var template = errorCodes[err.code]
|
|
1840
|
+
# if(typeof template !== "undefined") {
|
|
1841
|
+
# // if there are any handlebars, replace them with the field references
|
|
1842
|
+
# // triple braces - replace with value
|
|
1843
|
+
# // double braces - replace with json stringified value
|
|
1844
|
+
# var message = template.replace(/\{\{\{([^}]+)}}}/g, function() {
|
|
1845
|
+
# return err[arguments[1]]
|
|
1846
|
+
# })
|
|
1847
|
+
# message = message.replace(/\{\{([^}]+)}}/g, function() {
|
|
1848
|
+
# return JSON.stringify(err[arguments[1]])
|
|
1849
|
+
# })
|
|
1850
|
+
# err.message = message
|
|
1851
|
+
# }
|
|
1852
|
+
# Otherwise retain the original `err.message`
|
|
1853
|
+
return err
|
|
1854
|
+
|
|
1855
|
+
@staticmethod
|
|
1856
|
+
def _static_initializer() -> None:
|
|
1857
|
+
Jsonata.static_frame = Jsonata.Frame(None)
|
|
1858
|
+
Jsonata.register_functions()
|
|
1859
|
+
|
|
1860
|
+
# set system recursion limit to 10K (similar to JavaScript)
|
|
1861
|
+
sys.setrecursionlimit(10000)
|
|
1862
|
+
|
|
1863
|
+
#
|
|
1864
|
+
# JSONata
|
|
1865
|
+
# @param {Object} expr - JSONata expression
|
|
1866
|
+
# @returns Evaluated expression
|
|
1867
|
+
# @throws jexception.JException An exception if an error occured.
|
|
1868
|
+
#
|
|
1869
|
+
@staticmethod
|
|
1870
|
+
def jsonata(expression: Optional[str]) -> 'Jsonata':
|
|
1871
|
+
return Jsonata(expression)
|
|
1872
|
+
|
|
1873
|
+
#
|
|
1874
|
+
# Internal constructor
|
|
1875
|
+
# @param expr
|
|
1876
|
+
#
|
|
1877
|
+
|
|
1878
|
+
parser: parser.Parser
|
|
1879
|
+
errors: Optional[Sequence[Exception]]
|
|
1880
|
+
environment: Frame
|
|
1881
|
+
ast: Optional[parser.Parser.Symbol]
|
|
1882
|
+
timestamp: int
|
|
1883
|
+
input: Optional[Any]
|
|
1884
|
+
|
|
1885
|
+
def __init__(self, expr: Optional[str]) -> None:
|
|
1886
|
+
try:
|
|
1887
|
+
self.parser = Jsonata.get_parser()
|
|
1888
|
+
self.ast = self.parser.parse(expr) # , optionsRecover);
|
|
1889
|
+
self.errors = self.ast.errors
|
|
1890
|
+
self.ast.errors = None # delete ast.errors;
|
|
1891
|
+
except jexception.JException as err:
|
|
1892
|
+
# insert error message into structure
|
|
1893
|
+
# populateMessage(err); // possible side-effects on `err`
|
|
1894
|
+
raise err
|
|
1895
|
+
self.environment = self.create_frame(Jsonata.static_frame)
|
|
1896
|
+
|
|
1897
|
+
self.timestamp = timebox.Timebox.current_milli_time() # will be overridden on each call to evalute()
|
|
1898
|
+
|
|
1899
|
+
self.input = None
|
|
1900
|
+
self.validate_input = True
|
|
1901
|
+
|
|
1902
|
+
# Note: now and millis are implemented in Functions
|
|
1903
|
+
# environment.bind("now", defineFunction(function(picture, timezone) {
|
|
1904
|
+
# return datetime.fromMillis(timestamp.getTime(), picture, timezone)
|
|
1905
|
+
# }, "<s?s?:s>"))
|
|
1906
|
+
# environment.bind("millis", defineFunction(function() {
|
|
1907
|
+
# return timestamp.getTime()
|
|
1908
|
+
# }, "<:n>"))
|
|
1909
|
+
|
|
1910
|
+
# FIXED: options.RegexEngine not implemented in Java
|
|
1911
|
+
# if(options && options.RegexEngine) {
|
|
1912
|
+
# jsonata.RegexEngine = options.RegexEngine
|
|
1913
|
+
# } else {
|
|
1914
|
+
# jsonata.RegexEngine = RegExp
|
|
1915
|
+
# }
|
|
1916
|
+
|
|
1917
|
+
# Set instance for this thread
|
|
1918
|
+
Jsonata.CURRENT.jsonata = self
|
|
1919
|
+
|
|
1920
|
+
#
|
|
1921
|
+
# Flag: validate input objects to comply with JSON types
|
|
1922
|
+
#
|
|
1923
|
+
|
|
1924
|
+
#
|
|
1925
|
+
# Checks whether input validation is active
|
|
1926
|
+
#
|
|
1927
|
+
def is_validate_input(self) -> bool:
|
|
1928
|
+
return self.validate_input
|
|
1929
|
+
|
|
1930
|
+
#
|
|
1931
|
+
# Enable or disable input validation
|
|
1932
|
+
# @param validateInput
|
|
1933
|
+
#
|
|
1934
|
+
def set_validate_input(self, validate_input: bool) -> None:
|
|
1935
|
+
self.validate_input = validate_input
|
|
1936
|
+
|
|
1937
|
+
def evaluate(self, input: Optional[Any], bindings: Optional[Frame] = None) -> Optional[Any]:
|
|
1938
|
+
# throw if the expression compiled with syntax errors
|
|
1939
|
+
if self.errors is not None:
|
|
1940
|
+
raise jexception.JException("S0500", 0)
|
|
1941
|
+
|
|
1942
|
+
exec_env = None
|
|
1943
|
+
if bindings is not None:
|
|
1944
|
+
# var exec_env
|
|
1945
|
+
# the variable bindings have been passed in - create a frame to hold these
|
|
1946
|
+
exec_env = self.create_frame(self.environment)
|
|
1947
|
+
for k, v in bindings.bindings.items():
|
|
1948
|
+
exec_env.bind(k, v)
|
|
1949
|
+
else:
|
|
1950
|
+
exec_env = self.environment
|
|
1951
|
+
# put the input document into the environment as the root object
|
|
1952
|
+
exec_env.bind("$", input)
|
|
1953
|
+
|
|
1954
|
+
# capture the timestamp and put it in the execution environment
|
|
1955
|
+
# the $now() and $millis() functions will return this value - whenever it is called
|
|
1956
|
+
self.timestamp = timebox.Timebox.current_milli_time()
|
|
1957
|
+
# exec_env.timestamp = timestamp
|
|
1958
|
+
|
|
1959
|
+
# if the input is a JSON array, then wrap it in a singleton sequence so it gets treated as a single input
|
|
1960
|
+
if (isinstance(input, list)) and not utils.Utils.is_sequence(input):
|
|
1961
|
+
input = utils.Utils.create_sequence(input)
|
|
1962
|
+
input.outer_wrapper = True
|
|
1963
|
+
|
|
1964
|
+
if self.validate_input:
|
|
1965
|
+
functions.Functions.validate_input(input)
|
|
1966
|
+
|
|
1967
|
+
it = None
|
|
1968
|
+
try:
|
|
1969
|
+
it = self.eval(self.ast, input, exec_env)
|
|
1970
|
+
# if (typeof callback === "function") {
|
|
1971
|
+
# callback(null, it)
|
|
1972
|
+
# }
|
|
1973
|
+
it = utils.Utils.convert_nulls(it)
|
|
1974
|
+
return it
|
|
1975
|
+
except Exception as err:
|
|
1976
|
+
# insert error message into structure
|
|
1977
|
+
self.populate_message(err) # possible side-effects on `err`
|
|
1978
|
+
raise err
|
|
1979
|
+
|
|
1980
|
+
def assign(self, name: str, value: Optional[Any]) -> None:
|
|
1981
|
+
self.environment.bind(name, value)
|
|
1982
|
+
|
|
1983
|
+
def register_lambda(self, name: str, implementation: Callable) -> None:
|
|
1984
|
+
self.environment.bind(name, Jsonata.JLambda(implementation))
|
|
1985
|
+
|
|
1986
|
+
def register_function(self, name: str, function: Any) -> None:
|
|
1987
|
+
self.environment.bind(name, function)
|
|
1988
|
+
|
|
1989
|
+
def get_errors(self) -> Optional[list[Exception]]:
|
|
1990
|
+
return self.errors
|
|
1991
|
+
|
|
1992
|
+
PARSER = threading.local()
|
|
1993
|
+
|
|
1994
|
+
@staticmethod
|
|
1995
|
+
def get_parser() -> parser.Parser:
|
|
1996
|
+
with Jsonata.MUTEX:
|
|
1997
|
+
if hasattr(Jsonata.PARSER, "parser"):
|
|
1998
|
+
return Jsonata.PARSER.parser
|
|
1999
|
+
p = parser.Parser()
|
|
2000
|
+
Jsonata.PARSER.parser = p
|
|
2001
|
+
return p
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
Jsonata._static_initializer()
|