json_p3 0.4.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +26 -9
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +68 -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 +50 -0
  11. data/lib/json_p3/patch/op_add.rb +37 -0
  12. data/lib/json_p3/patch/op_copy.rb +52 -0
  13. data/lib/json_p3/patch/op_move.rb +64 -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 +224 -0
  28. data/lib/json_p3/path/node.rb +48 -0
  29. data/lib/json_p3/path/parser.rb +677 -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 +309 -0
  33. data/lib/json_p3/path/serialize.rb +16 -0
  34. data/lib/json_p3/path/unescape.rb +126 -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 +152 -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 +103 -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 -1318
  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 -440
  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,677 @@
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
+ comma_token = eat(:token_comma)
241
+ skip(:token_trivia)
242
+ if peek.first == :token_rbracket
243
+ raise SyntaxError.new(
244
+ "unexpected trailing comma",
245
+ comma_token,
246
+ @query
247
+ )
248
+ end
249
+ end
250
+ end
251
+
252
+ skip(:token_trivia)
253
+ eat(:token_rbracket)
254
+
255
+ if selectors.empty?
256
+ raise SyntaxError.new(
257
+ "unexpected empty segment",
258
+ segment_token,
259
+ @query
260
+ )
261
+ end
262
+
263
+ selectors
264
+ end
265
+
266
+ def parse_index_or_slice
267
+ token = self.next
268
+ index = parse_i_json_int(token)
269
+ skip(:token_trivia)
270
+
271
+ return @env.class::INDEX_SELECTOR.new(@env, token, index) unless peek.first == :token_colon
272
+
273
+ stop = nil
274
+ step = nil
275
+
276
+ eat(:token_colon)
277
+ skip(:token_trivia)
278
+
279
+ if peek.first == :token_index
280
+ stop = parse_i_json_int(self.next)
281
+ skip(:token_trivia)
282
+ end
283
+
284
+ if peek.first == :token_colon
285
+ self.next
286
+ skip(:token_trivia)
287
+ step = parse_i_json_int(self.next) if peek.first == :token_index
288
+ end
289
+
290
+ SliceSelector.new(@env, token, index, stop, step)
291
+ end
292
+
293
+ def parse_slice_selector
294
+ token = eat(:token_colon)
295
+ skip(:token_trivia)
296
+
297
+ stop = nil
298
+ step = nil
299
+
300
+ if peek.first == :token_index
301
+ stop = parse_i_json_int(self.next)
302
+ skip(:token_trivia)
303
+ end
304
+
305
+ if peek.first == :token_colon
306
+ self.next
307
+ skip(:token_trivia)
308
+ step = parse_i_json_int(self.next) if peek.first == :token_index
309
+ end
310
+
311
+ SliceSelector.new(@env, token, nil, stop, step)
312
+ end
313
+
314
+ def parse_filter_selector
315
+ token = eat(:token_question)
316
+ expr = parse_filter_expression(Precedence::LOWEST)
317
+ throw_for_not_compared(expr)
318
+ FilterSelector.new(@env, token, FilterExpression.new(token, expr))
319
+ end
320
+
321
+ def parse_filter_expression(precedence)
322
+ left = parse_primary
323
+
324
+ loop do
325
+ skip(:token_trivia)
326
+ kind = peek.first
327
+
328
+ if kind == :token_eoi ||
329
+ kind == :token_rbracket ||
330
+ !BINARY_OPERATORS.include?(kind) ||
331
+ PRECEDENCES.fetch(kind, Precedence::LOWEST) < precedence
332
+ break
333
+ end
334
+
335
+ left = parse_infix_expression(left)
336
+ end
337
+
338
+ left
339
+ end
340
+
341
+ def parse_function_expression
342
+ token = eat(:token_name)
343
+ eat(:token_lparen)
344
+
345
+ args = [] #: Array[Expression]
346
+
347
+ while peek.first != :token_rparen
348
+ expr = parse_primary
349
+ skip(:token_trivia)
350
+
351
+ expr = parse_infix_expression(expr) while BINARY_OPERATORS.include?(peek.first)
352
+ args << expr
353
+
354
+ if peek.first != :token_rparen
355
+ skip(:token_trivia)
356
+ eat(:token_comma)
357
+ end
358
+
359
+ end
360
+
361
+ skip(:token_trivia)
362
+ eat(:token_rparen)
363
+
364
+ name = JSONP3::Path.get_token_value(token, @query)
365
+ func = @env.function_extensions[name]
366
+
367
+ unless func
368
+ raise NameError.new(
369
+ "unknown function extension #{name}",
370
+ token,
371
+ @query
372
+ )
373
+ end
374
+
375
+ validate_function_signature(name, func, args, token)
376
+
377
+ FunctionExpression.new(
378
+ token,
379
+ name,
380
+ func,
381
+ args
382
+ )
383
+ end
384
+
385
+ def parse_primary
386
+ skip(:token_trivia)
387
+ peeked = peek
388
+
389
+ case peeked.first
390
+ when :token_single_quoted_string, :token_double_quoted_string
391
+ token = self.next
392
+ StringLiteral.new(token, JSONP3::Path.get_token_value(token, @query))
393
+ when :token_single_quoted_esc_string, :token_double_quoted_esc_string
394
+ token = self.next
395
+ StringLiteral.new(
396
+ token,
397
+ JSONP3::Path.unescape(
398
+ JSONP3::Path.get_token_value(token, @query), token, @query
399
+ )
400
+ )
401
+ when :token_name
402
+ case JSONP3::Path.get_token_value(peeked, @query)
403
+ when "null"
404
+ NullLiteral.new(self.next, nil)
405
+ when "false"
406
+ BooleanLiteral.new(self.next, false)
407
+ when "true"
408
+ BooleanLiteral.new(self.next, true)
409
+ else
410
+ parse_function_expression
411
+ end
412
+ when :token_lparen
413
+ parse_grouped_expression
414
+ when :token_index, :token_int
415
+ parse_integer_literal
416
+ when :token_float
417
+ parse_float_literal
418
+ when :token_dollar
419
+ parse_absolute_query
420
+ when :token_at
421
+ parse_relative_query
422
+ when :token_not
423
+ parse_prefix_expression
424
+ else
425
+ raise SyntaxError.new(
426
+ "unexpected token #{peek.first}",
427
+ self.next,
428
+ @query
429
+ )
430
+ end
431
+ end
432
+
433
+ def parse_grouped_expression
434
+ eat(:token_lparen)
435
+ expr = parse_filter_expression(Precedence::LOWEST)
436
+
437
+ loop do
438
+ skip(:token_trivia)
439
+ peeked = peek
440
+
441
+ break if peeked.first == :token_rparen
442
+
443
+ if peeked.first == :token_eoi
444
+ raise SyntaxError.new(
445
+ "unbalanced parentheses",
446
+ peeked,
447
+ @query
448
+ )
449
+ end
450
+
451
+ expr = parse_infix_expression(expr)
452
+ end
453
+
454
+ skip(:token_trivia)
455
+ eat(:token_rparen)
456
+ expr
457
+ end
458
+
459
+ def parse_prefix_expression
460
+ token = eat(:token_not)
461
+ LogicalNotExpression.new(
462
+ token,
463
+ parse_filter_expression(Precedence::PREFIX)
464
+ )
465
+ end
466
+
467
+ def parse_infix_expression(left)
468
+ token = self.next
469
+ kind = token.first
470
+ precedence = PRECEDENCES[kind] || Precedence::LOWEST
471
+ right = parse_filter_expression(precedence)
472
+
473
+ if COMPARISON_OPERATORS.include?(kind)
474
+ throw_for_non_comparable(left)
475
+ throw_for_non_comparable(right)
476
+
477
+ case kind
478
+ when :token_eq
479
+ EqExpression.new(token, left, right)
480
+ when :token_ne
481
+ NeExpression.new(token, left, right)
482
+ when :token_lt
483
+ LtExpression.new(token, left, right)
484
+ when :token_le
485
+ LeExpression.new(token, left, right)
486
+ when :token_gt
487
+ GtExpression.new(token, left, right)
488
+ when :token_ge
489
+ GeExpression.new(token, left, right)
490
+ else
491
+ raise SyntaxError.new(
492
+ "expected an infix operator",
493
+ token,
494
+ @query
495
+ )
496
+ end
497
+ else
498
+ throw_for_not_compared(left)
499
+ throw_for_not_compared(right)
500
+
501
+ case kind
502
+ when :token_and
503
+ LogicalAndExpression.new(token, left, right)
504
+ when :token_or
505
+ LogicalOrExpression.new(token, left, right)
506
+ else
507
+ raise SyntaxError.new(
508
+ "expected an infix operator",
509
+ token,
510
+ @query
511
+ )
512
+ end
513
+ end
514
+ end
515
+
516
+ def parse_integer_literal
517
+ token = self.next
518
+ value = JSONP3::Path.get_token_value(token, @query)
519
+
520
+ if value.start_with?("0") && value.length > 1
521
+ raise SyntaxError.new(
522
+ "invalid integer literal",
523
+ token,
524
+ @query
525
+ )
526
+ end
527
+
528
+ IntegerLiteral.new(token, Integer(Float(value)))
529
+ end
530
+
531
+ def parse_float_literal
532
+ token = self.next
533
+ value = JSONP3::Path.get_token_value(token, @query)
534
+
535
+ if value.start_with?("0") && value.split(".").first.length > 1
536
+ raise SyntaxError.new(
537
+ "invalid float literal",
538
+ token,
539
+ @query
540
+ )
541
+ end
542
+
543
+ FloatLiteral.new(token, Float(value))
544
+ end
545
+
546
+ def parse_absolute_query
547
+ token = eat(:token_dollar)
548
+ AbsoluteQueryExpression.new(token, Query.new(@env, parse_segments))
549
+ end
550
+
551
+ def parse_relative_query
552
+ token = eat(:token_at)
553
+ RelativeQueryExpression.new(token, Query.new(@env, parse_segments))
554
+ end
555
+
556
+ def parse_i_json_int(token)
557
+ value = JSONP3::Path.get_token_value(token, @query)
558
+
559
+ if value.length > 1 && value.start_with?("0", "-0")
560
+ raise SyntaxError.new(
561
+ "invalid index '#{value}'",
562
+ token,
563
+ @query
564
+ )
565
+ end
566
+
567
+ begin
568
+ int = Integer(value)
569
+ rescue ArgumentError
570
+ raise SyntaxError.new(
571
+ "invalid I-JSON integer",
572
+ token,
573
+ @query
574
+ )
575
+ end
576
+
577
+ if int < @env.class::MIN_INT_INDEX || int > @env.class::MAX_INT_INDEX
578
+ raise SyntaxError.new(
579
+ "index out of range",
580
+ token,
581
+ @query
582
+ )
583
+ end
584
+
585
+ int
586
+ end
587
+
588
+ def throw_for_not_compared(expression)
589
+ if expression.is_a?(FilterExpressionLiteral)
590
+ raise TypeError.new(
591
+ "filter expression literals must be compared",
592
+ expression.token,
593
+ @query
594
+ )
595
+ end
596
+
597
+ if expression.is_a?(FunctionExpression) &&
598
+ expression.func.class::RETURN_TYPE == :value_expression
599
+ raise TypeError.new(
600
+ "result of #{expression.name}() must be compared",
601
+ expression.token,
602
+ @query
603
+ )
604
+ end
605
+ end
606
+
607
+ def throw_for_non_comparable(expression)
608
+ if expression.is_a?(QueryExpression) && !expression.query.singular?
609
+ raise TypeError.new(
610
+ "non-singular query is not comparable",
611
+ expression.token,
612
+ @query
613
+ )
614
+ end
615
+
616
+ if expression.is_a?(FunctionExpression) &&
617
+ expression.func.class::RETURN_TYPE != :value_expression
618
+ raise TypeError.new(
619
+ "result of #{expression.name}() is not comparable",
620
+ expression.token,
621
+ @query
622
+ )
623
+ end
624
+ end
625
+
626
+ def validate_function_signature(name, func, args, token)
627
+ count = func.class::ARG_TYPES.length
628
+
629
+ unless args.length == count
630
+ raise TypeError.new(
631
+ "#{name}() takes #{count} argument#{"s" unless count == 1} (#{args.length} given)",
632
+ token,
633
+ @query
634
+ )
635
+ end
636
+
637
+ func.class::ARG_TYPES.each_with_index do |t, i|
638
+ arg = args[i]
639
+ case t
640
+ when :value_expression
641
+ unless arg.is_a?(FilterExpressionLiteral) ||
642
+ (arg.is_a?(QueryExpression) && arg.query.singular?) ||
643
+ (function_return_type(arg) == :value_expression)
644
+ raise TypeError.new(
645
+ "#{name}() argument #{i} must be of ValueType",
646
+ arg.token,
647
+ @query
648
+ )
649
+ end
650
+ when :logical_expression
651
+ unless arg.is_a?(QueryExpression) || arg.is_a?(InfixExpression)
652
+ raise TypeError.new(
653
+ "#{name}() argument #{i} must be of LogicalType",
654
+ arg.token,
655
+ @query
656
+ )
657
+ end
658
+ when :nodes_expression
659
+ unless arg.is_a?(QueryExpression) || function_return_type(arg) == :nodes_expression
660
+ raise TypeError.new(
661
+ "#{name}() argument #{i} must be of NodesType",
662
+ arg.token,
663
+ @query
664
+ )
665
+ end
666
+ end
667
+ end
668
+ end
669
+
670
+ def function_return_type(expression)
671
+ return nil unless expression.is_a? FunctionExpression
672
+
673
+ expression.func.class::RETURN_TYPE
674
+ end
675
+ end
676
+ end
677
+ end