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,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "node"
4
+
5
+ module JSONP3
6
+ module Path
7
+ # A compiled JSONPath expression ready to be applied to JSON-like values.
8
+ class Query
9
+ def initialize(env, segments)
10
+ @env = env
11
+ @segments = segments
12
+ end
13
+
14
+ def to_s
15
+ "$#{@segments.join}"
16
+ end
17
+
18
+ # Apply this JSONPath expression to JSON-like value _root_.
19
+ # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
20
+ # @return [Array<Node>] the sequence of nodes found while applying this query to _root_.
21
+ def find(root)
22
+ nodes = [Node.new(root, [], root)]
23
+ @segments.each { |segment| nodes = segment.resolve(nodes) }
24
+ NodeList.new(nodes)
25
+ end
26
+
27
+ alias apply find
28
+
29
+ # Apply this JSONPath expression to JSON-like value _root_.
30
+ # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
31
+ # @return [Enumerable<Node>] the sequence of nodes found while applying this query to _root_.
32
+ def find_enum(root)
33
+ nodes = [Node.new(root, [], root)] # : Enumerable[Node]
34
+ @segments.each { |segment| nodes = segment.resolve_enum(nodes) }
35
+ nodes
36
+ end
37
+
38
+ # Return the first node from applying this JSONPath expression to JSON-like value _root_.
39
+ # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
40
+ # @return [Node | nil] the first available node or nil if there were no matches.
41
+ def match(root)
42
+ find_enum(root).first
43
+ end
44
+
45
+ # Return `true` if this query results in at least one node, or `false` otherwise.
46
+ # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
47
+ # @return [bool] `true` if this query results in at least one node, or `false` otherwise.
48
+ def match?(root)
49
+ !find_enum(root).first.nil?
50
+ end
51
+
52
+ # Return the first node from applying this JSONPath expression to JSON-like value _root_.
53
+ # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
54
+ # @return [Node | nil] the first available node or nil if there were no matches.
55
+ def first(root)
56
+ find_enum(root).first
57
+ end
58
+
59
+ # Return _true_ if this JSONPath expression is a singular query.
60
+ def singular?
61
+ @segments.each do |segment|
62
+ return false if segment.instance_of? DescendantSegment
63
+ return false unless segment.selectors.length == 1 && segment.selectors[0].singular?
64
+ end
65
+ true
66
+ end
67
+
68
+ # Return _true_ if this JSONPath expression has no segments.
69
+ def empty?
70
+ @segments.empty?
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ module Path
5
+ # Base class for all JSONPath segments.
6
+ class Segment
7
+ # @dynamic token, selectors
8
+ attr_reader :token, :selectors
9
+
10
+ def initialize(env, token, selectors)
11
+ @env = env
12
+ @token = token
13
+ @selectors = selectors
14
+ end
15
+
16
+ # Select the children of each node in _nodes_.
17
+ # @return [Array<Node>]
18
+ def resolve(_nodes)
19
+ raise "segments must implement resolve(nodes)"
20
+ end
21
+
22
+ # Select the children of each node in _nodes_.
23
+ # @return [Enumerable<Node>]
24
+ def resolve_enum(_nodes)
25
+ raise "segments must implement resolve_enum(nodes)"
26
+ end
27
+ end
28
+
29
+ # The child selection segment.
30
+ class ChildSegment < Segment
31
+ def resolve(nodes)
32
+ rv = [] # : Array[Node]
33
+ nodes.each do |node|
34
+ @selectors.each do |selector|
35
+ rv.concat selector.resolve(node)
36
+ end
37
+ end
38
+ rv
39
+ end
40
+
41
+ def resolve_enum(nodes)
42
+ Enumerator.new do |yielder|
43
+ nodes.each do |node|
44
+ @selectors.each do |selector|
45
+ selector.resolve(node).each do |item|
46
+ yielder << item
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def to_s
54
+ "[#{@selectors.join(", ")}]"
55
+ end
56
+
57
+ def ==(other)
58
+ self.class == other.class &&
59
+ @selectors == other.selectors &&
60
+ @token == other.token
61
+ end
62
+
63
+ alias eql? ==
64
+
65
+ def hash
66
+ [@selectors, @token].hash
67
+ end
68
+ end
69
+
70
+ # The descendant segment
71
+ class DescendantSegment < Segment
72
+ def resolve(nodes)
73
+ rv = [] #: Array[Node]
74
+ nodes.each do |node|
75
+ visit(node).each do |descendant|
76
+ @selectors.each do |selector|
77
+ rv.concat selector.resolve(descendant)
78
+ end
79
+ end
80
+ end
81
+ rv
82
+ end
83
+
84
+ def resolve_enum(nodes)
85
+ Enumerator.new do |yielder|
86
+ nodes.each do |node|
87
+ visit_enum(node).each do |descendant|
88
+ @selectors.each do |selector|
89
+ selector.resolve(descendant).each do |item|
90
+ yielder << item
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def to_s
99
+ "..[#{@selectors.join(", ")}]"
100
+ end
101
+
102
+ def ==(other)
103
+ self.class == other.class &&
104
+ @selectors == other.selectors &&
105
+ @token == other.token
106
+ end
107
+
108
+ alias eql? ==
109
+
110
+ def hash
111
+ ["..", @selectors, @token].hash
112
+ end
113
+
114
+ protected
115
+
116
+ def visit(node, depth = 1)
117
+ if depth > @env.class::MAX_RECURSION_DEPTH
118
+ raise JSONP3::Path::RecursionError.new(
119
+ "recursion limit exceeded",
120
+ @token,
121
+ ""
122
+ )
123
+ end
124
+
125
+ rv = [node]
126
+
127
+ if node.value.is_a? Array
128
+ node.value.each_with_index do |value, i|
129
+ child = Node.new(value, [node.location, i], node.root)
130
+ rv.concat visit(child, depth + 1)
131
+ end
132
+ elsif node.value.is_a? Hash
133
+ node.value.each do |key, value|
134
+ child = Node.new(value, [node.location, key], node.root)
135
+ rv.concat visit(child, depth + 1)
136
+ end
137
+ end
138
+
139
+ rv
140
+ end
141
+
142
+ def visit_enum(node, depth = 1)
143
+ if depth > @env.class::MAX_RECURSION_DEPTH
144
+ raise JSONP3::Path::RecursionError.new(
145
+ "recursion limit exceeded",
146
+ @token,
147
+ ""
148
+ )
149
+ end
150
+
151
+ Enumerator.new do |yielder|
152
+ yielder << node
153
+ if node.value.is_a? Array
154
+ node.value.each_with_index do |value, i|
155
+ child = Node.new(value, [node.location, i], node.root)
156
+ visit_enum(child, depth + 1).each do |item|
157
+ yielder << item
158
+ end
159
+ end
160
+ elsif node.value.is_a? Hash
161
+ node.value.each do |key, value|
162
+ child = Node.new(value, [node.location, key], node.root)
163
+ visit_enum(child, depth + 1).each do |item|
164
+ yielder << item
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ module Path
5
+ # Base class for all JSONPath selectors
6
+ class Selector
7
+ attr_reader :token
8
+
9
+ def initialize(env, token)
10
+ @env = env
11
+ @token = token
12
+ end
13
+
14
+ # Apply this selector to _node_.
15
+ # @return [Array<Node>]
16
+ def resolve(_node)
17
+ raise "selectors must implement resolve(node)"
18
+ end
19
+
20
+ # Apply this selector to _node_.
21
+ # @return [Enumerable<Node>]
22
+ def resolve_enum(node)
23
+ resolve(node)
24
+ end
25
+
26
+ # Return true if this selector is a singular selector.
27
+ def singular?
28
+ false
29
+ end
30
+ end
31
+
32
+ # The name selector select values from hashes given a key.
33
+ class NameSelector < Selector
34
+ attr_reader :name
35
+
36
+ def initialize(env, token, name)
37
+ super(env, token)
38
+ @name = name
39
+ end
40
+
41
+ def resolve(node)
42
+ if node.value.is_a?(Hash) && node.value.key?(@name)
43
+ [node.new_child(node.value[@name], @name)]
44
+ else
45
+ []
46
+ end
47
+ end
48
+
49
+ def singular?
50
+ true
51
+ end
52
+
53
+ def to_s
54
+ JSONP3::Path.canonical_string(@name)
55
+ end
56
+
57
+ def ==(other)
58
+ self.class == other.class &&
59
+ @name == other.name &&
60
+ @token == other.token
61
+ end
62
+
63
+ alias eql? ==
64
+
65
+ def hash
66
+ [@name, @token].hash
67
+ end
68
+ end
69
+
70
+ # This non-standard name selector selects values from hashes given a string or
71
+ # symbol key.
72
+ class SymbolNameSelector < NameSelector
73
+ def initialize(env, token, name)
74
+ super
75
+ @sym = @name.to_sym
76
+ end
77
+
78
+ def resolve(node)
79
+ if node.value.is_a?(Hash)
80
+ if node.value.key?(@name)
81
+ [node.new_child(node.value[@name], @name)]
82
+ elsif node.value.key?(@sym)
83
+ [node.new_child(node.value[@sym], @name)]
84
+ else
85
+ []
86
+ end
87
+ else
88
+ []
89
+ end
90
+ end
91
+ end
92
+
93
+ # The index selector selects values from arrays given an index.
94
+ class IndexSelector < Selector
95
+ attr_reader :index
96
+
97
+ def initialize(env, token, index)
98
+ super(env, token)
99
+ @index = index
100
+ end
101
+
102
+ def resolve(node)
103
+ if node.value.is_a?(Array)
104
+ norm_index = normalize(@index, node.value.length)
105
+ return [] if norm_index.negative? || norm_index >= node.value.length
106
+
107
+ [node.new_child(node.value[@index], norm_index)]
108
+ else
109
+ []
110
+ end
111
+ end
112
+
113
+ def singular?
114
+ true
115
+ end
116
+
117
+ def to_s
118
+ @index.to_s
119
+ end
120
+
121
+ def ==(other)
122
+ self.class == other.class &&
123
+ @index == other.index &&
124
+ @token == other.token
125
+ end
126
+
127
+ alias eql? ==
128
+
129
+ def hash
130
+ [@index, @token].hash
131
+ end
132
+
133
+ private
134
+
135
+ def normalize(index, length)
136
+ index.negative? && length >= index.abs ? length + index : index
137
+ end
138
+ end
139
+
140
+ # The wildcard selector selects all elements from an array or values from a hash.
141
+ class WildcardSelector < Selector
142
+ def resolve(node)
143
+ if node.value.is_a? Hash
144
+ node.value.map { |k, v| node.new_child(v, k) }
145
+ elsif node.value.is_a? Array
146
+ node.value.map.with_index { |e, i| node.new_child(e, i) }
147
+ else
148
+ []
149
+ end
150
+ end
151
+
152
+ def resolve_enum(node)
153
+ if node.value.is_a? Hash
154
+ Enumerator.new do |yielder|
155
+ node.value.each do |k, v|
156
+ yielder << node.new_child(v, k)
157
+ end
158
+ end
159
+ elsif node.value.is_a? Array
160
+ Enumerator.new do |yielder|
161
+ node.value.each.with_index do |e, i|
162
+ yielder << node.new_child(e, i)
163
+ end
164
+ end
165
+ else
166
+ []
167
+ end
168
+ end
169
+
170
+ def to_s
171
+ "*"
172
+ end
173
+
174
+ def ==(other)
175
+ self.class == other.class && @token == other.token
176
+ end
177
+
178
+ alias eql? ==
179
+
180
+ def hash
181
+ @token.hash
182
+ end
183
+ end
184
+
185
+ # The slice selector selects a range of elements from an array.
186
+ class SliceSelector < Selector
187
+ attr_reader :start, :stop, :step
188
+
189
+ def initialize(env, token, start, stop, step)
190
+ super(env, token)
191
+ @start = start
192
+ @stop = stop
193
+ @step = step || 1
194
+ end
195
+
196
+ def resolve(node)
197
+ return [] unless node.value.is_a?(Array)
198
+
199
+ length = node.value.length
200
+ return [] if length.zero? || @step.zero?
201
+
202
+ normalized_start = if @start.nil?
203
+ @step.negative? ? length - 1 : 0
204
+ elsif @start&.negative?
205
+ [length + (@start || raise), 0].max
206
+ else
207
+ [@start || raise, length - 1].min
208
+ end
209
+
210
+ normalized_stop = if @stop.nil?
211
+ @step.negative? ? -1 : length
212
+ elsif @stop&.negative?
213
+ [length + (@stop || raise), -1].max
214
+ else
215
+ [@stop || raise, length].min
216
+ end
217
+
218
+ (normalized_start...normalized_stop).step(@step).map { |i| node.new_child(node.value[i], i) }
219
+ end
220
+
221
+ def to_s
222
+ start = @start || ""
223
+ stop = @stop || ""
224
+ step = @step || 1
225
+ "#{start}:#{stop}:#{step}"
226
+ end
227
+
228
+ def ==(other)
229
+ self.class == other.class &&
230
+ @start == other.start &&
231
+ @stop == other.stop &&
232
+ @step == other.step &&
233
+ @token == other.token
234
+ end
235
+
236
+ alias eql? ==
237
+
238
+ def hash
239
+ [@start, @stop, @step, @token].hash
240
+ end
241
+ end
242
+
243
+ # Select array elements or hash values according to a filter expression.
244
+ class FilterSelector < Selector
245
+ attr_reader :expression
246
+
247
+ def initialize(env, token, expression)
248
+ super(env, token)
249
+ @expression = expression
250
+ end
251
+
252
+ def resolve(node)
253
+ # @type var nodes: Array[Node]
254
+ nodes = []
255
+
256
+ if node.value.is_a?(Array)
257
+ node.value.each_with_index do |e, i|
258
+ context = FilterContext.new(@env, e, node.root)
259
+ nodes << node.new_child(e, i) if @expression.evaluate(context)
260
+ end
261
+ elsif node.value.is_a?(Hash)
262
+ node.value.each_pair do |k, v|
263
+ context = FilterContext.new(@env, v, node.root)
264
+ nodes << node.new_child(v, k) if @expression.evaluate(context)
265
+ end
266
+ end
267
+
268
+ nodes
269
+ end
270
+
271
+ def resolve_enum(node)
272
+ Enumerator.new do |yielder|
273
+ if node.value.is_a?(Array)
274
+ node.value.each_with_index do |e, i|
275
+ context = FilterContext.new(@env, e, node.root)
276
+ yielder << node.new_child(e, i) if @expression.evaluate(context)
277
+ end
278
+ elsif node.value.is_a?(Hash)
279
+ node.value.each_pair do |k, v|
280
+ context = FilterContext.new(@env, v, node.root)
281
+ yielder << node.new_child(v, k) if @expression.evaluate(context)
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ def to_s
288
+ "?#{@expression}"
289
+ end
290
+
291
+ def ==(other)
292
+ self.class == other.class &&
293
+ @expression == other.start &&
294
+ @token == other.token
295
+ end
296
+
297
+ alias eql? ==
298
+
299
+ def hash
300
+ [@expression, @token].hash
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module JSONP3
6
+ # JSONPath query expressions.
7
+ module Path
8
+ TRANS = { "\\\"" => "\"", "'" => "\\'" }.freeze
9
+
10
+ # Return _value_ formatted as a canonical string literal.
11
+ # @param value [String]
12
+ def self.canonical_string(value)
13
+ "'#{(JSON.dump(value)[1..-2] || raise).gsub(/('|\\")/, TRANS)}'"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module JSONP3
6
+ # JSONPath query expressions.
7
+ module Path
8
+ RE_SLASH_U = /\\u([0-9a-fA-F]{4})/
9
+
10
+ # Replace escape sequences with their equivalent Unicode code point.
11
+ def self.unescape(value, token, query)
12
+ unescaped = String.new(encoding: "UTF-8")
13
+ scanner = StringScanner.new(value)
14
+
15
+ until scanner.eos?
16
+ if scanner.scan(RE_SLASH_U)
17
+ code_point = (scanner.captures&.first || raise).to_i(16)
18
+
19
+ if low_surrogate?(code_point)
20
+ raise JSONP3::Path::SyntaxError.new(
21
+ "unexpected low surrogate",
22
+ token,
23
+ query
24
+ )
25
+ end
26
+
27
+ if high_surrogate?(code_point)
28
+ unless scanner.scan(RE_SLASH_U)
29
+ raise JSONP3::Path::SyntaxError.new(
30
+ "expected a low surrogate",
31
+ token,
32
+ query
33
+ )
34
+ end
35
+
36
+ low_surrogate = (scanner.captures&.first || raise).to_i(16)
37
+
38
+ unless low_surrogate?(low_surrogate)
39
+ raise JSONP3::Path::SyntaxError.new(
40
+ "expected a low surrogate",
41
+ token,
42
+ query
43
+ )
44
+ end
45
+
46
+ code_point = 0x10000 + (
47
+ ((code_point & 0x03FF) << 10) | (low_surrogate & 0x03FF)
48
+ )
49
+ end
50
+
51
+ if code_point <= 0x1f
52
+ raise JSONP3::Path::SyntaxError.new(
53
+ "invalid character #{code_point}",
54
+ token,
55
+ query
56
+ )
57
+ end
58
+
59
+ unescaped << code_point.chr(Encoding::UTF_8)
60
+ next
61
+ end
62
+
63
+ ch = scanner.getch
64
+
65
+ break if ch.nil?
66
+
67
+ unless ch == "\\"
68
+ if ch.ord <= 0x1f
69
+ raise JSONP3::Path::SyntaxError.new(
70
+ "invalid character #{ch.ord}",
71
+ token,
72
+ query
73
+ )
74
+ end
75
+ unescaped << ch
76
+ next
77
+ end
78
+
79
+ ch = scanner.getch
80
+
81
+ case ch
82
+ when "\""
83
+ if token.first == :token_single_quoted_esc_string
84
+ raise JSONP3::Path::SyntaxError.new(
85
+ "unexpected \\\" escape in single quoted string",
86
+ token,
87
+ query
88
+ )
89
+ end
90
+ unescaped << "\""
91
+ when "'"
92
+ if token.first == :token_double_quoted_esc_string
93
+ raise JSONP3::Path::SyntaxError.new(
94
+ "unexpected \\' escape in double quoted string",
95
+ token,
96
+ query
97
+ )
98
+ end
99
+ unescaped << "'"
100
+ when "\\"
101
+ unescaped << "\\"
102
+ when "/"
103
+ unescaped << "/"
104
+ when "b"
105
+ unescaped << "\x08"
106
+ when "f"
107
+ unescaped << "\x0c"
108
+ when "n"
109
+ unescaped << "\n"
110
+ when "r"
111
+ unescaped << "\r"
112
+ when "t"
113
+ unescaped << "\t"
114
+ when "u"
115
+ raise JSONP3::Path::SyntaxError.new("unexpected \\u escape sequence", token, query)
116
+ when nil
117
+ raise JSONP3::Path::SyntaxError.new("incomplete escape sequence", token, query)
118
+ else
119
+ raise JSONP3::Path::SyntaxError.new("unknown escape sequence", token, query)
120
+ end
121
+ end
122
+
123
+ unescaped
124
+ end
125
+
126
+ def self.high_surrogate?(code_point)
127
+ code_point.between?(0xD800, 0xDBFF)
128
+ end
129
+
130
+ def self.low_surrogate?(code_point)
131
+ code_point.between?(0xDC00, 0xDFFF)
132
+ end
133
+ end
134
+ end