factbase 0.0.50 → 0.0.52

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,91 @@
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
+ # Math terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Math
31
+ def plus(fact, maps)
32
+ arithmetic(:+, fact, maps)
33
+ end
34
+
35
+ def minus(fact, maps)
36
+ arithmetic(:-, fact, maps)
37
+ end
38
+
39
+ def times(fact, maps)
40
+ arithmetic(:*, fact, maps)
41
+ end
42
+
43
+ def div(fact, maps)
44
+ arithmetic(:/, fact, maps)
45
+ end
46
+
47
+ def zero(fact, maps)
48
+ assert_args(1)
49
+ vv = the_values(0, fact, maps)
50
+ return false if vv.nil?
51
+ vv.any? { |v| (v.is_a?(Integer) || v.is_a?(Float)) && v.zero? }
52
+ end
53
+
54
+ def eq(fact, maps)
55
+ cmp(:==, fact, maps)
56
+ end
57
+
58
+ def lt(fact, maps)
59
+ cmp(:<, fact, maps)
60
+ end
61
+
62
+ def gt(fact, maps)
63
+ cmp(:>, fact, maps)
64
+ end
65
+
66
+ def cmp(op, fact, maps)
67
+ assert_args(2)
68
+ lefts = the_values(0, fact, maps)
69
+ return false if lefts.nil?
70
+ rights = the_values(1, fact, maps)
71
+ return false if rights.nil?
72
+ lefts.any? do |l|
73
+ l = l.floor if l.is_a?(Time) && op == :==
74
+ rights.any? do |r|
75
+ r = r.floor if r.is_a?(Time) && op == :==
76
+ l.send(op, r)
77
+ end
78
+ end
79
+ end
80
+
81
+ def arithmetic(op, fact, maps)
82
+ assert_args(2)
83
+ lefts = the_values(0, fact, maps)
84
+ return nil if lefts.nil?
85
+ raise 'Too many values at first position, one expected' unless lefts.size == 1
86
+ rights = the_values(1, fact, maps)
87
+ return nil if rights.nil?
88
+ raise 'Too many values at second position, one expected' unless rights.size == 1
89
+ lefts[0].send(op, rights[0])
90
+ end
91
+ end
@@ -0,0 +1,73 @@
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
+ # Meta terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Meta
31
+ def exists(fact, _maps)
32
+ assert_args(1)
33
+ !by_symbol(0, fact).nil?
34
+ end
35
+
36
+ def absent(fact, _maps)
37
+ assert_args(1)
38
+ by_symbol(0, fact).nil?
39
+ end
40
+
41
+ def size(fact, _maps)
42
+ assert_args(1)
43
+ v = by_symbol(0, fact)
44
+ return 0 if v.nil?
45
+ return 1 unless v.is_a?(Array)
46
+ v.size
47
+ end
48
+
49
+ def type(fact, _maps)
50
+ assert_args(1)
51
+ v = by_symbol(0, fact)
52
+ return 'nil' if v.nil?
53
+ v = v[0] if v.is_a?(Array) && v.size == 1
54
+ v.class.to_s
55
+ end
56
+
57
+ def nil(fact, maps)
58
+ assert_args(1)
59
+ the_values(0, fact, maps).nil?
60
+ end
61
+
62
+ def many(fact, maps)
63
+ assert_args(1)
64
+ v = the_values(0, fact, maps)
65
+ !v.nil? && v.size > 1
66
+ end
67
+
68
+ def one(fact, maps)
69
+ assert_args(1)
70
+ v = the_values(0, fact, maps)
71
+ !v.nil? && v.size == 1
72
+ end
73
+ end
@@ -0,0 +1,51 @@
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
+ # Ordering terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Ordering
31
+ def prev(fact, maps)
32
+ assert_args(1)
33
+ before = @prev
34
+ v = the_values(0, fact, maps)
35
+ @prev = v
36
+ before
37
+ end
38
+
39
+ def unique(fact, _maps)
40
+ @uniques = [] if @uniques.nil?
41
+ assert_args(1)
42
+ vv = by_symbol(0, fact)
43
+ return false if vv.nil?
44
+ vv = [vv] unless vv.is_a?(Array)
45
+ vv.each do |v|
46
+ return false if @uniques.include?(v)
47
+ @uniques << v
48
+ end
49
+ true
50
+ end
51
+ end
@@ -0,0 +1,41 @@
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
+ # String terms.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Factbase::Term::Strings
31
+ def matches(fact, maps)
32
+ assert_args(2)
33
+ str = the_values(0, fact, maps)
34
+ return false if str.nil?
35
+ raise 'Exactly one string expected' unless str.size == 1
36
+ re = the_values(1, fact, maps)
37
+ raise 'Regexp is nil' if re.nil?
38
+ raise 'Exactly one regexp expected' unless re.size == 1
39
+ str[0].to_s.match?(re[0])
40
+ end
41
+ end
@@ -59,11 +59,11 @@ class Factbase::ToXML
59
59
  if vv.is_a?(Array)
60
60
  xml.send(:"#{k}_") do
61
61
  vv.each do |v|
62
- xml.send(:v, to_str(v))
62
+ xml.send(:v, to_str(v), t: type_of(v))
63
63
  end
64
64
  end
65
65
  else
66
- xml.send(:"#{k}_", to_str(vv))
66
+ xml.send(:"#{k}_", to_str(vv), t: type_of(vv))
67
67
  end
68
68
  end
69
69
  end
@@ -81,4 +81,8 @@ class Factbase::ToXML
81
81
  val.to_s
82
82
  end
83
83
  end
84
+
85
+ def type_of(val)
86
+ val.class.to_s[0]
87
+ end
84
88
  end
data/lib/factbase.rb CHANGED
@@ -74,12 +74,14 @@ require 'yaml'
74
74
  # It's important to use +binwrite+ and +binread+, because the content is
75
75
  # a chain of bytes, not a text.
76
76
  #
77
+ # It is NOT thread-safe!
78
+ #
77
79
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
78
80
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
79
81
  # License:: MIT
80
82
  class Factbase
81
83
  # Current version of the gem (changed by .rultor.yml on every release)
82
- VERSION = '0.0.50'
84
+ VERSION = '0.0.52'
83
85
 
84
86
  # An exception that may be thrown in a transaction, to roll it back.
85
87
  class Rollback < StandardError; end
@@ -0,0 +1,76 @@
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 'minitest/autorun'
24
+ require_relative '../../../lib/factbase/term'
25
+ require_relative '../../../lib/factbase/syntax'
26
+
27
+ # Aggregates test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestAggregates < Minitest::Test
32
+ def test_aggregation
33
+ maps = [
34
+ { 'x' => [1], 'y' => [0], 'z' => [4] },
35
+ { 'x' => [2], 'y' => [42], 'z' => [3] },
36
+ { 'x' => [3], 'y' => [42], 'z' => [5] },
37
+ { 'x' => [4], 'y' => [42], 'z' => [2] },
38
+ { 'x' => [5], 'y' => [42], 'z' => [1] },
39
+ { 'x' => [8], 'y' => [0], 'z' => [6] }
40
+ ]
41
+ {
42
+ '(eq x (agg (eq y 42) (min x)))' => '(eq x 2)',
43
+ '(eq z (agg (eq y 0) (max z)))' => '(eq x 8)',
44
+ '(eq x (agg (and (eq y 42) (gt z 1)) (max x)))' => '(eq x 4)',
45
+ '(and (eq x (agg (eq y 42) (min x))) (eq z 3))' => '(eq x 2)',
46
+ '(eq x (agg (eq y 0) (nth 0 x)))' => '(eq x 1)',
47
+ '(eq x (agg (eq y 0) (first x)))' => '(eq x 1)',
48
+ '(agg (eq foo 42) (always))' => '(eq x 1)'
49
+ }.each do |q, r|
50
+ t = Factbase::Syntax.new(q).to_term
51
+ f = maps.find { |m| t.evaluate(fact(m), maps) }
52
+ assert(!f.nil?, "nothing found by: #{q}")
53
+ assert(Factbase::Syntax.new(r).to_term.evaluate(fact(f), []))
54
+ end
55
+ end
56
+
57
+ def test_empty
58
+ maps = [
59
+ { 'x' => [1], 'y' => [0], 'z' => [4] },
60
+ { 'x' => [8], 'y' => [0] }
61
+ ]
62
+ {
63
+ '(empty (eq y 42))' => true,
64
+ '(empty (eq x 1))' => false
65
+ }.each do |q, r|
66
+ t = Factbase::Syntax.new(q).to_term
67
+ assert_equal(r, t.evaluate(nil, maps), q)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def fact(map = {})
74
+ Factbase::Fact.new(Mutex.new, map)
75
+ end
76
+ end
@@ -0,0 +1,79 @@
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 'minitest/autorun'
24
+ require_relative '../../../lib/factbase/term'
25
+ require_relative '../../../lib/factbase/syntax'
26
+ require_relative '../../../lib/factbase/accum'
27
+
28
+ # Aliases test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestAliases < Minitest::Test
33
+ def test_aliases
34
+ maps = [
35
+ { 'x' => [1], 'y' => [0] },
36
+ { 'x' => [2], 'y' => [42] }
37
+ ]
38
+ {
39
+ '(as foo (plus x 1))' => '(exists foo)',
40
+ '(as foo (plus x y))' => '(gt foo 0)',
41
+ '(as foo (plus bar 1))' => '(absent foo)'
42
+ }.each do |q, r|
43
+ t = Factbase::Syntax.new(q).to_term
44
+ maps.each do |m|
45
+ f = Factbase::Accum.new(fact(m), {}, false)
46
+ next unless t.evaluate(f, maps)
47
+ assert(Factbase::Syntax.new(r).to_term.evaluate(f, []), "#{q} -> #{f}")
48
+ end
49
+ end
50
+ end
51
+
52
+ def test_join
53
+ maps = [
54
+ { 'x' => 1, 'y' => 0, 'z' => 4 },
55
+ { 'x' => [2], 'bar' => [44, 55, 66] }
56
+ ]
57
+ {
58
+ '(join "foo_x<=x" (gt x 1))' => '(exists foo_x)',
59
+ '(join "foo <=bar " (exists bar))' => '(and (eq foo 44) (eq foo 55))',
60
+ '(join "uuu" (eq x 1))' => '(absent uuu)',
61
+ '(join "uuu <= fff" (eq fff 1))' => '(absent uuu)'
62
+ }.each do |q, r|
63
+ t = Factbase::Syntax.new(q).to_term
64
+ maps.each do |m|
65
+ f = Factbase::Accum.new(fact(m), {}, false)
66
+ require_relative '../../../lib/factbase/looged'
67
+ f = Factbase::Looged::Fact.new(f, Loog::NULL)
68
+ next unless t.evaluate(f, maps)
69
+ assert(Factbase::Syntax.new(r).to_term.evaluate(f, []), "#{q} -> #{f} doesn't match #{r}")
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def fact(map = {})
77
+ Factbase::Fact.new(Mutex.new, map)
78
+ end
79
+ end
@@ -0,0 +1,99 @@
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 'minitest/autorun'
24
+ require_relative '../../../lib/factbase/term'
25
+
26
+ # Aggregates test.
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
+ # License:: MIT
30
+ class TestMath < Minitest::Test
31
+ def test_simple
32
+ t = Factbase::Term.new(:eq, [:foo, 42])
33
+ assert(t.evaluate(fact('foo' => [42]), []))
34
+ assert(!t.evaluate(fact('foo' => 'Hello!'), []))
35
+ assert(!t.evaluate(fact('bar' => ['Hello!']), []))
36
+ end
37
+
38
+ def test_zero
39
+ t = Factbase::Term.new(:zero, [:foo])
40
+ assert(t.evaluate(fact('foo' => [0]), []))
41
+ assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 0, 9, 'fdsf']), []))
42
+ assert(!t.evaluate(fact('foo' => [100]), []))
43
+ assert(!t.evaluate(fact('foo' => []), []))
44
+ assert(!t.evaluate(fact('bar' => []), []))
45
+ end
46
+
47
+ def test_eq
48
+ t = Factbase::Term.new(:eq, [:foo, 42])
49
+ assert(t.evaluate(fact('foo' => 42), []))
50
+ assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf']), []))
51
+ assert(!t.evaluate(fact('foo' => [100]), []))
52
+ assert(!t.evaluate(fact('foo' => []), []))
53
+ assert(!t.evaluate(fact('bar' => []), []))
54
+ end
55
+
56
+ def test_eq_time
57
+ now = Time.now
58
+ t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
59
+ assert(t.evaluate(fact('foo' => now), []))
60
+ assert(t.evaluate(fact('foo' => [now, Time.now]), []))
61
+ end
62
+
63
+ def test_lt
64
+ t = Factbase::Term.new(:lt, [:foo, 42])
65
+ assert(t.evaluate(fact('foo' => [10]), []))
66
+ assert(!t.evaluate(fact('foo' => [100]), []))
67
+ assert(!t.evaluate(fact('foo' => 100), []))
68
+ assert(!t.evaluate(fact('bar' => 100), []))
69
+ end
70
+
71
+ def test_gt
72
+ t = Factbase::Term.new(:gt, [:foo, 42])
73
+ assert(t.evaluate(fact('foo' => [100]), []))
74
+ assert(t.evaluate(fact('foo' => 100), []))
75
+ assert(!t.evaluate(fact('foo' => [10]), []))
76
+ assert(!t.evaluate(fact('foo' => 10), []))
77
+ assert(!t.evaluate(fact('bar' => 10), []))
78
+ end
79
+
80
+ def test_lt_time
81
+ t = Factbase::Term.new(:lt, [:foo, Time.now])
82
+ assert(t.evaluate(fact('foo' => [Time.now - 100]), []))
83
+ assert(!t.evaluate(fact('foo' => [Time.now + 100]), []))
84
+ assert(!t.evaluate(fact('bar' => [100]), []))
85
+ end
86
+
87
+ def test_gt_time
88
+ t = Factbase::Term.new(:gt, [:foo, Time.now])
89
+ assert(t.evaluate(fact('foo' => [Time.now + 100]), []))
90
+ assert(!t.evaluate(fact('foo' => [Time.now - 100]), []))
91
+ assert(!t.evaluate(fact('bar' => [100]), []))
92
+ end
93
+
94
+ private
95
+
96
+ def fact(map = {})
97
+ Factbase::Fact.new(Mutex.new, map)
98
+ end
99
+ end