factbase 0.0.49 → 0.0.51
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +18 -25
- data/README.md +52 -35
- data/factbase.gemspec +1 -0
- data/lib/factbase/{tuples.rb → accum.rb} +39 -40
- data/lib/factbase/fact.rb +4 -9
- data/lib/factbase/looged.rb +11 -7
- data/lib/factbase/query.rb +7 -2
- data/lib/factbase/rules.rb +35 -12
- data/lib/factbase/syntax.rb +2 -2
- data/lib/factbase/tee.rb +57 -0
- data/lib/factbase/term.rb +32 -278
- data/lib/factbase/terms/aggregates.rb +102 -0
- data/lib/factbase/terms/aliases.rb +56 -0
- data/lib/factbase/terms/debug.rb +39 -0
- data/lib/factbase/terms/defn.rb +54 -0
- data/lib/factbase/terms/logical.rb +95 -0
- data/lib/factbase/terms/math.rb +91 -0
- data/lib/factbase/terms/meta.rb +73 -0
- data/lib/factbase/terms/ordering.rb +51 -0
- data/lib/factbase/terms/strings.rb +41 -0
- data/lib/factbase/to_xml.rb +6 -2
- data/lib/factbase.rb +3 -1
- data/test/factbase/terms/test_aggregates.rb +62 -0
- data/test/factbase/terms/test_aliases.rb +76 -0
- data/test/factbase/terms/test_math.rb +99 -0
- data/test/factbase/test_accum.rb +70 -0
- data/test/factbase/test_looged.rb +1 -0
- data/test/factbase/test_query.rb +23 -8
- data/test/factbase/test_rules.rb +14 -1
- data/test/factbase/test_syntax.rb +1 -1
- data/test/factbase/test_tee.rb +43 -0
- data/test/factbase/test_term.rb +7 -76
- data/test/factbase/test_to_xml.rb +26 -2
- data/test/test_factbase.rb +3 -3
- metadata +32 -4
- data/test/factbase/test_tuples.rb +0 -106
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}
|
98
|
+
raise "Term '#{@op}' is not defined at #{self}:\n#{Backtrace.new(e)}"
|
70
99
|
rescue StandardError => e
|
71
|
-
raise "#{e.message} at #{self}
|
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
|