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