json_p3 0.2.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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.rubocop.yml +14 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +7 -0
- data/LICENCE +21 -0
- data/README.md +353 -0
- data/Rakefile +23 -0
- data/Steepfile +27 -0
- data/lib/json_p3/cache.rb +40 -0
- data/lib/json_p3/environment.rb +76 -0
- data/lib/json_p3/errors.rb +49 -0
- data/lib/json_p3/filter.rb +426 -0
- data/lib/json_p3/function.rb +16 -0
- data/lib/json_p3/function_extensions/count.rb +15 -0
- data/lib/json_p3/function_extensions/length.rb +17 -0
- data/lib/json_p3/function_extensions/match.rb +62 -0
- data/lib/json_p3/function_extensions/pattern.rb +39 -0
- data/lib/json_p3/function_extensions/search.rb +44 -0
- data/lib/json_p3/function_extensions/value.rb +15 -0
- data/lib/json_p3/lexer.rb +420 -0
- data/lib/json_p3/node.rb +42 -0
- data/lib/json_p3/parser.rb +553 -0
- data/lib/json_p3/path.rb +42 -0
- data/lib/json_p3/segment.rb +102 -0
- data/lib/json_p3/selector.rb +285 -0
- data/lib/json_p3/token.rb +74 -0
- data/lib/json_p3/unescape.rb +112 -0
- data/lib/json_p3/version.rb +5 -0
- data/lib/json_p3.rb +17 -0
- data/performance/benchmark.rb +33 -0
- data/performance/benchmark_ips.rb +29 -0
- data/performance/benchmark_small_citylots.rb +18 -0
- data/performance/memory_profile.rb +19 -0
- data/performance/memory_profile_small_citylots.rb +14 -0
- data/performance/profile.rb +30 -0
- data/sig/json_p3.rbs +1058 -0
- data.tar.gz.sig +1 -0
- metadata +110 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,426 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require_relative "function"
|
5
|
+
|
6
|
+
module JSONP3 # rubocop:disable Style/Documentation
|
7
|
+
# Base class for all filter expression nodes.
|
8
|
+
class Expression
|
9
|
+
# @dynamic token
|
10
|
+
attr_reader :token
|
11
|
+
|
12
|
+
def initialize(token)
|
13
|
+
@token = token
|
14
|
+
end
|
15
|
+
|
16
|
+
# Evaluate the filter expression in the given context.
|
17
|
+
def evaluate(_context)
|
18
|
+
raise "filter expressions must implement `evaluate(context)`"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# An expression that evaluates to true or false.
|
23
|
+
class FilterExpression < Expression
|
24
|
+
attr_reader :expression
|
25
|
+
|
26
|
+
def initialize(token, expression)
|
27
|
+
super(token)
|
28
|
+
@expression = expression
|
29
|
+
end
|
30
|
+
|
31
|
+
def evaluate(context)
|
32
|
+
JSONP3.truthy?(@expression.evaluate(context))
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@expression.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
self.class == other.class &&
|
41
|
+
@expression == other.expression &&
|
42
|
+
@token == other.token
|
43
|
+
end
|
44
|
+
|
45
|
+
alias eql? ==
|
46
|
+
|
47
|
+
def hash
|
48
|
+
[@expression, @token].hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Base class for expression literals.
|
53
|
+
class FilterExpressionLiteral < Expression
|
54
|
+
attr_reader :value
|
55
|
+
|
56
|
+
def initialize(token, value)
|
57
|
+
super(token)
|
58
|
+
@value = value
|
59
|
+
end
|
60
|
+
|
61
|
+
def evaluate(_context)
|
62
|
+
@value
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
@value.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def ==(other)
|
70
|
+
self.class == other.class &&
|
71
|
+
@value == other.value &&
|
72
|
+
@token == other.token
|
73
|
+
end
|
74
|
+
|
75
|
+
alias eql? ==
|
76
|
+
|
77
|
+
def hash
|
78
|
+
[@value, @token].hash
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Literal true or false.
|
83
|
+
class BooleanLiteral < FilterExpressionLiteral; end
|
84
|
+
|
85
|
+
# A double or single quoted string literal.
|
86
|
+
class StringLiteral < FilterExpressionLiteral
|
87
|
+
def to_s
|
88
|
+
JSON.generate(@value)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# A literal integer.
|
93
|
+
class IntegerLiteral < FilterExpressionLiteral; end
|
94
|
+
|
95
|
+
# A literal float
|
96
|
+
class FloatLiteral < FilterExpressionLiteral; end
|
97
|
+
|
98
|
+
# A literal null
|
99
|
+
class NullLiteral < FilterExpressionLiteral
|
100
|
+
def to_s
|
101
|
+
"null"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# An expression prefixed with the logical not operator.
|
106
|
+
class LogicalNotExpression < Expression
|
107
|
+
attr_reader :expression
|
108
|
+
|
109
|
+
def initialize(token, expression)
|
110
|
+
super(token)
|
111
|
+
@expression = expression
|
112
|
+
end
|
113
|
+
|
114
|
+
def evaluate(context)
|
115
|
+
!JSONP3.truthy?(@expression.evaluate(context))
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
"!#{@expression}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def ==(other)
|
123
|
+
self.class == other.class &&
|
124
|
+
@expression == other.expression &&
|
125
|
+
@token == other.token
|
126
|
+
end
|
127
|
+
|
128
|
+
alias eql? ==
|
129
|
+
|
130
|
+
def hash
|
131
|
+
[@expression, @token].hash
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Base class for expression with a left expression, operator and right expression.
|
136
|
+
class InfixExpression < Expression
|
137
|
+
attr_reader :left, :right
|
138
|
+
|
139
|
+
def initialize(token, left, right)
|
140
|
+
super(token)
|
141
|
+
@left = left
|
142
|
+
@right = right
|
143
|
+
end
|
144
|
+
|
145
|
+
def evaluate(_context)
|
146
|
+
raise "infix expressions must implement `evaluate(context)`"
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_s
|
150
|
+
raise "infix expressions must implement `to_s`"
|
151
|
+
end
|
152
|
+
|
153
|
+
def ==(other)
|
154
|
+
self.class == other.class &&
|
155
|
+
@left == other.left &&
|
156
|
+
@right == other.right &&
|
157
|
+
@token == other.token
|
158
|
+
end
|
159
|
+
|
160
|
+
alias eql? ==
|
161
|
+
|
162
|
+
def hash
|
163
|
+
[@left, @right, @token].hash
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# A logical `&&` expression.
|
168
|
+
class LogicalAndExpression < InfixExpression
|
169
|
+
def evaluate(context)
|
170
|
+
JSONP3.truthy?(@left.evaluate(context)) && JSONP3.truthy?(@right.evaluate(context))
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
"#{@left} && #{@right}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# A logical `||` expression.
|
179
|
+
class LogicalOrExpression < InfixExpression
|
180
|
+
def evaluate(context)
|
181
|
+
JSONP3.truthy?(@left.evaluate(context)) || JSONP3.truthy?(@right.evaluate(context))
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_s
|
185
|
+
"#{@left} || #{@right}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# An `==` expression.
|
190
|
+
class EqExpression < InfixExpression
|
191
|
+
def evaluate(context)
|
192
|
+
JSONP3.eq?(@left.evaluate(context), @right.evaluate(context))
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_s
|
196
|
+
"#{@left} == #{@right}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# A `!=` expression.
|
201
|
+
class NeExpression < InfixExpression
|
202
|
+
def evaluate(context)
|
203
|
+
!JSONP3.eq?(@left.evaluate(context), @right.evaluate(context))
|
204
|
+
end
|
205
|
+
|
206
|
+
def to_s
|
207
|
+
"#{@left} != #{@right}"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# A `<=` expression.
|
212
|
+
class LeExpression < InfixExpression
|
213
|
+
def evaluate(context)
|
214
|
+
left = @left.evaluate(context)
|
215
|
+
right = @right.evaluate(context)
|
216
|
+
JSONP3.eq?(left, right) || JSONP3.lt?(left, right)
|
217
|
+
end
|
218
|
+
|
219
|
+
def to_s
|
220
|
+
"#{@left} <= #{@right}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# A `>=` expression.
|
225
|
+
class GeExpression < InfixExpression
|
226
|
+
def evaluate(context)
|
227
|
+
left = @left.evaluate(context)
|
228
|
+
right = @right.evaluate(context)
|
229
|
+
JSONP3.eq?(left, right) || JSONP3.lt?(right, left)
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_s
|
233
|
+
"#{@left} >= #{@right}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# A `<` expression.
|
238
|
+
class LtExpression < InfixExpression
|
239
|
+
def evaluate(context)
|
240
|
+
JSONP3.lt?(@left.evaluate(context), @right.evaluate(context))
|
241
|
+
end
|
242
|
+
|
243
|
+
def to_s
|
244
|
+
"#{@left} < #{@right}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# A `>` expression.
|
249
|
+
class GtExpression < InfixExpression
|
250
|
+
def evaluate(context)
|
251
|
+
JSONP3.lt?(@right.evaluate(context), @left.evaluate(context))
|
252
|
+
end
|
253
|
+
|
254
|
+
def to_s
|
255
|
+
"#{@left} > #{@right}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Base class for all embedded filter queries
|
260
|
+
class QueryExpression < Expression
|
261
|
+
attr_reader :query
|
262
|
+
|
263
|
+
def initialize(token, query)
|
264
|
+
super(token)
|
265
|
+
@query = query
|
266
|
+
end
|
267
|
+
|
268
|
+
def evaluate(_context)
|
269
|
+
raise "query expressions must implement `evaluate(context)`"
|
270
|
+
end
|
271
|
+
|
272
|
+
def to_s
|
273
|
+
raise "query expressions must implement `to_s`"
|
274
|
+
end
|
275
|
+
|
276
|
+
def ==(other)
|
277
|
+
self.class == other.class &&
|
278
|
+
@query == other.query &&
|
279
|
+
@token == other.token
|
280
|
+
end
|
281
|
+
|
282
|
+
alias eql? ==
|
283
|
+
|
284
|
+
def hash
|
285
|
+
[@query, @token].hash
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# An embedded query starting at the current node.
|
290
|
+
class RelativeQueryExpression < QueryExpression
|
291
|
+
def evaluate(context)
|
292
|
+
unless context.current.is_a?(Array) || context.current.is_a?(Hash)
|
293
|
+
return @query.empty? ? context.current : JSONPathNodeList.new
|
294
|
+
end
|
295
|
+
|
296
|
+
@query.find(context.current)
|
297
|
+
end
|
298
|
+
|
299
|
+
def to_s
|
300
|
+
"@#{@query.to_s[1..]}"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# An embedded query starting at the root node.
|
305
|
+
class RootQueryExpression < QueryExpression
|
306
|
+
def evaluate(context)
|
307
|
+
@query.find(context.root)
|
308
|
+
end
|
309
|
+
|
310
|
+
def to_s
|
311
|
+
@query.to_s
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# A filter function call.
|
316
|
+
class FunctionExpression < Expression
|
317
|
+
attr_reader :name, :args
|
318
|
+
|
319
|
+
# @param name [String]
|
320
|
+
# @param args [Array<Expression>]
|
321
|
+
def initialize(token, name, args)
|
322
|
+
super(token)
|
323
|
+
@name = name
|
324
|
+
@args = args
|
325
|
+
end
|
326
|
+
|
327
|
+
def evaluate(context)
|
328
|
+
func = context.env.function_extensions.fetch(@name)
|
329
|
+
args = @args.map { |arg| arg.evaluate(context) }
|
330
|
+
unpacked_args = unpack_node_lists(func, args)
|
331
|
+
func.call(*unpacked_args)
|
332
|
+
rescue KeyError
|
333
|
+
:nothing
|
334
|
+
end
|
335
|
+
|
336
|
+
def to_s
|
337
|
+
args = @args.map(&:to_s).join(", ")
|
338
|
+
"#{@name}(#{args})"
|
339
|
+
end
|
340
|
+
|
341
|
+
def ==(other)
|
342
|
+
self.class == other.class &&
|
343
|
+
@name == other.name &&
|
344
|
+
@args == other.args &&
|
345
|
+
@token == other.token
|
346
|
+
end
|
347
|
+
|
348
|
+
alias eql? ==
|
349
|
+
|
350
|
+
def hash
|
351
|
+
[@name, @args, @token].hash
|
352
|
+
end
|
353
|
+
|
354
|
+
private
|
355
|
+
|
356
|
+
# @param func [Proc]
|
357
|
+
# @param args [Array<Object>]
|
358
|
+
# @return [Array<Object>]
|
359
|
+
def unpack_node_lists(func, args) # rubocop:disable Metrics/MethodLength
|
360
|
+
unpacked_args = []
|
361
|
+
args.each_with_index do |arg, i|
|
362
|
+
unless arg.is_a?(JSONPathNodeList) && func.class::ARG_TYPES[i] != ExpressionType::NODES
|
363
|
+
unpacked_args << arg
|
364
|
+
next
|
365
|
+
end
|
366
|
+
|
367
|
+
unpacked_args << case arg.length
|
368
|
+
when 0
|
369
|
+
:nothing
|
370
|
+
when 1
|
371
|
+
arg.first.value
|
372
|
+
else
|
373
|
+
arg
|
374
|
+
end
|
375
|
+
end
|
376
|
+
unpacked_args
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def self.truthy?(obj)
|
381
|
+
return !obj.empty? if obj.is_a?(JSONPathNodeList)
|
382
|
+
return false if obj == :nothing
|
383
|
+
|
384
|
+
obj != false
|
385
|
+
end
|
386
|
+
|
387
|
+
def self.eq?(left, right) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
388
|
+
left = left.first.value if left.is_a?(JSONPathNodeList) && left.length == 1
|
389
|
+
right = right.first.value if right.is_a?(JSONPathNodeList) && right.length == 1
|
390
|
+
|
391
|
+
right, left = left, right if right.is_a?(JSONPathNodeList)
|
392
|
+
|
393
|
+
if left.is_a? JSONPathNodeList
|
394
|
+
return left == right if right.is_a? JSONPathNodeList
|
395
|
+
return right == :nothing if left.empty?
|
396
|
+
return left.first == right if left.length == 1
|
397
|
+
|
398
|
+
return false
|
399
|
+
end
|
400
|
+
|
401
|
+
return true if left == :nothing && right == :nothing
|
402
|
+
|
403
|
+
left == right
|
404
|
+
end
|
405
|
+
|
406
|
+
def self.lt?(left, right) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
|
407
|
+
left = left.first.value if left.is_a?(JSONPathNodeList) && left.length == 1
|
408
|
+
right = right.first.value if right.is_a?(JSONPathNodeList) && right.length == 1
|
409
|
+
return left < right if left.is_a?(String) && right.is_a?(String)
|
410
|
+
return left < right if (left.is_a?(Integer) || left.is_a?(Float)) &&
|
411
|
+
(right.is_a?(Integer) || right.is_a?(Float))
|
412
|
+
|
413
|
+
false
|
414
|
+
end
|
415
|
+
|
416
|
+
# Contextual information and data used for evaluating a filter expression.
|
417
|
+
class FilterContext
|
418
|
+
attr_reader :env, :current, :root
|
419
|
+
|
420
|
+
def initialize(env, current, root)
|
421
|
+
@env = env
|
422
|
+
@current = current
|
423
|
+
@root = root
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONP3
|
4
|
+
class ExpressionType
|
5
|
+
VALUE = :value_expression
|
6
|
+
LOGICAL = :logical_expression
|
7
|
+
NODES = :nodes_expression
|
8
|
+
end
|
9
|
+
|
10
|
+
# Base class for all filter functions.
|
11
|
+
class FunctionExtension
|
12
|
+
def call(*_args, **_kwargs)
|
13
|
+
raise "function extensions must implement `call()`"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../function"
|
4
|
+
|
5
|
+
module JSONP3
|
6
|
+
# The standard `count` function.
|
7
|
+
class Count < FunctionExtension
|
8
|
+
ARG_TYPES = [ExpressionType::NODES].freeze
|
9
|
+
RETURN_TYPE = ExpressionType::VALUE
|
10
|
+
|
11
|
+
def call(node_list)
|
12
|
+
node_list.length
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../function"
|
4
|
+
|
5
|
+
module JSONP3
|
6
|
+
# The standard `length` function.
|
7
|
+
class Length < FunctionExtension
|
8
|
+
ARG_TYPES = [ExpressionType::VALUE].freeze
|
9
|
+
RETURN_TYPE = ExpressionType::VALUE
|
10
|
+
|
11
|
+
def call(obj)
|
12
|
+
return :nothing unless obj.is_a?(Array) || obj.is_a?(Hash) || obj.is_a?(String)
|
13
|
+
|
14
|
+
obj.length
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../cache"
|
4
|
+
require_relative "../function"
|
5
|
+
require_relative "pattern"
|
6
|
+
|
7
|
+
module JSONP3
|
8
|
+
# The standard `match` function.
|
9
|
+
class Match < FunctionExtension
|
10
|
+
ARG_TYPES = [ExpressionType::VALUE, ExpressionType::VALUE].freeze
|
11
|
+
RETURN_TYPE = ExpressionType::LOGICAL
|
12
|
+
|
13
|
+
# @param cache_size [Integer] the maximum size of the regexp cache. Set it to
|
14
|
+
# zero or negative to disable the cache.
|
15
|
+
# @param raise_errors [Boolean] if _false_ (the default), return _false_ when this
|
16
|
+
# function causes a RegexpError instead of raising the exception.
|
17
|
+
def initialize(cache_size = 128, raise_errors: false)
|
18
|
+
super()
|
19
|
+
@cache_size = cache_size
|
20
|
+
@raise_errors = raise_errors
|
21
|
+
@cache = LRUCache.new(cache_size)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param value [String]
|
25
|
+
# @param pattern [String]
|
26
|
+
# @return Boolean
|
27
|
+
def call(value, pattern) # rubocop:disable Metrics/MethodLength
|
28
|
+
return false unless pattern.is_a?(String) && value.is_a?(String)
|
29
|
+
|
30
|
+
if @cache_size.positive?
|
31
|
+
re = @cache[pattern] || Regexp.new(full_match(pattern))
|
32
|
+
else
|
33
|
+
re = Regexp.new(full_match(pattern))
|
34
|
+
@cache[pattern] = re
|
35
|
+
end
|
36
|
+
|
37
|
+
re.match?(value)
|
38
|
+
rescue RegexpError
|
39
|
+
raise if @raise_errors
|
40
|
+
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def full_match(pattern)
|
47
|
+
parts = []
|
48
|
+
explicit_caret = pattern.start_with?("^")
|
49
|
+
explicit_dollar = pattern.end_with?("$")
|
50
|
+
|
51
|
+
# Replace '^' with '\A' and '$' with '\z'
|
52
|
+
pattern = pattern.sub("^", "\\A") if explicit_caret
|
53
|
+
pattern = "#{pattern[..-1]}\\z" if explicit_dollar
|
54
|
+
|
55
|
+
# Wrap with '\A' and '\z' if they are not already part of the pattern.
|
56
|
+
parts << "\\A(?:" if !explicit_caret && !explicit_dollar
|
57
|
+
parts << JSONP3.map_iregexp(pattern)
|
58
|
+
parts << ")\\z" if !explicit_caret && !explicit_dollar
|
59
|
+
parts.join
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONP3 # rubocop:disable Style/Documentation
|
4
|
+
# Map I-Regexp pattern to Ruby regex pattern.
|
5
|
+
# @param pattern [String]
|
6
|
+
# @return [String]
|
7
|
+
def self.map_iregexp(pattern) # rubocop:disable Metrics/MethodLength
|
8
|
+
escaped = false
|
9
|
+
char_class = false
|
10
|
+
mapped = String.new(encoding: "UTF-8")
|
11
|
+
|
12
|
+
pattern.each_char do |c|
|
13
|
+
if escaped
|
14
|
+
mapped << c
|
15
|
+
escaped = false
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
case c
|
20
|
+
when "."
|
21
|
+
# mapped << (char_class ? c : "(?:(?![\\r\\n])\\P{Cs}|\\p{Cs}\\p{Cs})")
|
22
|
+
mapped << (char_class ? c : "[^\\n\\r]")
|
23
|
+
when "\\"
|
24
|
+
escaped = true
|
25
|
+
mapped << "\\"
|
26
|
+
when "["
|
27
|
+
char_class = true
|
28
|
+
mapped << "["
|
29
|
+
when "]"
|
30
|
+
char_class = false
|
31
|
+
mapped << "]"
|
32
|
+
else
|
33
|
+
mapped << c
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
mapped
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../cache"
|
4
|
+
require_relative "../function"
|
5
|
+
require_relative "pattern"
|
6
|
+
|
7
|
+
module JSONP3
|
8
|
+
# The standard `search` function.
|
9
|
+
class Search < FunctionExtension
|
10
|
+
ARG_TYPES = [ExpressionType::VALUE, ExpressionType::VALUE].freeze
|
11
|
+
RETURN_TYPE = ExpressionType::LOGICAL
|
12
|
+
|
13
|
+
# @param cache_size [Integer] the maximum size of the regexp cache. Set it to
|
14
|
+
# zero or negative to disable the cache.
|
15
|
+
# @param raise_errors [Boolean] if _false_ (the default), return _false_ when this
|
16
|
+
# function causes a RegexpError instead of raising the exception.
|
17
|
+
def initialize(cache_size = 128, raise_errors: false)
|
18
|
+
super()
|
19
|
+
@cache_size = cache_size
|
20
|
+
@raise_errors = raise_errors
|
21
|
+
@cache = LRUCache.new(cache_size)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param value [String]
|
25
|
+
# @param pattern [String]
|
26
|
+
# @return Boolean
|
27
|
+
def call(value, pattern) # rubocop:disable Metrics/MethodLength
|
28
|
+
return false unless pattern.is_a?(String) && value.is_a?(String)
|
29
|
+
|
30
|
+
if @cache_size.positive?
|
31
|
+
re = @cache[pattern] || Regexp.new(JSONP3.map_iregexp(pattern))
|
32
|
+
else
|
33
|
+
re = Regexp.new(JSONP3.map_iregexp(pattern))
|
34
|
+
@cache[pattern] = re
|
35
|
+
end
|
36
|
+
|
37
|
+
re.match?(value)
|
38
|
+
rescue RegexpError
|
39
|
+
raise if @raise_errors
|
40
|
+
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../function"
|
4
|
+
|
5
|
+
module JSONP3
|
6
|
+
# The standard `value` function.
|
7
|
+
class Value < FunctionExtension
|
8
|
+
ARG_TYPES = [ExpressionType::NODES].freeze
|
9
|
+
RETURN_TYPE = ExpressionType::VALUE
|
10
|
+
|
11
|
+
def call(node_list)
|
12
|
+
node_list.length == 1 ? node_list.first.value : :nothing
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|