factbase 0.0.50 → 0.0.51

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.
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