dry-logic 0.5.0 → 1.5.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 +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 }
|