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/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()