factbase 0.0.35 → 0.0.36

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: fc97bfbf967aa07fa189c2d3c696eb8d963f4647c06fb5d97ca4b0e2b4edb4db
4
- data.tar.gz: b38987779b663f76cbf9876b4e117cd1fabb3e1c2ef07bd6ab433be0dede9a44
3
+ metadata.gz: c801d7ec256766a03b081de5949367aef74dc991b67207ae30c6e6229f11c425
4
+ data.tar.gz: 2cfc12996cc7e4d85fad86377ddc9b87916d5d2a1cbe5542ba8128488c1e0049
5
5
  SHA512:
6
- metadata.gz: 97cdc3269d4fbfea8d7246f6cee197be5bdf8b1e463573940b9be2384cbc2718a454e7f1291c5efc11554fc59c84a97c9349def6413d0ee54192118820cdb252
7
- data.tar.gz: '049a1b2ae928bae38e1079f4e67d4f29a3efe3201e27c28c92f58bf4a2eda519a62a9a18095d857792337b19775e319921f8494643718a406bf6c377e36eefa6'
6
+ metadata.gz: 6d2d1ec011eaf2d34f00068fa71809bf8bd3fe794b6097eeb1292ad25f1a6916e319490a137fa4c1908c00ce640f19c8efff27444ca277e72048daf5e688c3ef
7
+ data.tar.gz: 7f20423de58a836eb68261a8d7ad45ec00056a4f991b8f34c68dd4833a42bbb0009bca75a4a8092112cfb80599e2be04e5cca9a9bfe18b3ae18102622d98f9a6
data/README.md CHANGED
@@ -54,7 +54,7 @@ f2.import(File.read(file))
54
54
  assert(f2.query('(eq foo 42)').each.to_a.size == 1)
55
55
  ```
56
56
 
57
- All terms available in a query:
57
+ There are some terms available in a query:
58
58
 
59
59
  * `(always)` and `(never)` are "true" and "false"
60
60
  * `(not t)` inverses the `t` if it's boolean (exception otherwise)
@@ -68,14 +68,39 @@ All terms available in a query:
68
68
  * `(gt a b)` returns true if `a` is greater than `b`
69
69
  * `(size k)` returns cardinality of `k` property (zero if property is absent)
70
70
  * `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
71
+ * `(many a)` return true if there are many values in the `a` property
72
+ * `(one a)` returns true if there is only one value in the `a` property
73
+ * `(at i a)` returns the `i`-th value of the `a` property
74
+ * `(nonil a b)` returns `b` if `a` is `nil`
71
75
  * `(matches a re)` returns true when `a` matches regular expression `re`
72
76
  * `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
73
77
 
74
- There are also terms that match the entire factbase:
78
+ Also, some simple arithmetic:
75
79
 
76
- * `(max k)` returns true if the value of `k` property
77
- is the largest in the entire factbase
78
- * `(min k)` returns true if the value of `k` is the smallest
80
+ * `(plus a b)` is a sum of `a` and `b`
81
+ * `(minus a b)` is a deducation of `b` from `a`
82
+ * `(times a b)` is a multiplication of `a` and `b`
83
+ * `(div a b)` is a division of `a` by `b`
84
+
85
+ There are terms that are history of search aware:
86
+
87
+ * `(prev a)` returns the value of `a` in the previously seen fact
88
+
89
+ There are also terms that match the entire factbase
90
+ and must be used inside the `(agg ..)` term:
91
+
92
+ * `(count)` returns the tally of facts
93
+ * `(max k)` returns the maximum value of the `k` property in all facts
94
+ * `(min k)` returns the minimum
95
+ * `(sum k)` returns the arithmetic sum of all values of the `k` property
96
+
97
+ The `agg` term enables sub-queries by evaluating the first argument (term)
98
+ over all available facts, passing the entire subset to the second argument,
99
+ and then returning the result as an atomic value:
100
+
101
+ * `(lt age (agg (eq gender 'F') (max age)))` selects all facts where
102
+ the `age` is smaller than the maximum `age` of all women
103
+ * `(eq id (agg (always) (max id)))` selects the fact with the largest `id`
79
104
 
80
105
  ## How to contribute
81
106
 
data/lib/factbase/fact.rb CHANGED
@@ -48,7 +48,7 @@ class Factbase::Fact
48
48
  k = args[0].to_s
49
49
  if k.end_with?('=')
50
50
  kk = k[0..-2]
51
- raise "Invalid prop name '#{kk}'" unless kk.match?(/^[a-z][_a-zA-Z0-9]*$/)
51
+ raise "Invalid prop name '#{kk}'" unless kk.match?(/^[a-z_][_a-zA-Z0-9]*$/)
52
52
  raise "Prohibited prop name '#{kk}'" if kk == 'to_s'
53
53
  v = args[1]
54
54
  raise "Prop value can't be nil" if v.nil?
@@ -129,9 +129,9 @@ class Factbase::Syntax
129
129
  elsif t.start_with?('\'', '"')
130
130
  raise 'String literal can\'t be empty' if t.length <= 2
131
131
  t[1..-2]
132
- elsif t.match?(/^[0-9]+$/)
132
+ elsif t.match?(/^(\+|-)?[0-9]+$/)
133
133
  t.to_i
134
- elsif t.match?(/^[0-9]+\.[0-9]+(e\+[0-9]+)?$/)
134
+ elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e\+[0-9]+)?$/)
135
135
  t.to_f
136
136
  elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
137
137
  Time.parse(t)
data/lib/factbase/term.rb CHANGED
@@ -93,19 +93,19 @@ class Factbase::Term
93
93
 
94
94
  def not(fact, maps)
95
95
  assert_args(1)
96
- !only_bool(the_value(0, fact, maps))
96
+ !only_bool(the_values(0, fact, maps))
97
97
  end
98
98
 
99
99
  def or(fact, maps)
100
100
  (0..@operands.size - 1).each do |i|
101
- return true if only_bool(the_value(i, fact, maps))
101
+ return true if only_bool(the_values(i, fact, maps))
102
102
  end
103
103
  false
104
104
  end
105
105
 
106
106
  def and(fact, maps)
107
107
  (0..@operands.size - 1).each do |i|
108
- return false unless only_bool(the_value(i, fact, maps))
108
+ return false unless only_bool(the_values(i, fact, maps))
109
109
  end
110
110
  true
111
111
  end
@@ -149,16 +149,70 @@ class Factbase::Term
149
149
  by_symbol(0, fact).nil?
150
150
  end
151
151
 
152
+ def nonil(fact, maps)
153
+ assert_args(2)
154
+ v = the_values(0, fact, maps)
155
+ return v unless v.nil?
156
+ the_values(1, fact, maps)
157
+ end
158
+
159
+ def at(fact, maps)
160
+ assert_args(2)
161
+ i = the_values(0, fact, maps)
162
+ raise 'Too many values at first position, one expected' unless i.size == 1
163
+ i = i[0]
164
+ return nil if i.nil?
165
+ v = the_values(1, fact, maps)
166
+ return nil if v.nil?
167
+ v[i]
168
+ end
169
+
170
+ def prev(fact, maps)
171
+ assert_args(1)
172
+ before = @prev
173
+ v = the_values(0, fact, maps)
174
+ @prev = v
175
+ before
176
+ end
177
+
178
+ def many(fact, maps)
179
+ assert_args(1)
180
+ v = the_values(0, fact, maps)
181
+ !v.nil? && v.size > 1
182
+ end
183
+
184
+ def one(fact, maps)
185
+ assert_args(1)
186
+ v = the_values(0, fact, maps)
187
+ !v.nil? && v.size == 1
188
+ end
189
+
190
+ def plus(fact, maps)
191
+ arithmetic(:+, fact, maps)
192
+ end
193
+
194
+ def minus(fact, maps)
195
+ arithmetic(:-, fact, maps)
196
+ end
197
+
198
+ def times(fact, maps)
199
+ arithmetic(:*, fact, maps)
200
+ end
201
+
202
+ def div(fact, maps)
203
+ arithmetic(:/, fact, maps)
204
+ end
205
+
152
206
  def eq(fact, maps)
153
- arithmetic(:==, fact, maps)
207
+ cmp(:==, fact, maps)
154
208
  end
155
209
 
156
210
  def lt(fact, maps)
157
- arithmetic(:<, fact, maps)
211
+ cmp(:<, fact, maps)
158
212
  end
159
213
 
160
214
  def gt(fact, maps)
161
- arithmetic(:>, fact, maps)
215
+ cmp(:>, fact, maps)
162
216
  end
163
217
 
164
218
  def size(fact, _maps)
@@ -178,20 +232,20 @@ class Factbase::Term
178
232
 
179
233
  def matches(fact, maps)
180
234
  assert_args(2)
181
- str = the_value(0, fact, maps)
235
+ str = the_values(0, fact, maps)
182
236
  return false if str.nil?
183
237
  raise 'Exactly one string expected' unless str.size == 1
184
- re = the_value(1, fact, maps)
238
+ re = the_values(1, fact, maps)
185
239
  raise 'Regexp is nil' if re.nil?
186
240
  raise 'Exactly one regexp expected' unless re.size == 1
187
241
  str[0].to_s.match?(re[0])
188
242
  end
189
243
 
190
- def arithmetic(op, fact, maps)
244
+ def cmp(op, fact, maps)
191
245
  assert_args(2)
192
- lefts = the_value(0, fact, maps)
246
+ lefts = the_values(0, fact, maps)
193
247
  return false if lefts.nil?
194
- rights = the_value(1, fact, maps)
248
+ rights = the_values(1, fact, maps)
195
249
  return false if rights.nil?
196
250
  lefts.any? do |l|
197
251
  l = l.floor if l.is_a?(Time) && op == :==
@@ -202,6 +256,17 @@ class Factbase::Term
202
256
  end
203
257
  end
204
258
 
259
+ def arithmetic(op, fact, maps)
260
+ assert_args(2)
261
+ lefts = the_values(0, fact, maps)
262
+ raise 'The first argument is NIL, while literal expected' if lefts.nil?
263
+ raise 'Too many values at first position, one expected' unless lefts.size == 1
264
+ rights = the_values(1, fact, maps)
265
+ raise 'The second argument is NIL, while literal expected' if rights.nil?
266
+ raise 'Too many values at second position, one expected' unless rights.size == 1
267
+ lefts[0].send(op, rights[0])
268
+ end
269
+
205
270
  def defn(_fact, _maps)
206
271
  fn = @operands[0]
207
272
  raise 'A symbol expected as first argument of defn' unless fn.is_a?(Symbol)
@@ -269,7 +334,7 @@ class Factbase::Term
269
334
  fact[k]
270
335
  end
271
336
 
272
- def the_value(pos, fact, maps)
337
+ def the_values(pos, fact, maps)
273
338
  v = @operands[pos]
274
339
  v = v.evaluate(fact, maps) if v.is_a?(Factbase::Term)
275
340
  v = fact[v.to_s] if v.is_a?(Symbol)
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.35'
32
+ VERSION = '0.0.36'
33
33
 
34
34
  # Constructor.
35
35
  def initialize(facts = [])
@@ -80,8 +80,8 @@ class TestFact < Minitest::Test
80
80
 
81
81
  def test_set_by_name
82
82
  f = Factbase::Fact.new(Mutex.new, {})
83
- f.send('foo_bar=', 42)
84
- assert_equal(42, f.foo_bar, f.to_s)
83
+ f.send('_foo_bar=', 42)
84
+ assert_equal(42, f._foo_bar, f.to_s)
85
85
  end
86
86
 
87
87
  def test_set_twice_same_value
@@ -53,9 +53,17 @@ class TestQuery < Minitest::Test
53
53
  '(gt num 60)' => 1,
54
54
  "(and (lt pi 100) \n\n (gt num 1000))" => 0,
55
55
  '(exists pi)' => 1,
56
+ '(eq pi +3.14)' => 1,
56
57
  '(not (exists hello))' => 3,
57
58
  '(eq "Integer" (type num))' => 2,
58
59
  '(when (eq num 0) (exists time))' => 2,
60
+ '(many num)' => 1,
61
+ '(one num)' => 2,
62
+ '(gt num (minus 1 (nonil (at 0 (prev num)) 0)))' => 3,
63
+ '(and (not (many num)) (eq num (plus 21 +21)))' => 1,
64
+ '(and (not (many num)) (eq num (minus -100 -142)))' => 1,
65
+ '(and (one num) (eq num (times 7 6)))' => 1,
66
+ '(and (one pi) (eq pi (div -6.28 -2)))' => 1,
59
67
  '(gt (size num) 2)' => 1,
60
68
  '(matches name "^[a-z]+$")' => 1,
61
69
  '(lt (size num) 2)' => 2,
@@ -68,7 +76,7 @@ class TestQuery < Minitest::Test
68
76
  '(eq time (min time))' => 1,
69
77
  '(and (absent time) (exists pi))' => 1,
70
78
  "(and (exists time) (not (\t\texists pi)))" => 1,
71
- "(or (eq num 66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
79
+ "(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
72
80
  }.each do |q, r|
73
81
  assert_equal(r, Factbase::Query.new(maps, Mutex.new, q).each.to_a.size, q)
74
82
  end
@@ -166,6 +166,23 @@ class TestTerm < Minitest::Test
166
166
  assert_equal('(foo \'hello, world!\')', t1.evaluate(fact, []))
167
167
  end
168
168
 
169
+ def test_past
170
+ t = Factbase::Term.new(:prev, [:foo])
171
+ assert_nil(t.evaluate(fact('foo' => 4), []))
172
+ assert_equal([4], t.evaluate(fact('foo' => 5), []))
173
+ end
174
+
175
+ def test_at
176
+ t = Factbase::Term.new(:at, [1, :foo])
177
+ assert_nil(t.evaluate(fact('foo' => 4), []))
178
+ assert_equal(5, t.evaluate(fact('foo' => [4, 5]), []))
179
+ end
180
+
181
+ def test_nonil
182
+ t = Factbase::Term.new(:nonil, [Factbase::Term.new(:at, [5, :foo]), 42])
183
+ assert_equal([42], t.evaluate(fact('foo' => 4), []))
184
+ end
185
+
169
186
  private
170
187
 
171
188
  def fact(map = {})
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.35
4
+ version: 0.0.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko