factbase 0.0.50 → 0.0.51

Sign up to get free protection for your applications and to get access to all the features.
data/lib/factbase/term.rb CHANGED
@@ -20,8 +20,10 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'backtrace'
23
24
  require_relative '../factbase'
24
25
  require_relative 'fact'
26
+ require_relative 'tee'
25
27
 
26
28
  # Term.
27
29
  #
@@ -51,6 +53,33 @@ require_relative 'fact'
51
53
  class Factbase::Term
52
54
  attr_reader :op, :operands
53
55
 
56
+ require_relative 'terms/math'
57
+ include Factbase::Term::Math
58
+
59
+ require_relative 'terms/logical'
60
+ include Factbase::Term::Logical
61
+
62
+ require_relative 'terms/aggregates'
63
+ include Factbase::Term::Aggregates
64
+
65
+ require_relative 'terms/strings'
66
+ include Factbase::Term::Strings
67
+
68
+ require_relative 'terms/meta'
69
+ include Factbase::Term::Meta
70
+
71
+ require_relative 'terms/aliases'
72
+ include Factbase::Term::Aliases
73
+
74
+ require_relative 'terms/ordering'
75
+ include Factbase::Term::Ordering
76
+
77
+ require_relative 'terms/defn'
78
+ include Factbase::Term::Defn
79
+
80
+ require_relative 'terms/debug'
81
+ include Factbase::Term::Debug
82
+
54
83
  # Ctor.
55
84
  # @param [Symbol] operator Operator
56
85
  # @param [Array] operands Operands
@@ -66,9 +95,9 @@ class Factbase::Term
66
95
  def evaluate(fact, maps)
67
96
  send(@op, fact, maps)
68
97
  rescue NoMethodError => e
69
- raise "Term '#{@op}' is not defined at #{self}: #{e.message}"
98
+ raise "Term '#{@op}' is not defined at #{self}:\n#{Backtrace.new(e)}"
70
99
  rescue StandardError => e
71
- raise "#{e.message} at #{self} (#{e.backtrace[0]})"
100
+ raise "#{e.message} at #{self}:\n#{Backtrace.new(e)}"
72
101
  end
73
102
 
74
103
  # Simplify it if possible.
@@ -101,74 +130,6 @@ class Factbase::Term
101
130
 
102
131
  private
103
132
 
104
- def always(_fact, _maps)
105
- assert_args(0)
106
- true
107
- end
108
-
109
- def never(_fact, _maps)
110
- assert_args(0)
111
- false
112
- end
113
-
114
- def not(fact, maps)
115
- assert_args(1)
116
- !only_bool(the_values(0, fact, maps), 0)
117
- end
118
-
119
- def or(fact, maps)
120
- (0..@operands.size - 1).each do |i|
121
- return true if only_bool(the_values(i, fact, maps), i)
122
- end
123
- false
124
- end
125
-
126
- def and(fact, maps)
127
- (0..@operands.size - 1).each do |i|
128
- return false unless only_bool(the_values(i, fact, maps), i)
129
- end
130
- true
131
- end
132
-
133
- def and_or_simplify
134
- strs = []
135
- ops = []
136
- @operands.each do |o|
137
- o = o.simplify
138
- s = o.to_s
139
- next if strs.include?(s)
140
- strs << s
141
- ops << o
142
- end
143
- return ops[0] if ops.size == 1
144
- Factbase::Term.new(@op, ops)
145
- end
146
-
147
- def and_simplify
148
- and_or_simplify
149
- end
150
-
151
- def or_simplify
152
- and_or_simplify
153
- end
154
-
155
- def when(fact, maps)
156
- assert_args(2)
157
- a = @operands[0]
158
- b = @operands[1]
159
- !a.evaluate(fact, maps) || (a.evaluate(fact, maps) && b.evaluate(fact, maps))
160
- end
161
-
162
- def exists(fact, _maps)
163
- assert_args(1)
164
- !by_symbol(0, fact).nil?
165
- end
166
-
167
- def absent(fact, _maps)
168
- assert_args(1)
169
- by_symbol(0, fact).nil?
170
- end
171
-
172
133
  def either(fact, maps)
173
134
  assert_args(2)
174
135
  v = the_values(0, fact, maps)
@@ -187,189 +148,6 @@ class Factbase::Term
187
148
  v[i]
188
149
  end
189
150
 
190
- def prev(fact, maps)
191
- assert_args(1)
192
- before = @prev
193
- v = the_values(0, fact, maps)
194
- @prev = v
195
- before
196
- end
197
-
198
- def unique(fact, _maps)
199
- @uniques = [] if @uniques.nil?
200
- assert_args(1)
201
- vv = by_symbol(0, fact)
202
- return false if vv.nil?
203
- vv = [vv] unless vv.is_a?(Array)
204
- vv.each do |v|
205
- return false if @uniques.include?(v)
206
- @uniques << v
207
- end
208
- true
209
- end
210
-
211
- def many(fact, maps)
212
- assert_args(1)
213
- v = the_values(0, fact, maps)
214
- !v.nil? && v.size > 1
215
- end
216
-
217
- def one(fact, maps)
218
- assert_args(1)
219
- v = the_values(0, fact, maps)
220
- !v.nil? && v.size == 1
221
- end
222
-
223
- def plus(fact, maps)
224
- arithmetic(:+, fact, maps)
225
- end
226
-
227
- def minus(fact, maps)
228
- arithmetic(:-, fact, maps)
229
- end
230
-
231
- def times(fact, maps)
232
- arithmetic(:*, fact, maps)
233
- end
234
-
235
- def div(fact, maps)
236
- arithmetic(:/, fact, maps)
237
- end
238
-
239
- def eq(fact, maps)
240
- cmp(:==, fact, maps)
241
- end
242
-
243
- def lt(fact, maps)
244
- cmp(:<, fact, maps)
245
- end
246
-
247
- def gt(fact, maps)
248
- cmp(:>, fact, maps)
249
- end
250
-
251
- def size(fact, _maps)
252
- assert_args(1)
253
- v = by_symbol(0, fact)
254
- return 0 if v.nil?
255
- return 1 unless v.is_a?(Array)
256
- v.size
257
- end
258
-
259
- def type(fact, _maps)
260
- assert_args(1)
261
- v = by_symbol(0, fact)
262
- return 'nil' if v.nil?
263
- v = v[0] if v.is_a?(Array) && v.size == 1
264
- v.class.to_s
265
- end
266
-
267
- def matches(fact, maps)
268
- assert_args(2)
269
- str = the_values(0, fact, maps)
270
- return false if str.nil?
271
- raise 'Exactly one string expected' unless str.size == 1
272
- re = the_values(1, fact, maps)
273
- raise 'Regexp is nil' if re.nil?
274
- raise 'Exactly one regexp expected' unless re.size == 1
275
- str[0].to_s.match?(re[0])
276
- end
277
-
278
- def cmp(op, fact, maps)
279
- assert_args(2)
280
- lefts = the_values(0, fact, maps)
281
- return false if lefts.nil?
282
- rights = the_values(1, fact, maps)
283
- return false if rights.nil?
284
- lefts.any? do |l|
285
- l = l.floor if l.is_a?(Time) && op == :==
286
- rights.any? do |r|
287
- r = r.floor if r.is_a?(Time) && op == :==
288
- l.send(op, r)
289
- end
290
- end
291
- end
292
-
293
- def arithmetic(op, fact, maps)
294
- assert_args(2)
295
- lefts = the_values(0, fact, maps)
296
- raise 'The first argument is NIL, while literal expected' if lefts.nil?
297
- raise 'Too many values at first position, one expected' unless lefts.size == 1
298
- rights = the_values(1, fact, maps)
299
- raise 'The second argument is NIL, while literal expected' if rights.nil?
300
- raise 'Too many values at second position, one expected' unless rights.size == 1
301
- lefts[0].send(op, rights[0])
302
- end
303
-
304
- def defn(_fact, _maps)
305
- assert_args(2)
306
- fn = @operands[0]
307
- raise "A symbol expected as first argument of 'defn'" unless fn.is_a?(Symbol)
308
- raise "Can't use '#{fn}' name as a term" if Factbase::Term.instance_methods(true).include?(fn)
309
- raise "Term '#{fn}' is already defined" if Factbase::Term.private_instance_methods(false).include?(fn)
310
- raise "The '#{fn}' is a bad name for a term" unless fn.match?(/^[a-z_]+$/)
311
- e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps)\n#{@operands[1]}\nend\nend"
312
- # rubocop:disable Security/Eval
313
- eval(e)
314
- # rubocop:enable Security/Eval
315
- true
316
- end
317
-
318
- def undef(_fact, _maps)
319
- assert_args(1)
320
- fn = @operands[0]
321
- raise "A symbol expected as first argument of 'undef'" unless fn.is_a?(Symbol)
322
- if Factbase::Term.private_instance_methods(false).include?(fn)
323
- Factbase::Term.class_eval("undef :#{fn}", __FILE__, __LINE__ - 1) # undef :foo
324
- end
325
- true
326
- end
327
-
328
- def min(_fact, maps)
329
- @min ||= best(maps) { |v, b| v < b }
330
- end
331
-
332
- def max(_fact, maps)
333
- @max ||= best(maps) { |v, b| v > b }
334
- end
335
-
336
- def count(_fact, maps)
337
- @count ||= maps.size
338
- end
339
-
340
- def sum(_fact, maps)
341
- @sum ||=
342
- begin
343
- k = @operands[0]
344
- raise "A symbol expected, but '#{k}' provided" unless k.is_a?(Symbol)
345
- sum = 0
346
- maps.each do |m|
347
- vv = m[k.to_s]
348
- next if vv.nil?
349
- vv = [vv] unless vv.is_a?(Array)
350
- vv.each do |v|
351
- sum += v
352
- end
353
- end
354
- sum
355
- end
356
- end
357
-
358
- def agg(_fact, maps)
359
- assert_args(2)
360
- selector = @operands[0]
361
- raise "A term expected, but '#{selector}' provided" unless selector.is_a?(Factbase::Term)
362
- term = @operands[1]
363
- raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
364
- subset = maps.select { |m| selector.evaluate(m, maps) }
365
- @agg ||=
366
- if subset.empty?
367
- term.evaluate(Factbase::Fact.new(Mutex.new, {}), subset)
368
- else
369
- term.evaluate(subset.first, subset)
370
- end
371
- end
372
-
373
151
  def assert_args(num)
374
152
  c = @operands.size
375
153
  raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
@@ -378,7 +156,7 @@ class Factbase::Term
378
156
 
379
157
  def by_symbol(pos, fact)
380
158
  o = @operands[pos]
381
- raise "A symbol expected at ##{pos}, but '#{o}' provided" unless o.is_a?(Symbol)
159
+ raise "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided" unless o.is_a?(Symbol)
382
160
  k = o.to_s
383
161
  fact[k]
384
162
  end
@@ -391,28 +169,4 @@ class Factbase::Term
391
169
  v = [v] unless v.is_a?(Array)
392
170
  v
393
171
  end
394
-
395
- def only_bool(val, pos)
396
- val = val[0] if val.is_a?(Array)
397
- return false if val.nil?
398
- unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
399
- raise "Boolean expected, while #{val.class} received from #{@operands[pos]}"
400
- end
401
- val
402
- end
403
-
404
- def best(maps)
405
- k = @operands[0]
406
- raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
407
- best = nil
408
- maps.each do |m|
409
- vv = m[k.to_s]
410
- next if vv.nil?
411
- vv = [vv] unless vv.is_a?(Array)
412
- vv.each do |v|
413
- best = v if best.nil? || yield(v, best)
414
- end
415
- end
416
- best
417
- end
418
172
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../../factbase'
24
+
25
+ # Aggregating terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Aggregates
31
+ def min(_fact, maps)
32
+ assert_args(1)
33
+ best(maps) { |v, b| v < b }
34
+ end
35
+
36
+ def max(_fact, maps)
37
+ assert_args(1)
38
+ best(maps) { |v, b| v > b }
39
+ end
40
+
41
+ def count(_fact, maps)
42
+ maps.size
43
+ end
44
+
45
+ def nth(_fact, maps)
46
+ assert_args(2)
47
+ pos = @operands[0]
48
+ raise "An integer expected, but #{pos} provided" unless pos.is_a?(Integer)
49
+ k = @operands[1]
50
+ raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
51
+ maps[pos][k.to_s]
52
+ end
53
+
54
+ def first(_fact, maps)
55
+ assert_args(1)
56
+ k = @operands[0]
57
+ raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
58
+ first = maps[0]
59
+ return nil if first.nil?
60
+ first[k.to_s]
61
+ end
62
+
63
+ def sum(_fact, maps)
64
+ k = @operands[0]
65
+ raise "A symbol expected, but '#{k}' provided" unless k.is_a?(Symbol)
66
+ sum = 0
67
+ maps.each do |m|
68
+ vv = m[k.to_s]
69
+ next if vv.nil?
70
+ vv = [vv] unless vv.is_a?(Array)
71
+ vv.each do |v|
72
+ sum += v
73
+ end
74
+ end
75
+ sum
76
+ end
77
+
78
+ def agg(fact, maps)
79
+ assert_args(2)
80
+ selector = @operands[0]
81
+ raise "A term expected, but '#{selector}' provided" unless selector.is_a?(Factbase::Term)
82
+ term = @operands[1]
83
+ raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
84
+ subset = maps.select { |m| selector.evaluate(Factbase::Tee.new(Factbase::Fact.new(Mutex.new, m), fact), maps) }
85
+ term.evaluate(nil, subset)
86
+ end
87
+
88
+ def best(maps)
89
+ k = @operands[0]
90
+ raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
91
+ best = nil
92
+ maps.each do |m|
93
+ vv = m[k.to_s]
94
+ next if vv.nil?
95
+ vv = [vv] unless vv.is_a?(Array)
96
+ vv.each do |v|
97
+ best = v if best.nil? || yield(v, best)
98
+ end
99
+ end
100
+ best
101
+ end
102
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../../factbase'
24
+
25
+ # Aliases terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Aliases
31
+ def as(fact, maps)
32
+ assert_args(2)
33
+ a = @operands[0]
34
+ raise "A symbol expected as first argument of 'as'" unless a.is_a?(Symbol)
35
+ vv = the_values(1, fact, maps)
36
+ vv&.each { |v| fact.send("#{a}=", v) }
37
+ true
38
+ end
39
+
40
+ def join(fact, maps)
41
+ assert_args(2)
42
+ mask = @operands[0]
43
+ raise "A string expected as first argument of 'join'" unless mask.is_a?(String)
44
+ term = @operands[1]
45
+ raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
46
+ subset = maps.select { |m| term.evaluate(Factbase::Tee.new(Factbase::Fact.new(Mutex.new, m), fact), maps) }
47
+ subset.each do |m|
48
+ m.each do |k, vv|
49
+ vv.each do |v|
50
+ fact.send("#{mask.gsub('*', k)}=", v)
51
+ end
52
+ end
53
+ end
54
+ true
55
+ end
56
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../../factbase'
24
+
25
+ # Debug terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Debug
31
+ def traced(fact, maps)
32
+ assert_args(1)
33
+ t = @operands[0]
34
+ raise "A term expected, but '#{t}' provided" unless t.is_a?(Factbase::Term)
35
+ r = t.evaluate(fact, maps)
36
+ puts "#{self} -> #{r}"
37
+ r
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../../factbase'
24
+
25
+ # Defn terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Defn
31
+ def defn(_fact, _maps)
32
+ assert_args(2)
33
+ fn = @operands[0]
34
+ raise "A symbol expected as first argument of 'defn'" unless fn.is_a?(Symbol)
35
+ raise "Can't use '#{fn}' name as a term" if Factbase::Term.instance_methods(true).include?(fn)
36
+ raise "Term '#{fn}' is already defined" if Factbase::Term.private_instance_methods(false).include?(fn)
37
+ raise "The '#{fn}' is a bad name for a term" unless fn.match?(/^[a-z_]+$/)
38
+ e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps)\n#{@operands[1]}\nend\nend"
39
+ # rubocop:disable Security/Eval
40
+ eval(e)
41
+ # rubocop:enable Security/Eval
42
+ true
43
+ end
44
+
45
+ def undef(_fact, _maps)
46
+ assert_args(1)
47
+ fn = @operands[0]
48
+ raise "A symbol expected as first argument of 'undef'" unless fn.is_a?(Symbol)
49
+ if Factbase::Term.private_instance_methods(false).include?(fn)
50
+ Factbase::Term.class_eval("undef :#{fn}", __FILE__, __LINE__ - 1) # undef :foo
51
+ end
52
+ true
53
+ end
54
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../../factbase'
24
+
25
+ # Logical terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Logical
31
+ def always(_fact, _maps)
32
+ assert_args(0)
33
+ true
34
+ end
35
+
36
+ def never(_fact, _maps)
37
+ assert_args(0)
38
+ false
39
+ end
40
+
41
+ def not(fact, maps)
42
+ assert_args(1)
43
+ !_only_bool(the_values(0, fact, maps), 0)
44
+ end
45
+
46
+ def or(fact, maps)
47
+ (0..@operands.size - 1).each do |i|
48
+ return true if _only_bool(the_values(i, fact, maps), i)
49
+ end
50
+ false
51
+ end
52
+
53
+ def and(fact, maps)
54
+ (0..@operands.size - 1).each do |i|
55
+ return false unless _only_bool(the_values(i, fact, maps), i)
56
+ end
57
+ true
58
+ end
59
+
60
+ def and_or_simplify
61
+ strs = []
62
+ ops = []
63
+ @operands.each do |o|
64
+ o = o.simplify
65
+ s = o.to_s
66
+ next if strs.include?(s)
67
+ strs << s
68
+ ops << o
69
+ end
70
+ return ops[0] if ops.size == 1
71
+ Factbase::Term.new(@op, ops)
72
+ end
73
+
74
+ def and_simplify
75
+ and_or_simplify
76
+ end
77
+
78
+ def or_simplify
79
+ and_or_simplify
80
+ end
81
+
82
+ def when(fact, maps)
83
+ assert_args(2)
84
+ a = @operands[0]
85
+ b = @operands[1]
86
+ !a.evaluate(fact, maps) || (a.evaluate(fact, maps) && b.evaluate(fact, maps))
87
+ end
88
+
89
+ def _only_bool(val, pos)
90
+ val = val[0] if val.is_a?(Array)
91
+ return false if val.nil?
92
+ return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
93
+ raise "Boolean expected, while #{val.class} received from #{@operands[pos]}"
94
+ end
95
+ end