dry-logic 0.5.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +222 -17
- data/LICENSE +1 -1
- data/README.md +11 -20
- data/dry-logic.gemspec +36 -20
- data/lib/dry/logic/appliable.rb +2 -0
- data/lib/dry/logic/builder.rb +88 -0
- data/lib/dry/logic/evaluator.rb +1 -1
- data/lib/dry/logic/operations/abstract.rb +4 -6
- data/lib/dry/logic/operations/and.rb +12 -3
- data/lib/dry/logic/operations/attr.rb +1 -1
- data/lib/dry/logic/operations/binary.rb +4 -3
- data/lib/dry/logic/operations/check.rb +3 -5
- data/lib/dry/logic/operations/each.rb +1 -2
- data/lib/dry/logic/operations/implication.rb +1 -2
- data/lib/dry/logic/operations/key.rb +3 -5
- data/lib/dry/logic/operations/negation.rb +1 -2
- data/lib/dry/logic/operations/or.rb +1 -2
- data/lib/dry/logic/operations/set.rb +3 -4
- data/lib/dry/logic/operations/unary.rb +1 -1
- data/lib/dry/logic/operations/xor.rb +1 -2
- data/lib/dry/logic/operators.rb +6 -4
- data/lib/dry/logic/predicates.rb +110 -28
- data/lib/dry/logic/result.rb +2 -4
- data/lib/dry/logic/rule/interface.rb +143 -0
- data/lib/dry/logic/rule/predicate.rb +23 -17
- data/lib/dry/logic/rule.rb +31 -31
- data/lib/dry/logic/rule_compiler.rb +2 -7
- data/lib/dry/logic/version.rb +3 -1
- data/lib/dry/logic.rb +21 -5
- data/lib/dry-logic.rb +3 -1
- metadata +27 -143
- data/.codeclimate.yml +0 -23
- data/.gitignore +0 -7
- data/.rspec +0 -3
- data/.rubocop.yml +0 -16
- data/.rubocop_todo.yml +0 -7
- data/.travis.yml +0 -30
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -17
- data/Rakefile +0 -12
- data/bin/console +0 -10
- data/examples/basic.rb +0 -14
- data/lib/dry/logic/operations.rb +0 -13
- data/spec/integration/result_spec.rb +0 -59
- data/spec/integration/rule_spec.rb +0 -53
- data/spec/shared/predicates.rb +0 -57
- data/spec/shared/rule.rb +0 -67
- data/spec/spec_helper.rb +0 -34
- data/spec/support/mutant.rb +0 -9
- data/spec/unit/operations/and_spec.rb +0 -64
- data/spec/unit/operations/attr_spec.rb +0 -27
- data/spec/unit/operations/check_spec.rb +0 -49
- data/spec/unit/operations/each_spec.rb +0 -47
- data/spec/unit/operations/implication_spec.rb +0 -30
- data/spec/unit/operations/key_spec.rb +0 -133
- data/spec/unit/operations/negation_spec.rb +0 -49
- data/spec/unit/operations/or_spec.rb +0 -73
- data/spec/unit/operations/set_spec.rb +0 -41
- data/spec/unit/operations/xor_spec.rb +0 -61
- data/spec/unit/predicates/array_spec.rb +0 -41
- data/spec/unit/predicates/attr_spec.rb +0 -29
- data/spec/unit/predicates/bool_spec.rb +0 -34
- data/spec/unit/predicates/case_spec.rb +0 -33
- data/spec/unit/predicates/date_spec.rb +0 -31
- data/spec/unit/predicates/date_time_spec.rb +0 -31
- data/spec/unit/predicates/decimal_spec.rb +0 -32
- data/spec/unit/predicates/empty_spec.rb +0 -38
- data/spec/unit/predicates/eql_spec.rb +0 -21
- data/spec/unit/predicates/even_spec.rb +0 -31
- data/spec/unit/predicates/excluded_from_spec.rb +0 -35
- data/spec/unit/predicates/excludes_spec.rb +0 -56
- data/spec/unit/predicates/false_spec.rb +0 -35
- data/spec/unit/predicates/filled_spec.rb +0 -38
- data/spec/unit/predicates/float_spec.rb +0 -31
- data/spec/unit/predicates/format_spec.rb +0 -21
- data/spec/unit/predicates/gt_spec.rb +0 -40
- data/spec/unit/predicates/gteq_spec.rb +0 -40
- data/spec/unit/predicates/included_in_spec.rb +0 -35
- data/spec/unit/predicates/includes_spec.rb +0 -24
- data/spec/unit/predicates/int_spec.rb +0 -34
- data/spec/unit/predicates/key_spec.rb +0 -29
- data/spec/unit/predicates/lt_spec.rb +0 -40
- data/spec/unit/predicates/lteq_spec.rb +0 -40
- data/spec/unit/predicates/max_size_spec.rb +0 -49
- data/spec/unit/predicates/min_size_spec.rb +0 -49
- data/spec/unit/predicates/none_spec.rb +0 -28
- data/spec/unit/predicates/not_eql_spec.rb +0 -21
- data/spec/unit/predicates/number_spec.rb +0 -37
- data/spec/unit/predicates/odd_spec.rb +0 -31
- data/spec/unit/predicates/size_spec.rb +0 -55
- data/spec/unit/predicates/str_spec.rb +0 -32
- data/spec/unit/predicates/time_spec.rb +0 -31
- data/spec/unit/predicates/true_spec.rb +0 -35
- data/spec/unit/predicates/type_spec.rb +0 -35
- data/spec/unit/predicates_spec.rb +0 -23
- data/spec/unit/rule/predicate_spec.rb +0 -53
- data/spec/unit/rule_compiler_spec.rb +0 -127
- data/spec/unit/rule_spec.rb +0 -141
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'dry/logic/evaluator'
|
3
|
-
require 'dry/logic/result'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
3
|
module Dry
|
6
4
|
module Logic
|
@@ -10,13 +8,13 @@ module Dry
|
|
10
8
|
|
11
9
|
attr_reader :path
|
12
10
|
|
13
|
-
def self.new(rules, options)
|
11
|
+
def self.new(rules, **options)
|
14
12
|
if options[:evaluator]
|
15
13
|
super
|
16
14
|
else
|
17
15
|
name = options.fetch(:name)
|
18
16
|
eval = options.fetch(:evaluator, evaluator(name))
|
19
|
-
super(rules, options
|
17
|
+
super(rules, **options, evaluator: eval, path: name)
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'dry/logic/result'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module Dry
|
5
4
|
module Logic
|
@@ -14,7 +13,7 @@ module Dry
|
|
14
13
|
success = results.all?(&:success?)
|
15
14
|
|
16
15
|
Result.new(success, id) do
|
17
|
-
[type, results.select(&:failure?).map
|
16
|
+
[type, results.select(&:failure?).map(&:to_ast)]
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
@@ -27,7 +26,7 @@ module Dry
|
|
27
26
|
end
|
28
27
|
|
29
28
|
def to_s
|
30
|
-
"#{type}(#{rules.map(&:to_s).join(
|
29
|
+
"#{type}(#{rules.map(&:to_s).join(", ")})"
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
data/lib/dry/logic/operators.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Logic
|
3
5
|
module Operators
|
4
6
|
def and(other)
|
5
7
|
Operations::And.new(self, other)
|
6
8
|
end
|
7
|
-
|
9
|
+
alias_method :&, :and
|
8
10
|
|
9
11
|
def or(other)
|
10
12
|
Operations::Or.new(self, other)
|
11
13
|
end
|
12
|
-
|
14
|
+
alias_method :|, :or
|
13
15
|
|
14
16
|
def xor(other)
|
15
17
|
Operations::Xor.new(self, other)
|
16
18
|
end
|
17
|
-
|
19
|
+
alias_method :^, :xor
|
18
20
|
|
19
21
|
def then(other)
|
20
22
|
Operations::Implication.new(self, other)
|
21
23
|
end
|
22
|
-
|
24
|
+
alias_method :>, :then
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
data/lib/dry/logic/predicates.rb
CHANGED
@@ -1,17 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/constants"
|
4
|
+
|
5
|
+
require "bigdecimal"
|
6
|
+
require "bigdecimal/util"
|
7
|
+
require "date"
|
4
8
|
|
5
9
|
module Dry
|
6
10
|
module Logic
|
7
11
|
module Predicates
|
12
|
+
include Dry::Core::Constants
|
13
|
+
|
14
|
+
# rubocop:disable Metrics/ModuleLength
|
8
15
|
module Methods
|
16
|
+
def self.uuid_format(version)
|
17
|
+
::Regexp.new(<<~FORMAT.chomp, ::Regexp::IGNORECASE)
|
18
|
+
\\A[0-9A-F]{8}-[0-9A-F]{4}-#{version}[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\\z
|
19
|
+
FORMAT
|
20
|
+
end
|
21
|
+
|
22
|
+
UUIDv1 = uuid_format(1)
|
23
|
+
|
24
|
+
UUIDv2 = uuid_format(2)
|
25
|
+
|
26
|
+
UUIDv3 = uuid_format(3)
|
27
|
+
|
28
|
+
UUIDv4 = uuid_format(4)
|
29
|
+
|
30
|
+
UUIDv5 = uuid_format(5)
|
31
|
+
|
9
32
|
def [](name)
|
10
33
|
method(name)
|
11
34
|
end
|
12
35
|
|
13
36
|
def type?(type, input)
|
14
|
-
input.
|
37
|
+
input.is_a?(type)
|
15
38
|
end
|
16
39
|
|
17
40
|
def nil?(input)
|
@@ -57,11 +80,9 @@ module Dry
|
|
57
80
|
end
|
58
81
|
|
59
82
|
def number?(input)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
false
|
64
|
-
end
|
83
|
+
true if Float(input)
|
84
|
+
rescue ArgumentError, TypeError
|
85
|
+
false
|
65
86
|
end
|
66
87
|
|
67
88
|
def int?(input)
|
@@ -114,7 +135,7 @@ module Dry
|
|
114
135
|
|
115
136
|
def size?(size, input)
|
116
137
|
case size
|
117
|
-
when Integer then size
|
138
|
+
when Integer then size.equal?(input.size)
|
118
139
|
when Range, Array then size.include?(input.size)
|
119
140
|
else
|
120
141
|
raise ArgumentError, "+#{size}+ is not supported type for size? predicate."
|
@@ -129,13 +150,30 @@ module Dry
|
|
129
150
|
input.size <= num
|
130
151
|
end
|
131
152
|
|
153
|
+
def bytesize?(size, input)
|
154
|
+
case size
|
155
|
+
when Integer then size.equal?(input.bytesize)
|
156
|
+
when Range, Array then size.include?(input.bytesize)
|
157
|
+
else
|
158
|
+
raise ArgumentError, "+#{size}+ is not supported type for bytesize? predicate."
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def min_bytesize?(num, input)
|
163
|
+
input.bytesize >= num
|
164
|
+
end
|
165
|
+
|
166
|
+
def max_bytesize?(num, input)
|
167
|
+
input.bytesize <= num
|
168
|
+
end
|
169
|
+
|
132
170
|
def inclusion?(list, input)
|
133
|
-
|
171
|
+
deprecated(:inclusion?, :included_in?)
|
134
172
|
included_in?(list, input)
|
135
173
|
end
|
136
174
|
|
137
175
|
def exclusion?(list, input)
|
138
|
-
|
176
|
+
deprecated(:exclusion?, :excluded_from?)
|
139
177
|
excluded_from?(list, input)
|
140
178
|
end
|
141
179
|
|
@@ -148,22 +186,23 @@ module Dry
|
|
148
186
|
end
|
149
187
|
|
150
188
|
def includes?(value, input)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
else
|
155
|
-
false
|
156
|
-
end
|
157
|
-
rescue TypeError
|
189
|
+
if input.respond_to?(:include?)
|
190
|
+
input.include?(value)
|
191
|
+
else
|
158
192
|
false
|
159
193
|
end
|
194
|
+
rescue TypeError
|
195
|
+
false
|
160
196
|
end
|
161
197
|
|
162
198
|
def excludes?(value, input)
|
163
199
|
!includes?(value, input)
|
164
200
|
end
|
165
201
|
|
166
|
-
|
202
|
+
# This overrides Object#eql? so we need to make it compatible
|
203
|
+
def eql?(left, right = Undefined)
|
204
|
+
return super(left) if right.equal?(Undefined)
|
205
|
+
|
167
206
|
left.eql?(right)
|
168
207
|
end
|
169
208
|
|
@@ -183,23 +222,65 @@ module Dry
|
|
183
222
|
value.equal?(false)
|
184
223
|
end
|
185
224
|
|
186
|
-
|
187
|
-
|
188
|
-
!regex.match(input).nil?
|
189
|
-
end
|
190
|
-
else
|
191
|
-
def format?(regex, input)
|
192
|
-
regex.match?(input)
|
193
|
-
end
|
225
|
+
def format?(regex, input)
|
226
|
+
!input.nil? && regex.match?(input)
|
194
227
|
end
|
195
228
|
|
196
229
|
def case?(pattern, input)
|
230
|
+
# rubocop:disable Style/CaseEquality
|
197
231
|
pattern === input
|
232
|
+
# rubocop:enable Style/CaseEquality
|
233
|
+
end
|
234
|
+
|
235
|
+
def uuid_v1?(input)
|
236
|
+
format?(UUIDv1, input)
|
237
|
+
end
|
238
|
+
|
239
|
+
def uuid_v2?(input)
|
240
|
+
format?(UUIDv2, input)
|
241
|
+
end
|
242
|
+
|
243
|
+
def uuid_v3?(input)
|
244
|
+
format?(UUIDv3, input)
|
245
|
+
end
|
246
|
+
|
247
|
+
def uuid_v4?(input)
|
248
|
+
format?(UUIDv4, input)
|
249
|
+
end
|
250
|
+
|
251
|
+
def uuid_v5?(input)
|
252
|
+
format?(UUIDv5, input)
|
253
|
+
end
|
254
|
+
|
255
|
+
def uri?(schemes, input)
|
256
|
+
uri_format = URI::DEFAULT_PARSER.make_regexp(schemes)
|
257
|
+
format?(uri_format, input)
|
258
|
+
end
|
259
|
+
|
260
|
+
def uri_rfc3986?(input)
|
261
|
+
format?(URI::RFC3986_Parser::RFC3986_URI, input)
|
262
|
+
end
|
263
|
+
|
264
|
+
# This overrides Object#respond_to? so we need to make it compatible
|
265
|
+
def respond_to?(method, input = Undefined)
|
266
|
+
return super if input.equal?(Undefined)
|
267
|
+
|
268
|
+
input.respond_to?(method)
|
198
269
|
end
|
199
270
|
|
200
271
|
def predicate(name, &block)
|
201
272
|
define_singleton_method(name, &block)
|
202
273
|
end
|
274
|
+
|
275
|
+
def deprecated(name, in_favor_of)
|
276
|
+
Core::Deprecations.warn(
|
277
|
+
"#{name} predicate is deprecated and will " \
|
278
|
+
"be removed in the next major version\n" \
|
279
|
+
"Please use #{in_favor_of} predicate instead",
|
280
|
+
tag: "dry-logic",
|
281
|
+
uplevel: 3
|
282
|
+
)
|
283
|
+
end
|
203
284
|
end
|
204
285
|
|
205
286
|
extend Methods
|
@@ -209,5 +290,6 @@ module Dry
|
|
209
290
|
other.extend(Methods)
|
210
291
|
end
|
211
292
|
end
|
293
|
+
# rubocop:enable Metrics/ModuleLength
|
212
294
|
end
|
213
295
|
end
|
data/lib/dry/logic/result.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Logic
|
5
5
|
class Result
|
6
|
-
include Core::Constants
|
7
|
-
|
8
6
|
SUCCESS = Class.new {
|
9
7
|
def success?
|
10
8
|
true
|
@@ -67,7 +65,7 @@ module Dry
|
|
67
65
|
if args.empty?
|
68
66
|
name.to_s
|
69
67
|
else
|
70
|
-
"#{name}(#{args.map(&:last).map(&:inspect).join(
|
68
|
+
"#{name}(#{args.map(&:last).map(&:inspect).join(", ")})"
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Logic
|
5
|
+
class Rule
|
6
|
+
class Interface < ::Module
|
7
|
+
SPLAT = ["*rest"].freeze
|
8
|
+
|
9
|
+
attr_reader :arity
|
10
|
+
|
11
|
+
attr_reader :curried
|
12
|
+
|
13
|
+
def initialize(arity, curried)
|
14
|
+
super()
|
15
|
+
|
16
|
+
@arity = arity
|
17
|
+
@curried = curried
|
18
|
+
|
19
|
+
if !variable_arity? && curried > arity
|
20
|
+
raise ArgumentError, "wrong number of arguments (#{curried} for #{arity})"
|
21
|
+
end
|
22
|
+
|
23
|
+
define_constructor if curried?
|
24
|
+
|
25
|
+
if constant?
|
26
|
+
define_constant_application
|
27
|
+
else
|
28
|
+
define_application
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def constant?
|
33
|
+
arity.zero?
|
34
|
+
end
|
35
|
+
|
36
|
+
def variable_arity?
|
37
|
+
arity.negative?
|
38
|
+
end
|
39
|
+
|
40
|
+
def curried?
|
41
|
+
!curried.zero?
|
42
|
+
end
|
43
|
+
|
44
|
+
def unapplied
|
45
|
+
if variable_arity?
|
46
|
+
unapplied = arity.abs - 1 - curried
|
47
|
+
|
48
|
+
if unapplied.negative?
|
49
|
+
0
|
50
|
+
else
|
51
|
+
unapplied
|
52
|
+
end
|
53
|
+
else
|
54
|
+
arity - curried
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def name
|
59
|
+
if constant?
|
60
|
+
"Constant"
|
61
|
+
else
|
62
|
+
arity_str =
|
63
|
+
if variable_arity?
|
64
|
+
"Variable#{arity.abs - 1}Arity"
|
65
|
+
else
|
66
|
+
"#{arity}Arity"
|
67
|
+
end
|
68
|
+
|
69
|
+
curried_str =
|
70
|
+
if curried?
|
71
|
+
"#{curried}Curried"
|
72
|
+
else
|
73
|
+
EMPTY_STRING
|
74
|
+
end
|
75
|
+
|
76
|
+
"#{arity_str}#{curried_str}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def define_constructor
|
81
|
+
assignment =
|
82
|
+
if curried.equal?(1)
|
83
|
+
"@arg0 = @args[0]"
|
84
|
+
else
|
85
|
+
"#{curried_args.join(", ")} = @args"
|
86
|
+
end
|
87
|
+
|
88
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
89
|
+
def initialize(*) # def initialize(*)
|
90
|
+
super # super
|
91
|
+
#
|
92
|
+
#{assignment} # @arg0 = @args[0]
|
93
|
+
end # end
|
94
|
+
RUBY
|
95
|
+
end
|
96
|
+
|
97
|
+
def define_constant_application
|
98
|
+
module_exec do
|
99
|
+
def call(*)
|
100
|
+
if @predicate[]
|
101
|
+
Result::SUCCESS
|
102
|
+
else
|
103
|
+
Result.new(false, id) { ast }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def [](*)
|
108
|
+
@predicate[]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def define_application
|
114
|
+
splat = variable_arity? ? SPLAT : EMPTY_ARRAY
|
115
|
+
parameters = (unapplied_args + splat).join(", ")
|
116
|
+
application = "@predicate[#{(curried_args + unapplied_args + splat).join(", ")}]"
|
117
|
+
|
118
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
119
|
+
def call(#{parameters}) # def call(input0, input1, *rest)
|
120
|
+
if #{application} # if @predicate[@arg0, @arg1, input0, input1, *rest]
|
121
|
+
Result::SUCCESS # ::Dry::Logic::Result::Success
|
122
|
+
else # else
|
123
|
+
Result.new(false, id) { ast(#{parameters}) } # ::Dry::Logic::Result.new(false, id) { ast(input0, input1, *rest) }
|
124
|
+
end # end
|
125
|
+
end # end
|
126
|
+
#
|
127
|
+
def [](#{parameters}) # def [](@arg0, @arg1, input0, input1, *rest)
|
128
|
+
#{application} # @predicate[@arg0, @arg1, input0, input1, *rest]
|
129
|
+
end # end
|
130
|
+
RUBY
|
131
|
+
end
|
132
|
+
|
133
|
+
def curried_args
|
134
|
+
@curried_args ||= ::Array.new(curried) { |i| "@arg#{i}" }
|
135
|
+
end
|
136
|
+
|
137
|
+
def unapplied_args
|
138
|
+
@unapplied_args ||= ::Array.new(unapplied) { |i| "input#{i}" }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -1,28 +1,34 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Logic
|
5
|
-
class Rule
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class Rule
|
6
|
+
class Predicate < Rule
|
7
|
+
def self.specialize(arity, curried, base = Predicate)
|
8
|
+
super
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def type
|
12
|
+
:predicate
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def name
|
16
|
+
predicate.name
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
if args.empty?
|
21
|
+
name.to_s
|
22
|
+
else
|
23
|
+
"#{name}(#{args.map(&:inspect).join(", ")})"
|
24
|
+
end
|
19
25
|
end
|
20
|
-
end
|
21
26
|
|
22
|
-
|
23
|
-
|
27
|
+
def ast(input = Undefined)
|
28
|
+
[type, [name, args_with_names(input)]]
|
29
|
+
end
|
30
|
+
alias_method :to_ast, :ast
|
24
31
|
end
|
25
|
-
alias_method :to_ast, :ast
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
data/lib/dry/logic/rule.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require 'dry/logic/result'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/map"
|
5
4
|
|
6
5
|
module Dry
|
7
6
|
module Logic
|
8
7
|
def self.Rule(*args, **options, &block)
|
9
8
|
if args.any?
|
10
|
-
Rule.
|
9
|
+
Rule.build(*args, **options)
|
11
10
|
elsif block
|
12
|
-
Rule.
|
11
|
+
Rule.build(block, **options)
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
@@ -18,8 +17,6 @@ module Dry
|
|
18
17
|
include Dry::Equalizer(:predicate, :options)
|
19
18
|
include Operators
|
20
19
|
|
21
|
-
DEFAULT_OPTIONS = { args: [].freeze }.freeze
|
22
|
-
|
23
20
|
attr_reader :predicate
|
24
21
|
|
25
22
|
attr_reader :options
|
@@ -28,10 +25,27 @@ module Dry
|
|
28
25
|
|
29
26
|
attr_reader :arity
|
30
27
|
|
31
|
-
def
|
28
|
+
def self.interfaces
|
29
|
+
@interfaces ||= ::Concurrent::Map.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.specialize(arity, curried, base = Rule)
|
33
|
+
base.interfaces.fetch_or_store([arity, curried]) do
|
34
|
+
interface = Interface.new(arity, curried)
|
35
|
+
klass = Class.new(base) { include interface }
|
36
|
+
base.const_set("#{base.name.split("::").last}#{interface.name}", klass)
|
37
|
+
klass
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.build(predicate, args: EMPTY_ARRAY, arity: predicate.arity, **options)
|
42
|
+
specialize(arity, args.size).new(predicate, {args: args, arity: arity, **options})
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(predicate, options = EMPTY_HASH)
|
32
46
|
@predicate = predicate
|
33
47
|
@options = options
|
34
|
-
@args = options[:args]
|
48
|
+
@args = options[:args] || EMPTY_ARRAY
|
35
49
|
@arity = options[:arity] || predicate.arity
|
36
50
|
end
|
37
51
|
|
@@ -43,41 +57,27 @@ module Dry
|
|
43
57
|
options[:id]
|
44
58
|
end
|
45
59
|
|
46
|
-
def call(*input)
|
47
|
-
Result.new(self[*input], id) { ast(*input) }
|
48
|
-
end
|
49
|
-
|
50
|
-
def [](*input)
|
51
|
-
arity == 0 ? predicate.() : predicate[*args, *input]
|
52
|
-
end
|
53
|
-
|
54
60
|
def curry(*new_args)
|
55
|
-
|
56
|
-
|
57
|
-
if all_args.size > arity
|
58
|
-
raise ArgumentError, "wrong number of arguments (#{all_args.size} for #{arity})"
|
59
|
-
else
|
60
|
-
with(args: all_args)
|
61
|
-
end
|
61
|
+
with(args: args + new_args)
|
62
62
|
end
|
63
63
|
|
64
64
|
def bind(object)
|
65
|
-
if
|
66
|
-
self.class.
|
65
|
+
if predicate.respond_to?(:bind)
|
66
|
+
self.class.build(predicate.bind(object), **options)
|
67
67
|
else
|
68
|
-
self.class.
|
68
|
+
self.class.build(
|
69
69
|
-> *args { object.instance_exec(*args, &predicate) },
|
70
|
-
options
|
70
|
+
**options, arity: arity, parameters: parameters
|
71
71
|
)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
def eval_args(object)
|
76
|
-
with(args: args.map { |arg| UnboundMethod
|
76
|
+
with(args: args.map { |arg| arg.is_a?(UnboundMethod) ? arg.bind(object).() : arg })
|
77
77
|
end
|
78
78
|
|
79
79
|
def with(new_opts)
|
80
|
-
self.class.
|
80
|
+
self.class.build(predicate, **options, **new_opts)
|
81
81
|
end
|
82
82
|
|
83
83
|
def parameters
|
@@ -1,13 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'dry/logic/rule'
|
4
|
-
require 'dry/logic/rule/predicate'
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
3
|
module Dry
|
7
4
|
module Logic
|
8
5
|
class RuleCompiler
|
9
|
-
include Core::Constants
|
10
|
-
|
11
6
|
attr_reader :predicates
|
12
7
|
|
13
8
|
def initialize(predicates)
|
@@ -52,7 +47,7 @@ module Dry
|
|
52
47
|
|
53
48
|
def visit_predicate(node)
|
54
49
|
name, params = node
|
55
|
-
predicate = Rule::Predicate.
|
50
|
+
predicate = Rule::Predicate.build(predicates[name])
|
56
51
|
|
57
52
|
if params.size > 1
|
58
53
|
args = params.map(&:last).reject { |val| val == Undefined }
|