factbase 0.0.34 → 0.0.35

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20c84ac9e67311e6333da8c6da3d69ae445aad64686017a69bc702f50d703223
4
- data.tar.gz: c275ca48a224b42f09e71551b563585036b866f99acf8dd58e13311bdaa0beec
3
+ metadata.gz: fc97bfbf967aa07fa189c2d3c696eb8d963f4647c06fb5d97ca4b0e2b4edb4db
4
+ data.tar.gz: b38987779b663f76cbf9876b4e117cd1fabb3e1c2ef07bd6ab433be0dede9a44
5
5
  SHA512:
6
- metadata.gz: 1ef21c5e33b3cf5f67e7f5edb56110e9f4d62c970cbc52158ca3922661d9a3d3ed411c26b95b2be97c1d6b6a4a5ebb5c521c87401325da42e585bd61f31b403f
7
- data.tar.gz: 9f02e0addedcff827c76f7c5a58cae70c84b73c02522ab429ba0607bf967f0412c8a82ba1b8caa49c0c0329498f0f440dc95e861fed563362bd1f7295bea52ba
6
+ metadata.gz: 97cdc3269d4fbfea8d7246f6cee197be5bdf8b1e463573940b9be2384cbc2718a454e7f1291c5efc11554fc59c84a97c9349def6413d0ee54192118820cdb252
7
+ data.tar.gz: '049a1b2ae928bae38e1079f4e67d4f29a3efe3201e27c28c92f58bf4a2eda519a62a9a18095d857792337b19775e319921f8494643718a406bf6c377e36eefa6'
data/lib/factbase/fact.rb CHANGED
@@ -25,6 +25,9 @@ require 'time'
25
25
  require_relative '../factbase'
26
26
 
27
27
  # Fact.
28
+ #
29
+ # This is an internal class, it is not supposed to be instantiated directly.
30
+ #
28
31
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
32
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
33
  # License:: MIT
@@ -37,7 +40,7 @@ class Factbase::Fact
37
40
  # Convert it to a string.
38
41
  # @return [String] String representation of it (in JSON)
39
42
  def to_s
40
- @map.to_json
43
+ "[ #{@map.map { |k, v| "#{k}: #{v}" }.join(', ')} ]"
41
44
  end
42
45
 
43
46
  # When a method is missing, this method is called.
data/lib/factbase/inv.rb CHANGED
@@ -61,6 +61,9 @@ class Factbase::Inv
61
61
  end
62
62
 
63
63
  # Fact decorator.
64
+ #
65
+ # This is an internal class, it is not supposed to be instantiated directly.
66
+ #
64
67
  class Fact
65
68
  def initialize(fact, block)
66
69
  @fact = fact
@@ -89,6 +92,9 @@ class Factbase::Inv
89
92
  end
90
93
 
91
94
  # Query decorator.
95
+ #
96
+ # This is an internal class, it is not supposed to be instantiated directly.
97
+ #
92
98
  class Query
93
99
  def initialize(query, block)
94
100
  @query = query
@@ -63,6 +63,9 @@ class Factbase::Looged
63
63
  end
64
64
 
65
65
  # Fact decorator.
66
+ #
67
+ # This is an internal class, it is not supposed to be instantiated directly.
68
+ #
66
69
  class Fact
67
70
  MAX_LENGTH = 64
68
71
 
@@ -98,6 +101,9 @@ class Factbase::Looged
98
101
  end
99
102
 
100
103
  # Query decorator.
104
+ #
105
+ # This is an internal class, it is not supposed to be instantiated directly.
106
+ #
101
107
  class Query
102
108
  def initialize(query, expr, loog)
103
109
  @query = query
@@ -25,6 +25,9 @@ require_relative 'syntax'
25
25
  require_relative 'fact'
26
26
 
27
27
  # Query.
28
+ #
29
+ # This is an internal class, it is not supposed to be instantiated directly.
30
+ #
28
31
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
32
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
33
  # License:: MIT
@@ -40,11 +43,13 @@ class Factbase::Query
40
43
  # @return [Integer] Total number of facts yielded
41
44
  def each
42
45
  return to_enum(__method__) unless block_given?
43
- term = Factbase::Syntax.new(@query).to_term.on(@maps)
46
+ term = Factbase::Syntax.new(@query).to_term
44
47
  yielded = 0
45
48
  @maps.each do |m|
46
49
  f = Factbase::Fact.new(@mutex, m)
47
- next unless term.evaluate(f)
50
+ r = term.evaluate(f, @maps)
51
+ raise 'Unexpected evaluation result, must be boolean' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
52
+ next unless r
48
53
  yield f
49
54
  yielded += 1
50
55
  end
@@ -59,7 +64,7 @@ class Factbase::Query
59
64
  @mutex.synchronize do
60
65
  @maps.delete_if do |m|
61
66
  f = Factbase::Fact.new(@mutex, m)
62
- if term.evaluate(f)
67
+ if term.evaluate(f, @maps)
63
68
  deleted += 1
64
69
  true
65
70
  else
@@ -62,6 +62,9 @@ class Factbase::Rules
62
62
  end
63
63
 
64
64
  # Fact decorator.
65
+ #
66
+ # This is an internal class, it is not supposed to be instantiated directly.
67
+ #
65
68
  class Fact
66
69
  def initialize(fact, check)
67
70
  @fact = fact
@@ -91,6 +94,9 @@ class Factbase::Rules
91
94
  end
92
95
 
93
96
  # Query decorator.
97
+ #
98
+ # This is an internal class, it is not supposed to be instantiated directly.
99
+ #
94
100
  class Query
95
101
  def initialize(query, check)
96
102
  @query = query
@@ -110,6 +116,9 @@ class Factbase::Rules
110
116
  end
111
117
 
112
118
  # Check one fact.
119
+ #
120
+ # This is an internal class, it is not supposed to be instantiated directly.
121
+ #
113
122
  class Check
114
123
  def initialize(fb, expr)
115
124
  @fb = fb
@@ -117,7 +126,7 @@ class Factbase::Rules
117
126
  end
118
127
 
119
128
  def it(fact)
120
- return if Factbase::Syntax.new(@expr).to_term.evaluate(fact)
129
+ return if Factbase::Syntax.new(@expr).to_term.evaluate(fact, [])
121
130
  raise "The fact is in invalid state: #{fact}"
122
131
  end
123
132
  end
@@ -26,6 +26,9 @@ require_relative 'fact'
26
26
  require_relative 'term'
27
27
 
28
28
  # Syntax.
29
+ #
30
+ # This is an internal class, it is not supposed to be instantiated directly.
31
+ #
29
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
33
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
34
  # License:: MIT
data/lib/factbase/term.rb CHANGED
@@ -24,6 +24,9 @@ require_relative '../factbase'
24
24
  require_relative 'fact'
25
25
 
26
26
  # Term.
27
+ #
28
+ # This is an internal class, it is not supposed to be instantiated directly.
29
+ #
27
30
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
31
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
32
  # License:: MIT
@@ -40,23 +43,12 @@ class Factbase::Term
40
43
 
41
44
  # Does it match the fact?
42
45
  # @param [Factbase::Fact] fact The fact
46
+ # @param [Array<Factbase::Fact>] maps All maps available
43
47
  # @return [bool] TRUE if matches
44
- def evaluate(fact)
45
- send(@op, fact)
46
- rescue NoMethodError => _e
47
- raise "Term '#{@op}' is not defined"
48
- end
49
-
50
- # Put it into the context: let it see the entire array of maps.
51
- # @param [Array] maps The maps
52
- # @return [Factbase::Term] Itself
53
- def on(maps)
54
- m = "#{@op}_on"
55
- send(m, maps) if respond_to?(m, true)
56
- @operands.each do |o|
57
- o.on(maps) if o.is_a?(Factbase::Term)
58
- end
59
- self
48
+ def evaluate(fact, maps)
49
+ send(@op, fact, maps)
50
+ rescue NoMethodError => e
51
+ raise "Term '#{@op}' is not defined: #{e.message}"
60
52
  end
61
53
 
62
54
  # Simplify it if possible.
@@ -89,31 +81,31 @@ class Factbase::Term
89
81
 
90
82
  private
91
83
 
92
- def always(_fact)
84
+ def always(_fact, _maps)
93
85
  assert_args(0)
94
86
  true
95
87
  end
96
88
 
97
- def never(_fact)
89
+ def never(_fact, _maps)
98
90
  assert_args(0)
99
91
  false
100
92
  end
101
93
 
102
- def not(fact)
94
+ def not(fact, maps)
103
95
  assert_args(1)
104
- !only_bool(the_value(0, fact))
96
+ !only_bool(the_value(0, fact, maps))
105
97
  end
106
98
 
107
- def or(fact)
99
+ def or(fact, maps)
108
100
  (0..@operands.size - 1).each do |i|
109
- return true if only_bool(the_value(i, fact))
101
+ return true if only_bool(the_value(i, fact, maps))
110
102
  end
111
103
  false
112
104
  end
113
105
 
114
- def and(fact)
106
+ def and(fact, maps)
115
107
  (0..@operands.size - 1).each do |i|
116
- return false unless only_bool(the_value(i, fact))
108
+ return false unless only_bool(the_value(i, fact, maps))
117
109
  end
118
110
  true
119
111
  end
@@ -140,36 +132,36 @@ class Factbase::Term
140
132
  and_or_simplify
141
133
  end
142
134
 
143
- def when(fact)
135
+ def when(fact, maps)
144
136
  assert_args(2)
145
137
  a = @operands[0]
146
138
  b = @operands[1]
147
- !a.evaluate(fact) || (a.evaluate(fact) && b.evaluate(fact))
139
+ !a.evaluate(fact, maps) || (a.evaluate(fact, maps) && b.evaluate(fact, maps))
148
140
  end
149
141
 
150
- def exists(fact)
142
+ def exists(fact, _maps)
151
143
  assert_args(1)
152
144
  !by_symbol(0, fact).nil?
153
145
  end
154
146
 
155
- def absent(fact)
147
+ def absent(fact, _maps)
156
148
  assert_args(1)
157
149
  by_symbol(0, fact).nil?
158
150
  end
159
151
 
160
- def eq(fact)
161
- arithmetic(:==, fact)
152
+ def eq(fact, maps)
153
+ arithmetic(:==, fact, maps)
162
154
  end
163
155
 
164
- def lt(fact)
165
- arithmetic(:<, fact)
156
+ def lt(fact, maps)
157
+ arithmetic(:<, fact, maps)
166
158
  end
167
159
 
168
- def gt(fact)
169
- arithmetic(:>, fact)
160
+ def gt(fact, maps)
161
+ arithmetic(:>, fact, maps)
170
162
  end
171
163
 
172
- def size(fact)
164
+ def size(fact, _maps)
173
165
  assert_args(1)
174
166
  v = by_symbol(0, fact)
175
167
  return 0 if v.nil?
@@ -177,29 +169,29 @@ class Factbase::Term
177
169
  v.size
178
170
  end
179
171
 
180
- def type(fact)
172
+ def type(fact, _maps)
181
173
  assert_args(1)
182
174
  v = by_symbol(0, fact)
183
175
  return 'nil' if v.nil?
184
176
  v.class.to_s
185
177
  end
186
178
 
187
- def matches(fact)
179
+ def matches(fact, maps)
188
180
  assert_args(2)
189
- str = the_value(0, fact)
190
- raise 'String is nil' if str.nil?
181
+ str = the_value(0, fact, maps)
182
+ return false if str.nil?
191
183
  raise 'Exactly one string expected' unless str.size == 1
192
- re = the_value(1, fact)
184
+ re = the_value(1, fact, maps)
193
185
  raise 'Regexp is nil' if re.nil?
194
186
  raise 'Exactly one regexp expected' unless re.size == 1
195
187
  str[0].to_s.match?(re[0])
196
188
  end
197
189
 
198
- def arithmetic(op, fact)
190
+ def arithmetic(op, fact, maps)
199
191
  assert_args(2)
200
- lefts = the_value(0, fact)
192
+ lefts = the_value(0, fact, maps)
201
193
  return false if lefts.nil?
202
- rights = the_value(1, fact)
194
+ rights = the_value(1, fact, maps)
203
195
  return false if rights.nil?
204
196
  lefts.any? do |l|
205
197
  l = l.floor if l.is_a?(Time) && op == :==
@@ -210,34 +202,58 @@ class Factbase::Term
210
202
  end
211
203
  end
212
204
 
213
- def defn(_fact)
205
+ def defn(_fact, _maps)
214
206
  fn = @operands[0]
215
207
  raise 'A symbol expected as first argument of defn' unless fn.is_a?(Symbol)
216
- e = "class Factbase::Term\nprivate\ndef #{fn}(fact)\n#{@operands[1]}\nend\nend"
208
+ e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps)\n#{@operands[1]}\nend\nend"
217
209
  # rubocop:disable Security/Eval
218
210
  eval(e)
219
211
  # rubocop:enable Security/Eval
220
212
  true
221
213
  end
222
214
 
223
- def min(fact)
224
- vv = the_value(0, fact)
225
- return nil if vv.nil?
226
- vv.any? { |v| v == @min }
215
+ def min(_fact, maps)
216
+ @min ||= best(maps) { |v, b| v < b }
227
217
  end
228
218
 
229
- def max(fact)
230
- vv = the_value(0, fact)
231
- return nil if vv.nil?
232
- vv.any? { |v| v == @max }
219
+ def max(_fact, maps)
220
+ @max ||= best(maps) { |v, b| v > b }
233
221
  end
234
222
 
235
- def max_on(maps)
236
- @max = best(maps) { |v, b| v > b }
223
+ def count(_fact, maps)
224
+ @count ||= maps.size
237
225
  end
238
226
 
239
- def min_on(maps)
240
- @min = best(maps) { |v, b| v < b }
227
+ def sum(_fact, maps)
228
+ @sum ||=
229
+ begin
230
+ k = @operands[0]
231
+ raise "A symbol expected, but provided: #{k}" unless k.is_a?(Symbol)
232
+ sum = 0
233
+ maps.each do |m|
234
+ vv = m[k.to_s]
235
+ next if vv.nil?
236
+ vv = [vv] unless vv.is_a?(Array)
237
+ vv.each do |v|
238
+ sum += v
239
+ end
240
+ end
241
+ sum
242
+ end
243
+ end
244
+
245
+ def agg(_fact, maps)
246
+ selector = @operands[0]
247
+ raise "A term expected, but #{selector} provided" unless selector.is_a?(Factbase::Term)
248
+ term = @operands[1]
249
+ raise "A term expected, but #{term} provided" unless term.is_a?(Factbase::Term)
250
+ subset = maps.select { |m| selector.evaluate(m, maps) }
251
+ @agg ||=
252
+ if subset.empty?
253
+ term.evaluate(Factbase::Fact.new(Mutex.new, {}), subset)
254
+ else
255
+ term.evaluate(subset.first, subset)
256
+ end
241
257
  end
242
258
 
243
259
  def assert_args(num)
@@ -253,9 +269,9 @@ class Factbase::Term
253
269
  fact[k]
254
270
  end
255
271
 
256
- def the_value(pos, fact)
272
+ def the_value(pos, fact, maps)
257
273
  v = @operands[pos]
258
- v = v.evaluate(fact) if v.is_a?(Factbase::Term)
274
+ v = v.evaluate(fact, maps) if v.is_a?(Factbase::Term)
259
275
  v = fact[v.to_s] if v.is_a?(Symbol)
260
276
  return v if v.nil?
261
277
  v = [v] unless v.is_a?(Array)
@@ -25,6 +25,13 @@ require 'time'
25
25
  require_relative '../factbase'
26
26
 
27
27
  # Factbase to JSON converter.
28
+ #
29
+ # This class helps converting an entire Factbase to YAML format, for example:
30
+ #
31
+ # require 'factbase/to_json'
32
+ # fb = Factbase.new
33
+ # puts Factbase::ToJSON.new(fb).json
34
+ #
28
35
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
36
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
37
  # License:: MIT
@@ -25,6 +25,13 @@ require 'time'
25
25
  require_relative '../factbase'
26
26
 
27
27
  # Factbase to XML converter.
28
+ #
29
+ # This class helps converting an entire Factbase to YAML format, for example:
30
+ #
31
+ # require 'factbase/to_xml'
32
+ # fb = Factbase.new
33
+ # puts Factbase::ToXML.new(fb).xml
34
+ #
28
35
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
36
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
37
  # License:: MIT
@@ -25,6 +25,13 @@ require 'time'
25
25
  require_relative '../factbase'
26
26
 
27
27
  # Factbase to YAML converter.
28
+ #
29
+ # This class helps converting an entire Factbase to YAML format, for example:
30
+ #
31
+ # require 'factbase/to_yaml'
32
+ # fb = Factbase.new
33
+ # puts Factbase::ToYAML.new(fb).yaml
34
+ #
28
35
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
36
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
37
  # License:: MIT
@@ -23,6 +23,22 @@
23
23
  require_relative '../factbase'
24
24
 
25
25
  # Tuples.
26
+ #
27
+ # With the help of this class, it's possible to select a few facts
28
+ # from a factbase at a time, which depend on each other. For example,
29
+ # it's necessary to find a fact where the +name+ is set and then find
30
+ # another fact, where the salary is the +salary+ is the same as in the
31
+ # first found fact. Here is how:
32
+ #
33
+ # Factbase::Tuples.new(qt, ['(exists name)', '(eq salary, {f0.salary})']).each do |a, b|
34
+ # puts a.name
35
+ # puts b.salary
36
+ # end
37
+ #
38
+ # Here, the +{f0.salary}+ is a special substitution place, which is replaced
39
+ # by the +salary+ of the fact that is found by the previous query. The indexing
40
+ # of queries starts from zero.
41
+ #
26
42
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
43
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
44
  # License:: MIT
data/lib/factbase.rb CHANGED
@@ -29,7 +29,7 @@ require 'yaml'
29
29
  # License:: MIT
30
30
  class Factbase
31
31
  # Current version of the gem (changed by .rultor.yml on every release)
32
- VERSION = '0.0.34'
32
+ VERSION = '0.0.35'
33
33
 
34
34
  # Constructor.
35
35
  def initialize(facts = [])
@@ -44,8 +44,8 @@ class TestQuery < Minitest::Test
44
44
 
45
45
  def test_complex_parsing
46
46
  maps = []
47
- maps << { 'num' => 42 }
48
- maps << { 'pi' => 3.14, 'num' => [42, 66, 0] }
47
+ maps << { 'num' => 42, 'name' => 'Jeff' }
48
+ maps << { 'pi' => 3.14, 'num' => [42, 66, 0], 'name' => 'peter' }
49
49
  maps << { 'time' => Time.now - 100, 'num' => 0 }
50
50
  {
51
51
  '(eq num 444)' => 0,
@@ -55,15 +55,17 @@ class TestQuery < Minitest::Test
55
55
  '(exists pi)' => 1,
56
56
  '(not (exists hello))' => 3,
57
57
  '(eq "Integer" (type num))' => 2,
58
+ '(when (eq num 0) (exists time))' => 2,
58
59
  '(gt (size num) 2)' => 1,
60
+ '(matches name "^[a-z]+$")' => 1,
59
61
  '(lt (size num) 2)' => 2,
60
62
  '(eq (size hello) 0)' => 3,
61
63
  '(eq num pi)' => 0,
62
64
  '(absent time)' => 2,
63
- '(max num)' => 1,
64
- '(and (exists time) (max num))' => 0,
65
- '(and (exists pi) (max num))' => 1,
66
- '(min time)' => 1,
65
+ '(eq pi (agg (eq num 0) (sum pi)))' => 1,
66
+ '(eq num (agg (exists oops) (count)))' => 2,
67
+ '(lt num (agg (eq num 0) (max pi)))' => 2,
68
+ '(eq time (min time))' => 1,
67
69
  '(and (absent time) (exists pi))' => 1,
68
70
  "(and (exists time) (not (\t\texists pi)))" => 1,
69
71
  "(or (eq num 66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
@@ -83,7 +83,7 @@ class TestSyntax < Minitest::Test
83
83
  '(or (eq bar 888) (eq z 1))' => true,
84
84
  "(or (gt bar 100) (eq foo 'Hello, world!'))" => true
85
85
  }.each do |k, v|
86
- assert_equal(v, Factbase::Syntax.new(k).to_term.evaluate(m), k)
86
+ assert_equal(v, Factbase::Syntax.new(k).to_term.evaluate(m, []), k)
87
87
  end
88
88
  end
89
89
 
@@ -30,107 +30,107 @@ require_relative '../../lib/factbase/term'
30
30
  class TestTerm < Minitest::Test
31
31
  def test_simple_matching
32
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!'])))
33
+ assert(t.evaluate(fact('foo' => [42]), []))
34
+ assert(!t.evaluate(fact('foo' => 'Hello!'), []))
35
+ assert(!t.evaluate(fact('bar' => ['Hello!']), []))
36
36
  end
37
37
 
38
38
  def test_eq_matching
39
39
  t = Factbase::Term.new(:eq, [:foo, 42])
40
- assert(t.evaluate(fact('foo' => 42)))
41
- assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
- assert(!t.evaluate(fact('foo' => [100])))
43
- assert(!t.evaluate(fact('foo' => [])))
44
- assert(!t.evaluate(fact('bar' => [])))
40
+ assert(t.evaluate(fact('foo' => 42), []))
41
+ assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf']), []))
42
+ assert(!t.evaluate(fact('foo' => [100]), []))
43
+ assert(!t.evaluate(fact('foo' => []), []))
44
+ assert(!t.evaluate(fact('bar' => []), []))
45
45
  end
46
46
 
47
47
  def test_eq_matching_time
48
48
  now = Time.now
49
49
  t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
50
- assert(t.evaluate(fact('foo' => now)))
51
- assert(t.evaluate(fact('foo' => [now, Time.now])))
50
+ assert(t.evaluate(fact('foo' => now), []))
51
+ assert(t.evaluate(fact('foo' => [now, Time.now]), []))
52
52
  end
53
53
 
54
54
  def test_lt_matching
55
55
  t = Factbase::Term.new(:lt, [:foo, 42])
56
- assert(t.evaluate(fact('foo' => [10])))
57
- assert(!t.evaluate(fact('foo' => [100])))
58
- assert(!t.evaluate(fact('foo' => 100)))
59
- assert(!t.evaluate(fact('bar' => 100)))
56
+ assert(t.evaluate(fact('foo' => [10]), []))
57
+ assert(!t.evaluate(fact('foo' => [100]), []))
58
+ assert(!t.evaluate(fact('foo' => 100), []))
59
+ assert(!t.evaluate(fact('bar' => 100), []))
60
60
  end
61
61
 
62
62
  def test_gt_matching
63
63
  t = Factbase::Term.new(:gt, [:foo, 42])
64
- assert(t.evaluate(fact('foo' => [100])))
65
- assert(t.evaluate(fact('foo' => 100)))
66
- assert(!t.evaluate(fact('foo' => [10])))
67
- assert(!t.evaluate(fact('foo' => 10)))
68
- assert(!t.evaluate(fact('bar' => 10)))
64
+ assert(t.evaluate(fact('foo' => [100]), []))
65
+ assert(t.evaluate(fact('foo' => 100), []))
66
+ assert(!t.evaluate(fact('foo' => [10]), []))
67
+ assert(!t.evaluate(fact('foo' => 10), []))
68
+ assert(!t.evaluate(fact('bar' => 10), []))
69
69
  end
70
70
 
71
71
  def test_lt_matching_time
72
72
  t = Factbase::Term.new(:lt, [:foo, Time.now])
73
- assert(t.evaluate(fact('foo' => [Time.now - 100])))
74
- assert(!t.evaluate(fact('foo' => [Time.now + 100])))
75
- assert(!t.evaluate(fact('bar' => [100])))
73
+ assert(t.evaluate(fact('foo' => [Time.now - 100]), []))
74
+ assert(!t.evaluate(fact('foo' => [Time.now + 100]), []))
75
+ assert(!t.evaluate(fact('bar' => [100]), []))
76
76
  end
77
77
 
78
78
  def test_gt_matching_time
79
79
  t = Factbase::Term.new(:gt, [:foo, Time.now])
80
- assert(t.evaluate(fact('foo' => [Time.now + 100])))
81
- assert(!t.evaluate(fact('foo' => [Time.now - 100])))
82
- assert(!t.evaluate(fact('bar' => [100])))
80
+ assert(t.evaluate(fact('foo' => [Time.now + 100]), []))
81
+ assert(!t.evaluate(fact('foo' => [Time.now - 100]), []))
82
+ assert(!t.evaluate(fact('bar' => [100]), []))
83
83
  end
84
84
 
85
85
  def test_false_matching
86
86
  t = Factbase::Term.new(:never, [])
87
- assert(!t.evaluate(fact('foo' => [100])))
87
+ assert(!t.evaluate(fact('foo' => [100]), []))
88
88
  end
89
89
 
90
90
  def test_not_matching
91
91
  t = Factbase::Term.new(:not, [Factbase::Term.new(:always, [])])
92
- assert(!t.evaluate(fact('foo' => [100])))
92
+ assert(!t.evaluate(fact('foo' => [100]), []))
93
93
  end
94
94
 
95
95
  def test_not_eq_matching
96
96
  t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [:foo, 100])])
97
- assert(t.evaluate(fact('foo' => [42, 12, -90])))
98
- assert(!t.evaluate(fact('foo' => 100)))
97
+ assert(t.evaluate(fact('foo' => [42, 12, -90]), []))
98
+ assert(!t.evaluate(fact('foo' => 100), []))
99
99
  end
100
100
 
101
101
  def test_size_matching
102
102
  t = Factbase::Term.new(:size, [:foo])
103
- assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90])))
104
- assert_equal(0, t.evaluate(fact('bar' => 100)))
103
+ assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90]), []))
104
+ assert_equal(0, t.evaluate(fact('bar' => 100), []))
105
105
  end
106
106
 
107
107
  def test_exists_matching
108
108
  t = Factbase::Term.new(:exists, [:foo])
109
- assert(t.evaluate(fact('foo' => [42, 12, -90])))
110
- assert(!t.evaluate(fact('bar' => 100)))
109
+ assert(t.evaluate(fact('foo' => [42, 12, -90]), []))
110
+ assert(!t.evaluate(fact('bar' => 100), []))
111
111
  end
112
112
 
113
113
  def test_absent_matching
114
114
  t = Factbase::Term.new(:absent, [:foo])
115
- assert(t.evaluate(fact('z' => [42, 12, -90])))
116
- assert(!t.evaluate(fact('foo' => 100)))
115
+ assert(t.evaluate(fact('z' => [42, 12, -90]), []))
116
+ assert(!t.evaluate(fact('foo' => 100), []))
117
117
  end
118
118
 
119
119
  def test_type_matching
120
120
  t = Factbase::Term.new(:type, [:foo])
121
- assert_equal('Integer', t.evaluate(fact('foo' => 42)))
122
- assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3])))
123
- assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!')))
124
- assert_equal('Float', t.evaluate(fact('foo' => 3.14)))
125
- assert_equal('Time', t.evaluate(fact('foo' => Time.now)))
126
- assert_equal('nil', t.evaluate(fact))
121
+ assert_equal('Integer', t.evaluate(fact('foo' => 42), []))
122
+ assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3]), []))
123
+ assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!'), []))
124
+ assert_equal('Float', t.evaluate(fact('foo' => 3.14), []))
125
+ assert_equal('Time', t.evaluate(fact('foo' => Time.now), []))
126
+ assert_equal('nil', t.evaluate(fact, []))
127
127
  end
128
128
 
129
129
  def test_regexp_matching
130
130
  t = Factbase::Term.new(:matches, [:foo, '[a-z]+'])
131
- assert(t.evaluate(fact('foo' => 'hello')))
132
- assert(t.evaluate(fact('foo' => 'hello 42')))
133
- assert(!t.evaluate(fact('foo' => 42)))
131
+ assert(t.evaluate(fact('foo' => 'hello'), []))
132
+ assert(t.evaluate(fact('foo' => 'hello 42'), []))
133
+ assert(!t.evaluate(fact('foo' => 42), []))
134
134
  end
135
135
 
136
136
  def test_or_matching
@@ -141,9 +141,9 @@ class TestTerm < Minitest::Test
141
141
  Factbase::Term.new(:eq, [:bar, 5])
142
142
  ]
143
143
  )
144
- assert(t.evaluate(fact('foo' => [4])))
145
- assert(t.evaluate(fact('bar' => [5])))
146
- assert(!t.evaluate(fact('bar' => [42])))
144
+ assert(t.evaluate(fact('foo' => [4]), []))
145
+ assert(t.evaluate(fact('bar' => [5]), []))
146
+ assert(!t.evaluate(fact('bar' => [42]), []))
147
147
  end
148
148
 
149
149
  def test_when_matching
@@ -154,16 +154,16 @@ class TestTerm < Minitest::Test
154
154
  Factbase::Term.new(:eq, [:bar, 5])
155
155
  ]
156
156
  )
157
- assert(t.evaluate(fact('foo' => 4, 'bar' => 5)))
158
- assert(!t.evaluate(fact('foo' => 4)))
159
- assert(t.evaluate(fact('foo' => 5, 'bar' => 5)))
157
+ assert(t.evaluate(fact('foo' => 4, 'bar' => 5), []))
158
+ assert(!t.evaluate(fact('foo' => 4), []))
159
+ assert(t.evaluate(fact('foo' => 5, 'bar' => 5), []))
160
160
  end
161
161
 
162
162
  def test_defn_simple
163
163
  t = Factbase::Term.new(:defn, [:foo, 'self.to_s'])
164
- assert_equal(true, t.evaluate(fact('foo' => 4)))
164
+ assert_equal(true, t.evaluate(fact('foo' => 4), []))
165
165
  t1 = Factbase::Term.new(:foo, ['hello, world!'])
166
- assert_equal('(foo \'hello, world!\')', t1.evaluate(fact))
166
+ assert_equal('(foo \'hello, world!\')', t1.evaluate(fact, []))
167
167
  end
168
168
 
169
169
  private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.34
4
+ version: 0.0.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-25 00:00:00.000000000 Z
11
+ date: 2024-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json