factbase 0.19.3 → 0.19.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aba67403ce0d88eedb16e819e8000c675c43e45908d9e937a08394aa27b76ce2
4
- data.tar.gz: 955fe52ce4c8d8fca527f3471fee46d73f1f56a85b7778440df964e26de2d62f
3
+ metadata.gz: dd4224329381f390e0d8262e6868f1edf6b4f0ae07c972bf0d22fdc7581b0ea9
4
+ data.tar.gz: 92d79cb596b6b9dc647555c60b0ce7ddc30d270b7b7bdccf1cd8e0a1db4190ce
5
5
  SHA512:
6
- metadata.gz: 7fb94faf1459fc521be7d810831c870530898605664f8c2ae74fb15c66846b4c84957e63cb593e9c2f8d6c21f5584227d057a08e51d618994aaeeb3e4e427505
7
- data.tar.gz: 8ca685bc0115e1bc8ab7aa478218c179eb898cfb6a1fd7d38b8b082c3bfd479f48dd72ef82e06700cd6547bd0673d596d885521f8d43e92da879e9b01ebc2d90
6
+ metadata.gz: 7a2a9b52b2576c634702c057b2399dc985c6582c7f371c4feae37c8d794f68a3c71ac4467aed8d05becb00e8b63a5fa8cef2b20882455f2ecc6199d215888ce6
7
+ data.tar.gz: 88c27d05a03f5bf0270ad713ab76a3f592ecb4128f5d16e284fbf3f910f7e443f961c184b5f658647b4caafa4ccac83c2fcdd5c1578c2d6f1d7ac1e6c12dae46
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem 'benchmark', '~>0.5', require: false
10
10
  gem 'minitest', '~>6.0', require: false
11
11
  gem 'minitest-reporters', '~>1.7', require: false
12
12
  gem 'os', '~>1.1', require: false
13
+ gem 'psych', '5.3.1', require: false # GPL
13
14
  gem 'qbash', '~>0.4', require: false
14
15
  gem 'rake', '~>13.2', require: false
15
16
  gem 'rdoc', '7.1.0', require: false # GPL
@@ -19,6 +20,6 @@ gem 'rubocop-performance', '~>1.25', require: false
19
20
  gem 'rubocop-rake', '~>0.7', require: false
20
21
  gem 'simplecov', '~>0.22', require: false
21
22
  gem 'simplecov-cobertura', '~>3.0', require: false
22
- gem 'stackprof', '~>0.2', require: false, platforms: [:ruby]
23
+ gem 'stackprof', '0.2.27', require: false, platforms: [:ruby] # GPL
23
24
  gem 'threads', '~>0.4', require: false
24
- gem 'yard', '~>0.9', require: false
25
+ gem 'yard', '0.9.38', require: false # GPL
data/Gemfile.lock CHANGED
@@ -25,12 +25,12 @@ GEM
25
25
  date (3.5.1)
26
26
  decoor (0.1.0)
27
27
  docile (1.4.1)
28
- elapsed (0.2.2)
28
+ elapsed (0.3.1)
29
29
  loog (~> 0.6)
30
30
  tago (~> 0.1)
31
31
  ellipsized (0.3.0)
32
32
  erb (6.0.1)
33
- json (2.18.0)
33
+ json (2.18.1)
34
34
  language_server-protocol (3.17.0.5)
35
35
  lint_roller (1.1.0)
36
36
  logger (1.7.0)
@@ -58,11 +58,11 @@ GEM
58
58
  parser (3.3.10.1)
59
59
  ast (~> 2.4.1)
60
60
  racc
61
- prism (1.8.0)
61
+ prism (1.9.0)
62
62
  psych (5.3.1)
63
63
  date
64
64
  stringio
65
- qbash (0.6.0)
65
+ qbash (0.7.2)
66
66
  backtrace (> 0)
67
67
  elapsed (> 0)
68
68
  loog (> 0)
@@ -76,7 +76,7 @@ GEM
76
76
  tsort
77
77
  regexp_parser (2.11.3)
78
78
  rexml (3.4.4)
79
- rubocop (1.82.1)
79
+ rubocop (1.84.1)
80
80
  json (~> 2.3)
81
81
  language_server-protocol (~> 3.17.0.2)
82
82
  lint_roller (~> 1.1.0)
@@ -84,7 +84,7 @@ GEM
84
84
  parser (>= 3.3.0.2)
85
85
  rainbow (>= 2.2.2, < 4.0)
86
86
  regexp_parser (>= 2.9.3, < 3.0)
87
- rubocop-ast (>= 1.48.0, < 2.0)
87
+ rubocop-ast (>= 1.49.0, < 2.0)
88
88
  ruby-progressbar (~> 1.7)
89
89
  unicode-display_width (>= 2.4.0, < 4.0)
90
90
  rubocop-ast (1.49.0)
@@ -113,7 +113,7 @@ GEM
113
113
  simplecov_json_formatter (0.1.4)
114
114
  stackprof (0.2.27)
115
115
  stringio (3.2.0)
116
- tago (0.6.0)
116
+ tago (0.7.0)
117
117
  threads (0.5.0)
118
118
  backtrace (~> 0)
119
119
  concurrent-ruby (~> 1.0)
@@ -141,6 +141,7 @@ DEPENDENCIES
141
141
  minitest (~> 6.0)
142
142
  minitest-reporters (~> 1.7)
143
143
  os (~> 1.1)
144
+ psych (= 5.3.1)
144
145
  qbash (~> 0.4)
145
146
  rake (~> 13.2)
146
147
  rdoc (= 7.1.0)
@@ -150,9 +151,9 @@ DEPENDENCIES
150
151
  rubocop-rake (~> 0.7)
151
152
  simplecov (~> 0.22)
152
153
  simplecov-cobertura (~> 3.0)
153
- stackprof (~> 0.2)
154
+ stackprof (= 0.2.27)
154
155
  threads (~> 0.4)
155
- yard (~> 0.9)
156
+ yard (= 0.9.38)
156
157
 
157
158
  BUNDLED WITH
158
159
  2.6.8
data/README.md CHANGED
@@ -42,9 +42,9 @@ file = '/tmp/simple.fb'
42
42
  f1 = Factbase.new
43
43
  f = f1.insert
44
44
  f.foo = 42
45
- File.save(file, f1.export)
45
+ File.binwrite(file, f1.export)
46
46
  f2 = Factbase.new
47
- f2.import(File.read(file))
47
+ f2.import(File.binread(file))
48
48
  assert(f2.query('(eq foo 42)').each.to_a.size == 1)
49
49
  ```
50
50
 
@@ -80,6 +80,21 @@ churn = fb.churn
80
80
  assert churn.inserted == 1
81
81
  ```
82
82
 
83
+ Properties are accumulative.
84
+ Setting a property again adds a value instead of overwriting:
85
+
86
+ ```ruby
87
+ f = fb.insert
88
+ f.foo = 42
89
+ f.foo = 43
90
+ assert(f.foo == 42)
91
+ assert(f['foo'] == [42, 43])
92
+ fb.query('(eq foo 43)').each do |f|
93
+ assert(f.foo == 42)
94
+ assert(f['foo'].include?(43))
95
+ end
96
+ ```
97
+
83
98
  ## Terms
84
99
 
85
100
  There are some boolean terms available in a query
@@ -210,54 +225,54 @@ This is the result of the benchmark:
210
225
 
211
226
  <!-- benchmark_begin -->
212
227
  ```text
213
-
228
+
214
229
  query all facts from an empty factbase 0.00
215
230
  insert 20000 facts 0.67
216
231
  export 20000 facts 0.02
217
- import 411032 bytes (20000 facts) 0.03
232
+ import 410821 bytes (20000 facts) 0.01
218
233
  insert 10 facts 0.04
219
- query 10 times w/txn 2.52
220
- query 10 times w/o txn 0.05
221
- modify 10 attrs w/txn 1.84
222
- delete 10 facts w/txn 2.98
234
+ query 10 times w/txn 2.36
235
+ query 10 times w/o txn 0.07
236
+ modify 10 attrs w/txn 1.71
237
+ delete 10 facts w/txn 3.38
223
238
  build index on 5000 facts 0.05
224
- export 5000 facts with index 0.03
225
- import 5000 facts with persisted index 0.05
226
- query 5000 facts using persisted index 0.11
239
+ export 5000 facts with index 0.05
240
+ import 5000 facts with persisted index 0.03
241
+ query 5000 facts using persisted index 0.10
227
242
  export 5000 facts without index 0.00
228
- import 5000 facts without index 0.04
229
- query 5000 facts building index on-the-fly 0.10
230
- (and (eq what 'issue-was-closed') (exists... -> 200 1.21
231
- (and (eq what 'issue-was-closed') (exists... -> 200/txn 1.20
232
- (and (eq what 'issue-was-closed') (exists... -> zero 1.21
233
- (and (eq what 'issue-was-closed') (exists... -> zero/txn 1.23
234
- transaction rollback on factbase with 100000 facts 0.25
235
- (gt time '2024-03-23T03:21:43Z') 0.33
236
- (gt cost 50) 0.21
237
- (eq title 'Object Thinking 5000') 0.05
238
- (and (eq foo 42.998) (or (gt bar 200) (absent z... 0.05
239
- (and (exists foo) (not (exists blue))) 1.68
240
- (eq id (agg (always) (max id))) 2.76
241
- (join "c<=cost,b<=bar" (eq id (agg (always) (ma... 4.02
242
- (and (eq what "foo") (join "w<=what" (and (eq i... 7.68
243
- delete! 0.56
244
- (and (eq issue *) (eq repository *) (eq what '*') (eq where '*')) 2.75
245
- Taped.append() x50000 0.16
246
- Taped.each() x125 1.63
247
- Taped.delete_if() x375 0.84
248
- 50000 facts: read-only txn (no copy needed) 7.60
249
- 50000 facts: rollback txn (no copy needed) 7.47
250
- 50000 facts: insert in txn (copy triggered) 3.64
251
- 50000 facts: modify in txn (copy triggered) 39.42
252
- 100000 facts: read-only txn (no copy needed) 15.23
253
- 100000 facts: rollback txn (no copy needed) 15.17
254
- 100000 facts: insert in txn (copy triggered) 7.45
255
- 100000 facts: modify in txn (copy triggered) 73.97
243
+ import 5000 facts without index 0.01
244
+ query 5000 facts building index on-the-fly 0.09
245
+ (and (eq what 'issue-was-closed') (exists... -> 200 1.11
246
+ (and (eq what 'issue-was-closed') (exists... -> 200/txn 1.23
247
+ (and (eq what 'issue-was-closed') (exists... -> zero 1.18
248
+ (and (eq what 'issue-was-closed') (exists... -> zero/txn 1.30
249
+ transaction rollback on factbase with 100000 facts 0.28
250
+ (gt time '2024-03-23T03:21:43Z') 0.37
251
+ (gt cost 50) 0.18
252
+ (eq title 'Object Thinking 5000') 0.03
253
+ (and (eq foo 42.998) (or (gt bar 200) (absent z... 0.03
254
+ (and (exists foo) (not (exists blue))) 1.73
255
+ (eq id (agg (always) (max id))) 2.77
256
+ (join "c<=cost,b<=bar" (eq id (agg (always) (ma... 4.70
257
+ (and (eq what "foo") (join "w<=what" (and (eq i... 7.58
258
+ delete! 0.51
259
+ (and (eq issue *) (eq repository *) (eq what '*') (eq where '*')) 0.43
260
+ Taped.append() x50000 0.02
261
+ Taped.each() x125 1.15
262
+ Taped.delete_if() x375 0.97
263
+ 50000 facts: read-only txn (no copy needed) 5.30
264
+ 50000 facts: rollback txn (no copy needed) 5.16
265
+ 50000 facts: insert in txn (copy triggered) 3.23
266
+ 50000 facts: modify in txn (copy triggered) 34.56
267
+ 100000 facts: read-only txn (no copy needed) 12.28
268
+ 100000 facts: rollback txn (no copy needed) 12.19
269
+ 100000 facts: insert in txn (copy triggered) 6.57
270
+ 100000 facts: modify in txn (copy triggered) 70.11
256
271
  ```
257
272
 
258
273
  The results were calculated in [this GHA job][benchmark-gha]
259
- on 2025-12-11 at 13:29,
274
+ on 2026-02-04 at 05:19,
260
275
  on Linux with 4 CPUs.
261
276
  <!-- benchmark_end -->
262
277
 
263
- [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/20134646978
278
+ [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/21659492807
data/Rakefile CHANGED
@@ -35,7 +35,7 @@ task :picks do
35
35
  next if OS.windows?
36
36
  %w[test lib].each do |d|
37
37
  Dir["#{d}/**/*.rb"].each do |f|
38
- qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout, env: { 'PICKS' => 'yes' })
38
+ qbash("bundle exec ruby #{Shellwords.escape(f)}", stdout: $stdout, env: { 'PICKS' => 'yes' })
39
39
  end
40
40
  end
41
41
  end
@@ -26,6 +26,10 @@ class Factbase::IndexedAbsent
26
26
  end
27
27
  entry[:indexed_count] = maps_array.size
28
28
  end
29
- (maps & []) | entry[:facts]
29
+ if maps.respond_to?(:ensure_copied!)
30
+ maps & entry[:facts]
31
+ else
32
+ (maps & []) | entry[:facts]
33
+ end
30
34
  end
31
35
  end
@@ -45,7 +45,7 @@ class Factbase::IndexedAnd
45
45
  j = tuples.flat_map { |t| entry[:index][t] || [] }.uniq(&:object_id)
46
46
  r =
47
47
  if maps.respond_to?(:inserted)
48
- Factbase::Taped.new(j, inserted: maps.inserted, deleted: maps.deleted, added: maps.added)
48
+ maps & j
49
49
  else
50
50
  j
51
51
  end
@@ -38,7 +38,7 @@ class Factbase::IndexedEq
38
38
  end
39
39
  j = vv.flat_map { |v| entry[:index][v] || [] }.uniq(&:object_id)
40
40
  if maps.respond_to?(:inserted)
41
- Factbase::Taped.new(j, inserted: maps.inserted, deleted: maps.deleted, added: maps.added)
41
+ maps & j
42
42
  else
43
43
  j
44
44
  end
@@ -26,6 +26,10 @@ class Factbase::IndexedExists
26
26
  end
27
27
  entry[:indexed_count] = maps_array.size
28
28
  end
29
- (maps & []) | entry[:facts]
29
+ if maps.respond_to?(:ensure_copied!)
30
+ maps & entry[:facts]
31
+ else
32
+ (maps & []) | entry[:facts]
33
+ end
30
34
  end
31
35
  end
@@ -15,8 +15,8 @@ class Factbase::IndexedFact
15
15
  # Ctor.
16
16
  # @param [Factbase::Fact] origin The original fact
17
17
  # @param [Hash] idx The index
18
- # @param [Boolean] fresh True if this is a newly inserted fact (not yet in index)
19
- def initialize(origin, idx, fresh: false)
18
+ # @param [Set] fresh The shared set of fresh fact IDs
19
+ def initialize(origin, idx, fresh)
20
20
  @origin = origin
21
21
  @idx = idx
22
22
  @fresh = fresh
@@ -30,7 +30,7 @@ class Factbase::IndexedFact
30
30
  others do |*args|
31
31
  # Only clear index when modifying properties on existing (non-fresh) facts
32
32
  # Fresh facts are not in the index yet, so modifications don't affect it
33
- @idx.clear if args[0].to_s.end_with?('=') && !@fresh
33
+ @idx.clear if args[0].to_s.end_with?('=') && !@fresh.include?(object_id)
34
34
  @origin.send(*args)
35
35
  end
36
36
  end
@@ -21,17 +21,21 @@ class Factbase::IndexedFactbase
21
21
  # Constructor.
22
22
  # @param [Factbase] origin Original factbase to decorate
23
23
  # @param [Hash] idx Index to use
24
- def initialize(origin, idx = {})
24
+ # @param [Set] fresh The set of IDs of newly inserted facts
25
+ def initialize(origin, idx = {}, fresh = Set.new)
25
26
  raise 'Wrong type of original' unless origin.respond_to?(:query)
26
27
  @origin = origin
27
28
  raise 'Wrong type of index' unless idx.is_a?(Hash)
28
29
  @idx = idx
30
+ @fresh = fresh
29
31
  end
30
32
 
31
33
  # Insert a new fact and return it.
32
34
  # @return [Factbase::Fact] The fact just inserted
33
35
  def insert
34
- Factbase::IndexedFact.new(@origin.insert, @idx, fresh: true)
36
+ f = Factbase::IndexedFact.new(@origin.insert, @idx, @fresh)
37
+ @fresh.add(f.object_id)
38
+ f
35
39
  end
36
40
 
37
41
  # Convert a query to a term.
@@ -49,7 +53,9 @@ class Factbase::IndexedFactbase
49
53
  def query(term, maps = nil)
50
54
  term = to_term(term) if term.is_a?(String)
51
55
  q = @origin.query(term, maps)
52
- Factbase::IndexedQuery.new(q, @idx, self)
56
+ q = Factbase::IndexedQuery.new(q, @idx, self, @fresh)
57
+ @fresh.clear
58
+ q
53
59
  end
54
60
 
55
61
  # Run an ACID transaction.
@@ -57,9 +63,10 @@ class Factbase::IndexedFactbase
57
63
  def txn
58
64
  result =
59
65
  @origin.txn do |fbt|
60
- yield Factbase::IndexedFactbase.new(fbt, @idx)
66
+ yield Factbase::IndexedFactbase.new(fbt, @idx, @fresh)
61
67
  end
62
68
  @idx.clear
69
+ @fresh.clear
63
70
  result
64
71
  end
65
72
 
@@ -101,6 +108,7 @@ class Factbase::IndexedFactbase
101
108
  @origin.import(bytes)
102
109
  @idx.clear
103
110
  end
111
+ @fresh.clear
104
112
  end
105
113
 
106
114
  # Size, the total number of facts in the factbase.
@@ -36,7 +36,11 @@ class Factbase::IndexedGt
36
36
  return nil if threshold.nil?
37
37
  i = entry[:sorted].bsearch_index { |pair| pair[0] > threshold } || entry[:sorted].size
38
38
  result = entry[:sorted][i..].map { |pair| pair[1] }.uniq
39
- (maps & []) | result
39
+ if maps.respond_to?(:ensure_copied!)
40
+ maps & result
41
+ else
42
+ (maps & []) | result
43
+ end
40
44
  end
41
45
 
42
46
  private
@@ -41,7 +41,11 @@ class Factbase::IndexedLt
41
41
  return nil if threshold.nil?
42
42
  i = entry[:sorted].bsearch_index { |pair| pair[0] >= threshold } || entry[:sorted].size
43
43
  result = entry[:sorted][0...i].map { |pair| pair[1] }.uniq
44
- (maps & []) | result
44
+ if maps.respond_to?(:ensure_copied!)
45
+ maps & result
46
+ else
47
+ (maps & []) | result
48
+ end
45
49
  end
46
50
 
47
51
  private
@@ -34,6 +34,8 @@ class Factbase::IndexedNot
34
34
  r = entry[:facts]
35
35
  if r.nil?
36
36
  nil
37
+ elsif maps.respond_to?(:ensure_copied!)
38
+ maps & r
37
39
  else
38
40
  (maps & []) | r
39
41
  end
@@ -26,6 +26,10 @@ class Factbase::IndexedOne
26
26
  end
27
27
  entry[:indexed_count] = maps_array.size
28
28
  end
29
- (maps & []) | entry[:facts]
29
+ if maps.respond_to?(:ensure_copied!)
30
+ maps & entry[:facts]
31
+ else
32
+ (maps & []) | entry[:facts]
33
+ end
30
34
  end
31
35
  end
@@ -17,10 +17,12 @@ class Factbase::IndexedQuery
17
17
  # Constructor.
18
18
  # @param [Factbase::Query] origin Original query
19
19
  # @param [Hash] idx The index
20
- def initialize(origin, idx, fb)
20
+ # @param [Set] fresh The set of IDs of newly inserted facts
21
+ def initialize(origin, idx, fb, fresh)
21
22
  @origin = origin
22
23
  @idx = idx
23
24
  @fb = fb
25
+ @fresh = fresh
24
26
  end
25
27
 
26
28
  # Print it as a string.
@@ -37,7 +39,7 @@ class Factbase::IndexedQuery
37
39
  return to_enum(__method__, fb, params) unless block_given?
38
40
  a = @origin.each(fb, params).to_a
39
41
  a.each do |f|
40
- yield Factbase::IndexedFact.new(f, @idx)
42
+ yield Factbase::IndexedFact.new(f, @idx, @fresh)
41
43
  end
42
44
  a.size
43
45
  end
@@ -4,9 +4,23 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  # Indexed term 'unique'.
7
- # @todo #249:30min Improve prediction for 'unique' term. Current prediction is quite naive and
8
- # returns many false positives because it just filters facts which have exactly the same set
9
- # of keys regardless the values. We should introduce more smart prediction.
7
+ # The @idx[ikey] structure:
8
+ # {
9
+ # count: Integer (number of facts already processed),
10
+ # buckets: {
11
+ # key => {
12
+ # facts: Array (unique facts found),
13
+ # seen: Set (composite values already indexed to skip duplicates)
14
+ # }
15
+ # }
16
+ # }
17
+ # Example 1: (unique "fruit")
18
+ # - Apple, Apple, Banana
19
+ # - count: 3, facts: [Apple, Banana], seen: { [Apple], [Banana] }
20
+ #
21
+ # Example 2: (unique "fruit" "color")
22
+ # - [Apple, Red], [Apple, Green], [Apple, Red]
23
+ # - count: 3, facts: [[Apple, Red], [Apple, Green]], seen: { [Apple, Red], [Apple, Green] }
10
24
  class Factbase::IndexedUnique
11
25
  def initialize(term, idx)
12
26
  @term = term
@@ -14,21 +28,29 @@ class Factbase::IndexedUnique
14
28
  end
15
29
 
16
30
  def predict(maps, _fb, _params)
17
- return nil if @idx.nil?
18
- key = [maps.object_id, @term.operands.first, @term.op]
19
- entry = @idx[key]
20
- maps_array = maps.to_a
21
- if entry.nil?
22
- entry = { facts: [], indexed_count: 0 }
23
- @idx[key] = entry
31
+ operands = @term.operands.map(&:to_s)
32
+ bucket_key = operands.join('|')
33
+ idx_key = [maps.object_id, @term.op.to_s, bucket_key]
34
+ entry = (@idx[idx_key] ||= { buckets: {}, count: 0 })
35
+ feed(maps.to_a, entry, operands, bucket_key)
36
+ bucket = entry[:buckets][bucket_key]
37
+ if maps.respond_to?(:ensure_copied!)
38
+ maps & (bucket[:facts] || [])
39
+ else
40
+ (maps & []) | (bucket[:facts] || [])
24
41
  end
25
- if entry[:indexed_count] < maps_array.size
26
- props = @term.operands.map(&:to_s)
27
- maps_array[entry[:indexed_count]..].each do |m|
28
- entry[:facts] << m if props.all? { |p| !m[p].nil? }
29
- end
30
- entry[:indexed_count] = maps_array.size
42
+ end
43
+
44
+ private
45
+
46
+ def feed(facts, entry, operands, bucket_key)
47
+ entry[:buckets][bucket_key] ||= { facts: [], seen: Set.new }
48
+ bucket = entry[:buckets][bucket_key]
49
+ (facts[entry[:count]..] || []).each do |fact|
50
+ composite_val = operands.map { |o| fact[o] }
51
+ next if composite_val.any?(&:nil?)
52
+ bucket[:facts] << fact if bucket[:seen].add?(composite_val)
31
53
  end
32
- (maps & []) | entry[:facts]
54
+ entry[:count] = facts.size
33
55
  end
34
56
  end
@@ -62,7 +62,7 @@ class Factbase::LazyTaped
62
62
  end
63
63
 
64
64
  def <<(map)
65
- ensure_copied
65
+ ensure_copied!
66
66
  @maps << map
67
67
  @inserted.append(map.object_id)
68
68
  end
@@ -81,7 +81,7 @@ class Factbase::LazyTaped
81
81
  end
82
82
 
83
83
  def delete_if
84
- ensure_copied
84
+ ensure_copied!
85
85
  @maps.delete_if do |m|
86
86
  r = yield m
87
87
  @deleted.append(@pairs[m].object_id) if r
@@ -108,7 +108,7 @@ class Factbase::LazyTaped
108
108
  join(other, &:|)
109
109
  end
110
110
 
111
- def ensure_copied
111
+ def ensure_copied!
112
112
  return if @copied
113
113
  @pairs = {}.compare_by_identity
114
114
  @maps =
@@ -121,13 +121,14 @@ class Factbase::LazyTaped
121
121
  end
122
122
 
123
123
  def get_copied_map(original_map)
124
- ensure_copied
124
+ ensure_copied!
125
125
  @maps.find { |m| @pairs[m].equal?(original_map) }
126
126
  end
127
127
 
128
128
  private
129
129
 
130
130
  def join(other)
131
+ ensure_copied!
131
132
  n = yield (@maps || @origin).to_a, other.to_a
132
133
  raise 'Cannot join with another Taped' if other.respond_to?(:inserted)
133
134
  raise 'Can only join with array' unless other.is_a?(Array)
@@ -108,7 +108,7 @@ class Factbase::Syntax
108
108
  comment = false if comment && c == "\n"
109
109
  next if comment
110
110
  if quotes.include?(c)
111
- if string && acc[acc.length - 1] == '\\'
111
+ if string && acc[-1] == '\\'
112
112
  acc = acc[0..-2]
113
113
  else
114
114
  string = !string
@@ -27,7 +27,7 @@ class Factbase::Defn < Factbase::TermBase
27
27
  fn = @operands[0]
28
28
  raise "A symbol expected as first argument of 'defn'" unless fn.is_a?(Symbol)
29
29
  raise "Can't use '#{fn}' name as a term" if Factbase::Term.method_defined?(fn)
30
- raise "Term '#{fn}' is already defined" if Factbase::Term.private_instance_methods(false).include?(fn)
30
+ raise "Term '#{fn}' is already defined" if Factbase::Term.private_method_defined?(fn, false)
31
31
  raise "The '#{fn}' is a bad name for a term" unless fn.match?(/^[a-z_]+$/)
32
32
  e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps, fb)\n#{@operands[1]}\nend\nend"
33
33
  # rubocop:disable Security/Eval
@@ -25,7 +25,7 @@ class Factbase::Undef < Factbase::TermBase
25
25
  assert_args(1)
26
26
  fn = @operands[0]
27
27
  raise "A symbol expected as first argument of 'undef'" unless fn.is_a?(Symbol)
28
- if Factbase::Term.private_instance_methods(false).include?(fn)
28
+ if Factbase::Term.private_method_defined?(fn, false)
29
29
  Factbase::Term.class_eval("undef :#{fn}", __FILE__, __LINE__ - 1) # undef :foo
30
30
  end
31
31
  true
@@ -9,5 +9,5 @@
9
9
  # License:: MIT
10
10
  class Factbase
11
11
  # Current version of the gem (changed by .rultor.yml on every release)
12
- VERSION = '0.19.3' unless const_defined?(:VERSION)
12
+ VERSION = '0.19.5' unless const_defined?(:VERSION)
13
13
  end
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.19.3
4
+ version: 0.19.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko