senko 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +235 -0
- data/Rakefile +56 -0
- data/ext/senko/extconf.rb +5 -0
- data/ext/senko/instructions.c +1 -0
- data/ext/senko/instructions.h +12 -0
- data/ext/senko/senko.c +17 -0
- data/ext/senko/validator.c +107 -0
- data/ext/senko/validator.h +14 -0
- data/lib/senko/cache.rb +57 -0
- data/lib/senko/code_generator.rb +270 -0
- data/lib/senko/compiler/instruction.rb +69 -0
- data/lib/senko/compiler/optimizer.rb +80 -0
- data/lib/senko/compiler/ref_resolver.rb +409 -0
- data/lib/senko/compiler.rb +991 -0
- data/lib/senko/dialect.rb +59 -0
- data/lib/senko/errors.rb +41 -0
- data/lib/senko/format.rb +327 -0
- data/lib/senko/native.rb +11 -0
- data/lib/senko/result.rb +65 -0
- data/lib/senko/schema.rb +58 -0
- data/lib/senko/validator.rb +1391 -0
- data/lib/senko/version.rb +5 -0
- data/lib/senko/vocabulary.rb +25 -0
- data/lib/senko.rb +171 -0
- data/sig/senko.rbs +45 -0
- metadata +170 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
|
|
5
|
+
require_relative 'format'
|
|
6
|
+
|
|
7
|
+
module Senko
|
|
8
|
+
class CodeGenerator
|
|
9
|
+
SUPPORTED_KEYS = %w[
|
|
10
|
+
type enum const minimum maximum exclusiveMinimum exclusiveMaximum multipleOf
|
|
11
|
+
minLength maxLength pattern required properties additionalProperties allOf anyOf oneOf not
|
|
12
|
+
minItems maxItems uniqueItems prefixItems items contains minContains maxContains
|
|
13
|
+
minProperties maxProperties patternProperties propertyNames dependentRequired
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def self.generate(schema)
|
|
17
|
+
new(schema).generate
|
|
18
|
+
rescue UnsupportedSchema
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class UnsupportedSchema < StandardError; end
|
|
23
|
+
|
|
24
|
+
def initialize(schema)
|
|
25
|
+
@schema = deep_stringify(schema)
|
|
26
|
+
@regexps = []
|
|
27
|
+
@constants = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate
|
|
31
|
+
expression = compile_schema(@schema, 'data')
|
|
32
|
+
regexps = @regexps
|
|
33
|
+
constants = @constants
|
|
34
|
+
eval("lambda { |data| #{expression} }", binding, __FILE__, __LINE__)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def compile_schema(schema, variable)
|
|
40
|
+
case schema
|
|
41
|
+
when true
|
|
42
|
+
'true'
|
|
43
|
+
when false
|
|
44
|
+
'false'
|
|
45
|
+
when Hash
|
|
46
|
+
compile_hash_schema(schema, variable)
|
|
47
|
+
else
|
|
48
|
+
raise UnsupportedSchema
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def compile_hash_schema(schema, variable)
|
|
53
|
+
unsupported = schema.keys.map(&:to_s) - SUPPORTED_KEYS
|
|
54
|
+
raise UnsupportedSchema unless unsupported.empty?
|
|
55
|
+
|
|
56
|
+
checks = []
|
|
57
|
+
checks << compile_type(schema['type'], variable) if schema.key?('type')
|
|
58
|
+
checks << compile_enum(schema['enum'], variable) if schema.key?('enum')
|
|
59
|
+
checks << compile_const(schema['const'], variable) if schema.key?('const')
|
|
60
|
+
checks.concat(compile_numeric(schema, variable))
|
|
61
|
+
checks.concat(compile_string(schema, variable))
|
|
62
|
+
checks.concat(compile_array(schema, variable))
|
|
63
|
+
checks.concat(compile_object(schema, variable))
|
|
64
|
+
checks.concat(compile_applicators(schema, variable))
|
|
65
|
+
checks.empty? ? 'true' : checks.join(' && ')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def compile_type(type, variable)
|
|
69
|
+
types = type.is_a?(Array) ? type : [type]
|
|
70
|
+
parts = types.map do |name|
|
|
71
|
+
case name.to_s
|
|
72
|
+
when 'null' then "#{variable}.nil?"
|
|
73
|
+
when 'boolean' then "#{variable} == true || #{variable} == false"
|
|
74
|
+
when 'integer' then "(#{variable}.is_a?(Integer) || (#{variable}.is_a?(Float) && #{variable}.finite? && #{variable} == #{variable}.to_i) || (#{variable}.is_a?(BigDecimal) && #{variable}.finite? && #{variable} == #{variable}.to_i))"
|
|
75
|
+
when 'number' then "#{variable}.is_a?(Integer) || #{variable}.is_a?(Float) || #{variable}.is_a?(BigDecimal)"
|
|
76
|
+
when 'string' then "#{variable}.is_a?(String)"
|
|
77
|
+
when 'array' then "#{variable}.is_a?(Array)"
|
|
78
|
+
when 'object' then "#{variable}.is_a?(Hash)"
|
|
79
|
+
else raise UnsupportedSchema
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
"(#{parts.join(' || ')})"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def compile_enum(values, variable)
|
|
86
|
+
index = push_constant(values)
|
|
87
|
+
"constants[#{index}].any? { |value| value == #{variable} }"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def compile_const(value, variable)
|
|
91
|
+
index = push_constant(value)
|
|
92
|
+
"(constants[#{index}] == #{variable})"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def compile_numeric(schema, variable)
|
|
96
|
+
checks = []
|
|
97
|
+
guard = "(#{variable}.is_a?(Integer) || #{variable}.is_a?(Float) || #{variable}.is_a?(BigDecimal))"
|
|
98
|
+
checks << "(!#{guard} || #{variable} >= #{schema['minimum'].inspect})" if schema.key?('minimum')
|
|
99
|
+
checks << "(!#{guard} || #{variable} > #{schema['exclusiveMinimum'].inspect})" if schema.key?('exclusiveMinimum')
|
|
100
|
+
checks << "(!#{guard} || #{variable} <= #{schema['maximum'].inspect})" if schema.key?('maximum')
|
|
101
|
+
checks << "(!#{guard} || #{variable} < #{schema['exclusiveMaximum'].inspect})" if schema.key?('exclusiveMaximum')
|
|
102
|
+
if schema.key?('multipleOf')
|
|
103
|
+
checks << "(!#{guard} || (BigDecimal(#{variable}.to_s) % BigDecimal(#{schema['multipleOf'].inspect}.to_s)).zero?)"
|
|
104
|
+
end
|
|
105
|
+
checks
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def compile_string(schema, variable)
|
|
109
|
+
checks = []
|
|
110
|
+
guard = "#{variable}.is_a?(String)"
|
|
111
|
+
checks << "(!#{guard} || #{variable}.length >= #{schema['minLength'].to_i})" if schema.key?('minLength')
|
|
112
|
+
checks << "(!#{guard} || #{variable}.length <= #{schema['maxLength'].to_i})" if schema.key?('maxLength')
|
|
113
|
+
if schema.key?('pattern')
|
|
114
|
+
index = push_regexp(Regexp.new(Senko::Format.ecma_pattern_source(schema.fetch('pattern').to_s)))
|
|
115
|
+
checks << "(!#{guard} || #{variable}.match?(regexps[#{index}]))"
|
|
116
|
+
end
|
|
117
|
+
checks
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def compile_object(schema, variable)
|
|
121
|
+
object_checks = []
|
|
122
|
+
return [] unless object_keyword?(schema)
|
|
123
|
+
|
|
124
|
+
object_checks << "#{variable}.length >= #{schema['minProperties'].to_i}" if schema.key?('minProperties')
|
|
125
|
+
object_checks << "#{variable}.length <= #{schema['maxProperties'].to_i}" if schema.key?('maxProperties')
|
|
126
|
+
object_checks.concat(schema.fetch('required', []).map { |key| "#{variable}.key?(#{key.to_s.inspect})" })
|
|
127
|
+
object_checks.concat(compile_properties(schema.fetch('properties', {}), variable))
|
|
128
|
+
object_checks.concat(compile_pattern_properties(schema.fetch('patternProperties', {}), variable))
|
|
129
|
+
object_checks << compile_property_names(schema.fetch('propertyNames'), variable) if schema.key?('propertyNames')
|
|
130
|
+
if schema.key?('dependentRequired')
|
|
131
|
+
object_checks << compile_dependent_required(schema.fetch('dependentRequired'),
|
|
132
|
+
variable)
|
|
133
|
+
end
|
|
134
|
+
object_checks << compile_additional_properties(schema, variable) if schema.key?('additionalProperties')
|
|
135
|
+
["(!#{variable}.is_a?(Hash) || (#{object_checks.join(' && ')}))"]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def compile_array(schema, variable)
|
|
139
|
+
array_checks = []
|
|
140
|
+
return [] unless array_keyword?(schema)
|
|
141
|
+
|
|
142
|
+
array_checks << "#{variable}.length >= #{schema['minItems'].to_i}" if schema.key?('minItems')
|
|
143
|
+
array_checks << "#{variable}.length <= #{schema['maxItems'].to_i}" if schema.key?('maxItems')
|
|
144
|
+
array_checks << "#{variable}.uniq.length == #{variable}.length" if schema['uniqueItems'] == true
|
|
145
|
+
array_checks.concat(compile_prefix_items(schema.fetch('prefixItems', []), variable))
|
|
146
|
+
array_checks << compile_items(schema, variable) if schema.key?('items')
|
|
147
|
+
array_checks << compile_contains(schema, variable) if schema.key?('contains')
|
|
148
|
+
["(!#{variable}.is_a?(Array) || (#{array_checks.join(' && ')}))"]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def array_keyword?(schema)
|
|
152
|
+
%w[minItems maxItems uniqueItems prefixItems items contains].any? { |key| schema.key?(key) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def object_keyword?(schema)
|
|
156
|
+
%w[
|
|
157
|
+
minProperties maxProperties required properties patternProperties additionalProperties
|
|
158
|
+
propertyNames dependentRequired
|
|
159
|
+
].any? { |key| schema.key?(key) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def compile_properties(properties, variable)
|
|
163
|
+
properties.map do |key, subschema|
|
|
164
|
+
child = "#{variable}[#{key.to_s.inspect}]"
|
|
165
|
+
"(!#{variable}.key?(#{key.to_s.inspect}) || (#{compile_schema(subschema, child)}))"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def compile_pattern_properties(pattern_properties, variable)
|
|
170
|
+
pattern_properties.map do |pattern, subschema|
|
|
171
|
+
index = push_regexp(Regexp.new(Senko::Format.ecma_pattern_source(pattern.to_s)))
|
|
172
|
+
"#{variable}.all? { |key, value| !key.match?(regexps[#{index}]) || (#{compile_schema(subschema, 'value')}) }"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def compile_property_names(subschema, variable)
|
|
177
|
+
"#{variable}.keys.all? { |key| #{compile_schema(subschema, 'key')} }"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def compile_dependent_required(requirements, variable)
|
|
181
|
+
requirements.map do |property, dependencies|
|
|
182
|
+
deps = dependencies.map(&:to_s)
|
|
183
|
+
"(!#{variable}.key?(#{property.to_s.inspect}) || #{deps.inspect}.all? { |dependency| #{variable}.key?(dependency) })"
|
|
184
|
+
end.join(' && ')
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def compile_additional_properties(schema, variable)
|
|
188
|
+
value = schema.fetch('additionalProperties')
|
|
189
|
+
return 'true' if value == true
|
|
190
|
+
raise UnsupportedSchema unless value == false
|
|
191
|
+
|
|
192
|
+
known = schema.fetch('properties', {}).keys.map(&:to_s)
|
|
193
|
+
pattern_indexes = schema.fetch('patternProperties', {}).keys.map do |pattern|
|
|
194
|
+
push_regexp(Regexp.new(Senko::Format.ecma_pattern_source(pattern.to_s)))
|
|
195
|
+
end
|
|
196
|
+
pattern_check = pattern_indexes.map { |index| "key.match?(regexps[#{index}])" }.join(' || ')
|
|
197
|
+
allowed = "#{known.inspect}.include?(key)"
|
|
198
|
+
allowed = "(#{allowed} || #{pattern_check})" unless pattern_check.empty?
|
|
199
|
+
"#{variable}.keys.all? { |key| #{allowed} }"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def compile_prefix_items(prefix_items, variable)
|
|
203
|
+
prefix_items.each_with_index.map do |subschema, index|
|
|
204
|
+
"(#{variable}.length <= #{index} || (#{compile_schema(subschema, "#{variable}[#{index}]")}))"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def compile_items(schema, variable)
|
|
209
|
+
start_index = Array(schema['prefixItems']).length
|
|
210
|
+
"#{variable}.each_with_index.all? { |item, index| index < #{start_index} || (#{compile_schema(
|
|
211
|
+
schema.fetch('items'), 'item'
|
|
212
|
+
)}) }"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def compile_contains(schema, variable)
|
|
216
|
+
min = schema.fetch('minContains', 1).to_i
|
|
217
|
+
max = schema.key?('maxContains') ? schema.fetch('maxContains').to_i : nil
|
|
218
|
+
count = "#{variable}.count { |item| #{compile_schema(schema.fetch('contains'), 'item')} }"
|
|
219
|
+
if max
|
|
220
|
+
"((__senko_count = #{count}) >= #{min} && __senko_count <= #{max})"
|
|
221
|
+
else
|
|
222
|
+
"(#{count} >= #{min})"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def compile_applicators(schema, variable)
|
|
227
|
+
checks = []
|
|
228
|
+
if schema.key?('allOf')
|
|
229
|
+
checks << schema['allOf'].map { |subschema|
|
|
230
|
+
"(#{compile_schema(subschema, variable)})"
|
|
231
|
+
}.join(' && ')
|
|
232
|
+
end
|
|
233
|
+
if schema.key?('anyOf')
|
|
234
|
+
checks << schema['anyOf'].map { |subschema|
|
|
235
|
+
"(#{compile_schema(subschema, variable)})"
|
|
236
|
+
}.join(' || ')
|
|
237
|
+
end
|
|
238
|
+
if schema.key?('oneOf')
|
|
239
|
+
checks << "(#{schema['oneOf'].map do |subschema|
|
|
240
|
+
"(#{compile_schema(subschema, variable)} ? 1 : 0)"
|
|
241
|
+
end.join(' + ')} == 1)"
|
|
242
|
+
end
|
|
243
|
+
checks << "!(#{compile_schema(schema['not'], variable)})" if schema.key?('not')
|
|
244
|
+
checks
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def push_regexp(regexp)
|
|
248
|
+
@regexps << regexp
|
|
249
|
+
@regexps.length - 1
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def push_constant(value)
|
|
253
|
+
@constants << value
|
|
254
|
+
@constants.length - 1
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def deep_stringify(value)
|
|
258
|
+
case value
|
|
259
|
+
when Hash
|
|
260
|
+
value.each_with_object({}) do |(key, child), result|
|
|
261
|
+
result[key.to_s] = deep_stringify(child)
|
|
262
|
+
end
|
|
263
|
+
when Array
|
|
264
|
+
value.map { |child| deep_stringify(child) }
|
|
265
|
+
else
|
|
266
|
+
value
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senko
|
|
4
|
+
module Instructions
|
|
5
|
+
TYPE_NULL = 0b0000001
|
|
6
|
+
TYPE_BOOLEAN = 0b0000010
|
|
7
|
+
TYPE_INTEGER = 0b0000100
|
|
8
|
+
TYPE_NUMBER = 0b0001100
|
|
9
|
+
TYPE_STRING = 0b0010000
|
|
10
|
+
TYPE_ARRAY = 0b0100000
|
|
11
|
+
TYPE_OBJECT = 0b1000000
|
|
12
|
+
|
|
13
|
+
TYPE_MAP = {
|
|
14
|
+
'null' => TYPE_NULL,
|
|
15
|
+
'boolean' => TYPE_BOOLEAN,
|
|
16
|
+
'integer' => TYPE_INTEGER,
|
|
17
|
+
'number' => TYPE_NUMBER,
|
|
18
|
+
'string' => TYPE_STRING,
|
|
19
|
+
'array' => TYPE_ARRAY,
|
|
20
|
+
'object' => TYPE_OBJECT
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
KEYWORDS = {
|
|
24
|
+
false_schema: 'false',
|
|
25
|
+
type: 'type',
|
|
26
|
+
enum: 'enum',
|
|
27
|
+
const: 'const',
|
|
28
|
+
multiple_of: 'multipleOf',
|
|
29
|
+
maximum: 'maximum',
|
|
30
|
+
minimum: 'minimum',
|
|
31
|
+
max_length: 'maxLength',
|
|
32
|
+
min_length: 'minLength',
|
|
33
|
+
pattern: 'pattern',
|
|
34
|
+
max_items: 'maxItems',
|
|
35
|
+
min_items: 'minItems',
|
|
36
|
+
unique_items: 'uniqueItems',
|
|
37
|
+
prefix_items: 'prefixItems',
|
|
38
|
+
items: 'items',
|
|
39
|
+
contains: 'contains',
|
|
40
|
+
max_properties: 'maxProperties',
|
|
41
|
+
min_properties: 'minProperties',
|
|
42
|
+
required: 'required',
|
|
43
|
+
properties: 'properties',
|
|
44
|
+
pattern_properties: 'patternProperties',
|
|
45
|
+
additional_properties: 'additionalProperties',
|
|
46
|
+
property_names: 'propertyNames',
|
|
47
|
+
dependent_required: 'dependentRequired',
|
|
48
|
+
dependent_schemas: 'dependentSchemas',
|
|
49
|
+
all_of: 'allOf',
|
|
50
|
+
any_of: 'anyOf',
|
|
51
|
+
one_of: 'oneOf',
|
|
52
|
+
not: 'not',
|
|
53
|
+
if_then_else: 'if',
|
|
54
|
+
discriminator: 'discriminator',
|
|
55
|
+
ref: '$ref',
|
|
56
|
+
dynamic_ref: '$dynamicRef',
|
|
57
|
+
dynamic_scope: '$dynamicAnchor',
|
|
58
|
+
unevaluated_properties: 'unevaluatedProperties',
|
|
59
|
+
unevaluated_items: 'unevaluatedItems',
|
|
60
|
+
format: 'format'
|
|
61
|
+
}.freeze
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Instruction = Struct.new(:op, :payload, :keyword_location, :schema, keyword_init: true) do
|
|
65
|
+
def keyword
|
|
66
|
+
Instructions::KEYWORDS.fetch(op, op.to_s)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senko
|
|
4
|
+
class Compiler
|
|
5
|
+
class Optimizer
|
|
6
|
+
def optimize(instructions, seen = {})
|
|
7
|
+
return instructions if seen[instructions.object_id]
|
|
8
|
+
|
|
9
|
+
seen[instructions.object_id] = true
|
|
10
|
+
begin
|
|
11
|
+
instructions.flat_map do |instruction|
|
|
12
|
+
optimized = optimize_instruction(instruction, seen)
|
|
13
|
+
next [] if removable?(optimized)
|
|
14
|
+
|
|
15
|
+
inlineable?(optimized) ? optimized.payload[:schemas].first : [optimized]
|
|
16
|
+
end
|
|
17
|
+
ensure
|
|
18
|
+
seen.delete(instructions.object_id)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def optimize_instruction(instruction, seen)
|
|
25
|
+
payload = optimize_payload(instruction.payload, seen)
|
|
26
|
+
instruction.class.new(
|
|
27
|
+
op: instruction.op,
|
|
28
|
+
payload: payload,
|
|
29
|
+
keyword_location: instruction.keyword_location,
|
|
30
|
+
schema: instruction.schema
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def optimize_payload(payload, seen)
|
|
35
|
+
return payload unless payload.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
payload.transform_values do |value|
|
|
38
|
+
case value
|
|
39
|
+
when Array
|
|
40
|
+
optimize_array_value(value, seen)
|
|
41
|
+
when Hash
|
|
42
|
+
optimize_hash_value(value, seen)
|
|
43
|
+
else
|
|
44
|
+
value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def optimize_array_value(value, seen)
|
|
50
|
+
if value.all?(Instruction)
|
|
51
|
+
optimize(value, seen)
|
|
52
|
+
elsif value.all? { |item| instruction_array?(item) }
|
|
53
|
+
value.map { |item| optimize(item, seen) }
|
|
54
|
+
else
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def optimize_hash_value(value, seen)
|
|
60
|
+
value.transform_values do |child|
|
|
61
|
+
instruction_array?(child) ? optimize(child, seen) : child
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def instruction_array?(value)
|
|
66
|
+
value.is_a?(Array) && value.all?(Instruction)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def removable?(instruction)
|
|
70
|
+
(instruction.op == :min_length && instruction.payload[:limit].zero?) ||
|
|
71
|
+
(instruction.op == :min_items && instruction.payload[:limit].zero?) ||
|
|
72
|
+
(instruction.op == :min_properties && instruction.payload[:limit].zero?)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def inlineable?(_instruction)
|
|
76
|
+
false
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|