json_p3 0.4.0 → 1.0.0

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +26 -7
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +58 -0
  6. data/README.md +125 -123
  7. data/Rakefile +3 -3
  8. data/certs/jgrp.pem +21 -21
  9. data/lib/json_p3/errors.rb +51 -43
  10. data/lib/json_p3/patch/op.rb +23 -0
  11. data/lib/json_p3/patch/op_add.rb +51 -0
  12. data/lib/json_p3/patch/op_copy.rb +64 -0
  13. data/lib/json_p3/patch/op_move.rb +74 -0
  14. data/lib/json_p3/patch/op_remove.rb +56 -0
  15. data/lib/json_p3/patch/op_replace.rb +54 -0
  16. data/lib/json_p3/patch/op_test.rb +31 -0
  17. data/lib/json_p3/patch.rb +15 -330
  18. data/lib/json_p3/path/environment.rb +113 -0
  19. data/lib/json_p3/path/filter.rb +463 -0
  20. data/lib/json_p3/path/function.rb +12 -0
  21. data/lib/json_p3/path/function_extensions/count.rb +15 -0
  22. data/lib/json_p3/path/function_extensions/length.rb +17 -0
  23. data/lib/json_p3/path/function_extensions/match.rb +62 -0
  24. data/lib/json_p3/path/function_extensions/pattern.rb +42 -0
  25. data/lib/json_p3/path/function_extensions/search.rb +44 -0
  26. data/lib/json_p3/path/function_extensions/value.rb +15 -0
  27. data/lib/json_p3/path/lexer.rb +220 -0
  28. data/lib/json_p3/path/node.rb +48 -0
  29. data/lib/json_p3/path/parser.rb +676 -0
  30. data/lib/json_p3/path/query.rb +74 -0
  31. data/lib/json_p3/path/segment.rb +172 -0
  32. data/lib/json_p3/path/selector.rb +304 -0
  33. data/lib/json_p3/path/serialize.rb +16 -0
  34. data/lib/json_p3/path/unescape.rb +134 -0
  35. data/lib/json_p3/pointer.rb +15 -76
  36. data/lib/json_p3/relative_pointer.rb +69 -0
  37. data/lib/json_p3/version.rb +1 -1
  38. data/lib/json_p3.rb +50 -13
  39. data/sig/json_p3/cache.rbs +21 -0
  40. data/sig/json_p3/errors.rbs +55 -0
  41. data/sig/json_p3/patch.rbs +145 -0
  42. data/sig/json_p3/path/environment.rbs +81 -0
  43. data/sig/json_p3/path/filter.rbs +196 -0
  44. data/sig/json_p3/path/function.rbs +94 -0
  45. data/sig/json_p3/path/lexer.rbs +62 -0
  46. data/sig/json_p3/path/node.rbs +46 -0
  47. data/sig/json_p3/path/parser.rbs +92 -0
  48. data/sig/json_p3/path/query.rbs +47 -0
  49. data/sig/json_p3/path/segment.rbs +54 -0
  50. data/sig/json_p3/path/selector.rbs +100 -0
  51. data/sig/json_p3/path/serialize.rbs +9 -0
  52. data/sig/json_p3/path/unescape.rbs +12 -0
  53. data/sig/json_p3/pointer.rbs +64 -0
  54. data/sig/json_p3/relative_pointer.rbs +30 -0
  55. data/sig/json_p3.rbs +24 -1313
  56. data.tar.gz.sig +0 -0
  57. metadata +66 -46
  58. metadata.gz.sig +0 -0
  59. data/lib/json_p3/environment.rb +0 -111
  60. data/lib/json_p3/filter.rb +0 -459
  61. data/lib/json_p3/function.rb +0 -10
  62. data/lib/json_p3/function_extensions/count.rb +0 -15
  63. data/lib/json_p3/function_extensions/length.rb +0 -17
  64. data/lib/json_p3/function_extensions/match.rb +0 -62
  65. data/lib/json_p3/function_extensions/pattern.rb +0 -39
  66. data/lib/json_p3/function_extensions/search.rb +0 -44
  67. data/lib/json_p3/function_extensions/value.rb +0 -15
  68. data/lib/json_p3/lexer.rb +0 -419
  69. data/lib/json_p3/node.rb +0 -44
  70. data/lib/json_p3/parser.rb +0 -553
  71. data/lib/json_p3/path.rb +0 -72
  72. data/lib/json_p3/segment.rb +0 -158
  73. data/lib/json_p3/selector.rb +0 -306
  74. data/lib/json_p3/serialize.rb +0 -13
  75. data/lib/json_p3/token.rb +0 -36
  76. data/lib/json_p3/unescape.rb +0 -112
@@ -0,0 +1,676 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "filter"
6
+ require_relative "function"
7
+ require_relative "segment"
8
+ require_relative "selector"
9
+ require_relative "unescape"
10
+
11
+ module JSONP3
12
+ # JSONPath query expressions.
13
+ module Path
14
+ # JSONPath query parser.
15
+ class Parser
16
+ class Precedence
17
+ LOWEST = 1
18
+ LOGICAL_OR = 3
19
+ LOGICAL_AND = 4
20
+ RELATIONAL = 5
21
+ PREFIX = 7
22
+ end
23
+
24
+ PRECEDENCES = {
25
+ token_and: Precedence::LOGICAL_AND,
26
+ token_or: Precedence::LOGICAL_OR,
27
+ token_not: Precedence::PREFIX,
28
+ token_eq: Precedence::RELATIONAL,
29
+ token_ge: Precedence::RELATIONAL,
30
+ token_gt: Precedence::RELATIONAL,
31
+ token_le: Precedence::RELATIONAL,
32
+ token_lt: Precedence::RELATIONAL,
33
+ token_ne: Precedence::RELATIONAL,
34
+ token_rparen: Precedence::LOWEST
35
+ }.freeze
36
+
37
+ BINARY_OPERATORS = {
38
+ token_and: "&&",
39
+ token_or: "||",
40
+ token_eq: "==",
41
+ token_ge: ">=",
42
+ token_gt: ">",
43
+ token_le: "<=",
44
+ token_lt: "<",
45
+ token_ne: "!="
46
+ }.freeze
47
+
48
+ COMPARISON_OPERATORS = Set[
49
+ :token_eq,
50
+ :token_ge,
51
+ :token_gt,
52
+ :token_le,
53
+ :token_lt,
54
+ :token_ne
55
+ ]
56
+
57
+ # @param env [JSONPathEnvironment]
58
+ # @param query [String]
59
+ # @param tokens [Array[t_token]]
60
+ def initialize(env, query, tokens)
61
+ @env = env
62
+ @query = query
63
+ @tokens = tokens
64
+ @pos = 0
65
+ @eoi = [:token_eoi, query.size, query.size] #: t_token
66
+ end
67
+
68
+ def next
69
+ if (token = @tokens[@pos])
70
+ @pos += 1
71
+ token
72
+ else
73
+ @eoi
74
+ end
75
+ end
76
+
77
+ def eat(kind, message = nil)
78
+ token = self.next
79
+ unless token.first == kind
80
+ raise SyntaxError.new(
81
+ message || "expected #{kind}, found #{token.first}",
82
+ token,
83
+ @query
84
+ )
85
+ end
86
+
87
+ token
88
+ end
89
+
90
+ def skip(kind)
91
+ @pos += 1 if (@tokens[@pos] || @eoi).first == kind
92
+ end
93
+
94
+ def kind = (@tokens[@pos] || @eoi).first
95
+ def peek = @tokens[@pos] || @eoi
96
+
97
+ def parse
98
+ eat(:token_dollar)
99
+ segments = parse_segments
100
+ eat(:token_eoi)
101
+ segments
102
+ end
103
+
104
+ def parse_segments
105
+ segments = [] #: Array[Segment]
106
+
107
+ loop do
108
+ case peek.first
109
+ when :token_trivia
110
+ @pos += 1
111
+ if peek.first == :token_eoi
112
+ raise SyntaxError.new(
113
+ "unexpected trailing whitespace",
114
+ @tokens[@pos - 1],
115
+ @query
116
+ )
117
+ end
118
+ when :token_double_dot
119
+ segments << DescendantSegment.new(
120
+ @env,
121
+ self.next,
122
+ parse_descendant_selectors
123
+ )
124
+ when :token_dot
125
+ segments << ChildSegment.new(
126
+ @env,
127
+ self.next,
128
+ [parse_shorthand_selector]
129
+ )
130
+ when :token_lbracket
131
+ segments << ChildSegment.new(
132
+ @env,
133
+ peek,
134
+ parse_bracketed_selectors
135
+ )
136
+ else
137
+ break
138
+ end
139
+ end
140
+
141
+ segments
142
+ end
143
+
144
+ def parse_descendant_selectors
145
+ case peek.first
146
+ when :token_name, :token_asterisk
147
+ [parse_shorthand_selector]
148
+ when :token_lbracket
149
+ parse_bracketed_selectors
150
+ else
151
+ raise SyntaxError.new(
152
+ "expected a selector",
153
+ peek,
154
+ @query
155
+ )
156
+ end
157
+ end
158
+
159
+ def parse_shorthand_selector
160
+ token = self.next
161
+
162
+ case token.first
163
+ when :token_name
164
+ @env.class::NAME_SELECTOR.new(
165
+ @env,
166
+ token,
167
+ JSONP3::Path.get_token_value(token, @query)
168
+ )
169
+ when :token_asterisk
170
+ WildcardSelector.new(@env, token)
171
+ else
172
+ raise SyntaxError.new(
173
+ "expected a shorthand selector",
174
+ token,
175
+ @query
176
+ )
177
+ end
178
+ end
179
+
180
+ def parse_bracketed_selectors
181
+ segment_token = eat(:token_lbracket)
182
+ selectors = [] #: Array[Selector]
183
+
184
+ loop do
185
+ skip(:token_trivia)
186
+
187
+ case peek.first
188
+ when :token_rbracket
189
+ break
190
+ when :token_index
191
+ selectors << parse_index_or_slice
192
+ when :token_double_quoted_string, :token_single_quoted_string
193
+ token = self.next
194
+ selectors << @env.class::NAME_SELECTOR.new(
195
+ @env,
196
+ token,
197
+ JSONP3::Path.get_token_value(token, @query)
198
+ )
199
+ when :token_double_quoted_esc_string, :token_single_quoted_esc_string
200
+ token = self.next
201
+ selectors << @env.class::NAME_SELECTOR.new(
202
+ @env,
203
+ token,
204
+ JSONP3::Path.unescape(
205
+ JSONP3::Path.get_token_value(token, @query), token, @query
206
+ )
207
+ )
208
+ when :token_colon
209
+ selectors << parse_slice_selector
210
+ when :token_asterisk
211
+ selectors << WildcardSelector.new(@env, self.next)
212
+ when :token_question
213
+ selectors << parse_filter_selector
214
+ when :token_eoi
215
+ raise SyntaxError.new(
216
+ "unexpected end of query",
217
+ peek,
218
+ @query
219
+ )
220
+ else
221
+ raise SyntaxError.new(
222
+ "unexpected token #{JSONP3::Path.get_token_value(peek, @query).inspect}",
223
+ self.next,
224
+ @query
225
+ )
226
+ end
227
+
228
+ skip(:token_trivia)
229
+
230
+ case peek.first
231
+ when :token_eoi
232
+ raise SyntaxError.new(
233
+ "unexpected end of query",
234
+ peek,
235
+ @query
236
+ )
237
+ when :token_rbracket
238
+ break
239
+ else
240
+ eat(:token_comma)
241
+ if peek.first == :token_rbracket
242
+ raise SyntaxError.new(
243
+ "unexpected trailing comma",
244
+ peek,
245
+ @query
246
+ )
247
+ end
248
+ end
249
+ end
250
+
251
+ skip(:token_trivia)
252
+ eat(:token_rbracket)
253
+
254
+ if selectors.empty?
255
+ raise SyntaxError.new(
256
+ "unexpected empty segment",
257
+ segment_token,
258
+ @query
259
+ )
260
+ end
261
+
262
+ selectors
263
+ end
264
+
265
+ def parse_index_or_slice
266
+ token = self.next
267
+ index = parse_i_json_int(token)
268
+ skip(:token_trivia)
269
+
270
+ return @env.class::INDEX_SELECTOR.new(@env, token, index) unless peek.first == :token_colon
271
+
272
+ stop = nil
273
+ step = nil
274
+
275
+ eat(:token_colon)
276
+ skip(:token_trivia)
277
+
278
+ if peek.first == :token_index
279
+ stop = parse_i_json_int(self.next)
280
+ skip(:token_trivia)
281
+ end
282
+
283
+ if peek.first == :token_colon
284
+ self.next
285
+ skip(:token_trivia)
286
+ step = parse_i_json_int(self.next) if peek.first == :token_index
287
+ end
288
+
289
+ SliceSelector.new(@env, token, index, stop, step)
290
+ end
291
+
292
+ def parse_slice_selector
293
+ token = eat(:token_colon)
294
+ skip(:token_trivia)
295
+
296
+ stop = nil
297
+ step = nil
298
+
299
+ if peek.first == :token_index
300
+ stop = parse_i_json_int(self.next)
301
+ skip(:token_trivia)
302
+ end
303
+
304
+ if peek.first == :token_colon
305
+ self.next
306
+ skip(:token_trivia)
307
+ step = parse_i_json_int(self.next) if peek.first == :token_index
308
+ end
309
+
310
+ SliceSelector.new(@env, token, nil, stop, step)
311
+ end
312
+
313
+ def parse_filter_selector
314
+ token = eat(:token_question)
315
+ expr = parse_filter_expression(Precedence::LOWEST)
316
+ throw_for_not_compared(expr)
317
+ FilterSelector.new(@env, token, FilterExpression.new(token, expr))
318
+ end
319
+
320
+ def parse_filter_expression(precedence)
321
+ left = parse_primary
322
+
323
+ loop do
324
+ skip(:token_trivia)
325
+ kind = peek.first
326
+
327
+ if kind == :token_eoi ||
328
+ kind == :token_rbracket ||
329
+ !BINARY_OPERATORS.include?(kind) ||
330
+ PRECEDENCES.fetch(kind, Precedence::LOWEST) < precedence
331
+ break
332
+ end
333
+
334
+ left = parse_infix_expression(left)
335
+ end
336
+
337
+ left
338
+ end
339
+
340
+ def parse_function_expression
341
+ token = eat(:token_name)
342
+ eat(:token_lparen)
343
+
344
+ args = [] #: Array[Expression]
345
+
346
+ while peek.first != :token_rparen
347
+ expr = parse_primary
348
+ skip(:token_trivia)
349
+
350
+ expr = parse_infix_expression(expr) while BINARY_OPERATORS.include?(peek.first)
351
+ args << expr
352
+
353
+ if peek.first != :token_rparen
354
+ skip(:token_trivia)
355
+ eat(:token_comma)
356
+ end
357
+
358
+ end
359
+
360
+ skip(:token_trivia)
361
+ eat(:token_rparen)
362
+
363
+ name = JSONP3::Path.get_token_value(token, @query)
364
+ func = @env.function_extensions[name]
365
+
366
+ unless func
367
+ raise JSONPathNameError.new(
368
+ "unknown function extension #{name}",
369
+ token,
370
+ @query
371
+ )
372
+ end
373
+
374
+ validate_function_signature(name, func, args, token)
375
+
376
+ FunctionExpression.new(
377
+ token,
378
+ name,
379
+ func,
380
+ args
381
+ )
382
+ end
383
+
384
+ def parse_primary
385
+ skip(:token_trivia)
386
+ peeked = peek
387
+
388
+ case peeked.first
389
+ when :token_single_quoted_string, :token_double_quoted_string
390
+ token = self.next
391
+ StringLiteral.new(token, JSONP3::Path.get_token_value(token, @query))
392
+ when :token_single_quoted_esc_string, :token_double_quoted_esc_string
393
+ token = self.next
394
+ StringLiteral.new(
395
+ token,
396
+ JSONP3::Path.unescape(
397
+ JSONP3::Path.get_token_value(token, @query), token, @query
398
+ )
399
+ )
400
+ when :token_name
401
+ case JSONP3::Path.get_token_value(peeked, @query)
402
+ when "null"
403
+ NullLiteral.new(self.next, nil)
404
+ when "false"
405
+ BooleanLiteral.new(self.next, false)
406
+ when "true"
407
+ BooleanLiteral.new(self.next, true)
408
+ else
409
+ parse_function_expression
410
+ end
411
+ when :token_lparen
412
+ parse_grouped_expression
413
+ when :token_index, :token_int
414
+ parse_integer_literal
415
+ when :token_float
416
+ parse_float_literal
417
+ when :token_dollar
418
+ parse_absolute_query
419
+ when :token_at
420
+ parse_relative_query
421
+ when :token_not
422
+ parse_prefix_expression
423
+ else
424
+ raise SyntaxError.new(
425
+ "unexpected token #{peek.first}",
426
+ self.next,
427
+ @query
428
+ )
429
+ end
430
+ end
431
+
432
+ def parse_grouped_expression
433
+ eat(:token_lparen)
434
+ expr = parse_filter_expression(Precedence::LOWEST)
435
+
436
+ loop do
437
+ skip(:token_trivia)
438
+ peeked = peek
439
+
440
+ break if peeked.first == :token_rparen
441
+
442
+ if peeked.first == :token_eoi
443
+ raise SyntaxError.new(
444
+ "unbalanced parentheses",
445
+ peeked,
446
+ @query
447
+ )
448
+ end
449
+
450
+ expr = parse_infix_expression(expr)
451
+ end
452
+
453
+ skip(:token_trivia)
454
+ eat(:token_rparen)
455
+ expr
456
+ end
457
+
458
+ def parse_prefix_expression
459
+ token = eat(:token_not)
460
+ LogicalNotExpression.new(
461
+ token,
462
+ parse_filter_expression(Precedence::PREFIX)
463
+ )
464
+ end
465
+
466
+ def parse_infix_expression(left)
467
+ token = self.next
468
+ kind = token.first
469
+ precedence = PRECEDENCES[kind] || Precedence::LOWEST
470
+ right = parse_filter_expression(precedence)
471
+
472
+ if COMPARISON_OPERATORS.include?(kind)
473
+ throw_for_non_comparable(left)
474
+ throw_for_non_comparable(right)
475
+
476
+ case kind
477
+ when :token_eq
478
+ EqExpression.new(token, left, right)
479
+ when :token_ne
480
+ NeExpression.new(token, left, right)
481
+ when :token_lt
482
+ LtExpression.new(token, left, right)
483
+ when :token_le
484
+ LeExpression.new(token, left, right)
485
+ when :token_gt
486
+ GtExpression.new(token, left, right)
487
+ when :token_ge
488
+ GeExpression.new(token, left, right)
489
+ else
490
+ raise SyntaxError.new(
491
+ "expected an infix operator",
492
+ token,
493
+ @query
494
+ )
495
+ end
496
+ else
497
+ throw_for_not_compared(left)
498
+ throw_for_not_compared(right)
499
+
500
+ case kind
501
+ when :token_and
502
+ LogicalAndExpression.new(token, left, right)
503
+ when :token_or
504
+ LogicalOrExpression.new(token, left, right)
505
+ else
506
+ raise SyntaxError.new(
507
+ "expected an infix operator",
508
+ token,
509
+ @query
510
+ )
511
+ end
512
+ end
513
+ end
514
+
515
+ def parse_integer_literal
516
+ token = self.next
517
+ value = JSONP3::Path.get_token_value(token, @query)
518
+
519
+ if value.start_with?("0") && value.length > 1
520
+ raise SyntaxError.new(
521
+ "invalid integer literal",
522
+ token,
523
+ @query
524
+ )
525
+ end
526
+
527
+ IntegerLiteral.new(token, Integer(Float(value)))
528
+ end
529
+
530
+ def parse_float_literal
531
+ token = self.next
532
+ value = JSONP3::Path.get_token_value(token, @query)
533
+
534
+ if value.start_with?("0") && value.split(".").first.length > 1
535
+ raise SyntaxError.new(
536
+ "invalid float literal",
537
+ token,
538
+ @query
539
+ )
540
+ end
541
+
542
+ FloatLiteral.new(token, Float(value))
543
+ end
544
+
545
+ def parse_absolute_query
546
+ token = eat(:token_dollar)
547
+ AbsoluteQueryExpression.new(token, Query.new(@env, parse_segments))
548
+ end
549
+
550
+ def parse_relative_query
551
+ token = eat(:token_at)
552
+ RelativeQueryExpression.new(token, Query.new(@env, parse_segments))
553
+ end
554
+
555
+ def parse_i_json_int(token)
556
+ value = JSONP3::Path.get_token_value(token, @query)
557
+
558
+ if value.length > 1 && value.start_with?("0", "-0")
559
+ raise SyntaxError.new(
560
+ "invalid index '#{value}'",
561
+ token,
562
+ @query
563
+ )
564
+ end
565
+
566
+ begin
567
+ int = Integer(value)
568
+ rescue ArgumentError
569
+ raise SyntaxError.new(
570
+ "invalid I-JSON integer",
571
+ token,
572
+ @query
573
+ )
574
+ end
575
+
576
+ if int < @env.class::MIN_INT_INDEX || int > @env.class::MAX_INT_INDEX
577
+ raise SyntaxError.new(
578
+ "index out of range",
579
+ token,
580
+ @query
581
+ )
582
+ end
583
+
584
+ int
585
+ end
586
+
587
+ def throw_for_not_compared(expression)
588
+ if expression.is_a?(FilterExpressionLiteral)
589
+ raise TypeError.new(
590
+ "filter expression literals must be compared",
591
+ expression.token,
592
+ @query
593
+ )
594
+ end
595
+
596
+ if expression.is_a?(FunctionExpression) &&
597
+ expression.func.class::RETURN_TYPE == :value_expression
598
+ raise TypeError.new(
599
+ "result of #{expression.name}() must be compared",
600
+ expression.token,
601
+ @query
602
+ )
603
+ end
604
+ end
605
+
606
+ def throw_for_non_comparable(expression)
607
+ if expression.is_a?(QueryExpression) && !expression.query.singular?
608
+ raise TypeError.new(
609
+ "non-singular query is not comparable",
610
+ expression.token,
611
+ @query
612
+ )
613
+ end
614
+
615
+ if expression.is_a?(FunctionExpression) &&
616
+ expression.func.class::RETURN_TYPE != :value_expression
617
+ raise TypeError.new(
618
+ "result of #{expression.name}() is not comparable",
619
+ expression.token,
620
+ @query
621
+ )
622
+ end
623
+ end
624
+
625
+ def validate_function_signature(name, func, args, token)
626
+ count = func.class::ARG_TYPES.length
627
+
628
+ unless args.length == count
629
+ raise TypeError.new(
630
+ "#{name}() takes #{count} argument#{"s" unless count == 1} (#{args.length} given)",
631
+ token,
632
+ @query
633
+ )
634
+ end
635
+
636
+ func.class::ARG_TYPES.each_with_index do |t, i|
637
+ arg = args[i]
638
+ case t
639
+ when :value_expression
640
+ unless arg.is_a?(FilterExpressionLiteral) ||
641
+ (arg.is_a?(QueryExpression) && arg.query.singular?) ||
642
+ (function_return_type(arg) == :value_expression)
643
+ raise TypeError.new(
644
+ "#{name}() argument #{i} must be of ValueType",
645
+ arg.token,
646
+ @query
647
+ )
648
+ end
649
+ when :logical_expression
650
+ unless arg.is_a?(QueryExpression) || arg.is_a?(InfixExpression)
651
+ raise TypeError.new(
652
+ "#{name}() argument #{i} must be of LogicalType",
653
+ arg.token,
654
+ @query
655
+ )
656
+ end
657
+ when :nodes_expression
658
+ unless arg.is_a?(QueryExpression) || function_return_type(arg) == :nodes_expression
659
+ raise TypeError.new(
660
+ "#{name}() argument #{i} must be of NodesType",
661
+ arg.token,
662
+ @query
663
+ )
664
+ end
665
+ end
666
+ end
667
+ end
668
+
669
+ def function_return_type(expression)
670
+ return nil unless expression.is_a? FunctionExpression
671
+
672
+ expression.func.class::RETURN_TYPE
673
+ end
674
+ end
675
+ end
676
+ end