factbase 0.17.1 → 0.18.0
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 +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/lib/factbase/lazy_taped.rb +141 -0
- data/lib/factbase/lazy_taped_array.rb +62 -0
- data/lib/factbase/lazy_taped_hash.rb +79 -0
- data/lib/factbase/term.rb +2 -4
- data/lib/factbase/terms/agg.rb +1 -1
- data/lib/factbase/terms/base.rb +56 -3
- data/lib/factbase/version.rb +1 -1
- data/lib/factbase.rb +13 -20
- metadata +4 -2
- data/lib/factbase/terms/shared.rb +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90b1ab3d82ed371b4de11b7eceb98137b2a7687bc2625e61d4d54a6538849b34
|
|
4
|
+
data.tar.gz: f9fd82d8254d7cfa646a742db6989790791ce157ce53d9e5a310f8f8f8c0db11
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae0d064e075f304c9fae6f4903c961293d07e186ee6bdf4e180d5f407f5b166f2b3cc345cec612d8184675394a2ee504197d4c13d9ce0f480cffd850d7d10169
|
|
7
|
+
data.tar.gz: b53da29f44f9ad5520bed3602c9c7dadf882115856ddb344dfba95a32017cd2e89c4582a7a160baa2ed611ff37acd0ba39e8fb9a8aa8ed7bec62f0acf9f0ee70
|
data/Gemfile
CHANGED
|
@@ -12,7 +12,7 @@ gem 'minitest-reporters', '~>1.7', require: false
|
|
|
12
12
|
gem 'os', '~>1.1', require: false
|
|
13
13
|
gem 'qbash', '~>0.4', require: false
|
|
14
14
|
gem 'rake', '~>13.2', require: false
|
|
15
|
-
gem 'rdoc', '6.
|
|
15
|
+
gem 'rdoc', '6.17.0', require: false # GPL
|
|
16
16
|
gem 'rubocop', '~>1.74', require: false
|
|
17
17
|
gem 'rubocop-minitest', '~>0.38', require: false
|
|
18
18
|
gem 'rubocop-performance', '~>1.25', require: false
|
data/Gemfile.lock
CHANGED
|
@@ -68,7 +68,7 @@ GEM
|
|
|
68
68
|
racc (1.8.1)
|
|
69
69
|
rainbow (3.1.1)
|
|
70
70
|
rake (13.3.1)
|
|
71
|
-
rdoc (6.
|
|
71
|
+
rdoc (6.17.0)
|
|
72
72
|
erb
|
|
73
73
|
psych (>= 4.0.0)
|
|
74
74
|
tsort
|
|
@@ -140,7 +140,7 @@ DEPENDENCIES
|
|
|
140
140
|
os (~> 1.1)
|
|
141
141
|
qbash (~> 0.4)
|
|
142
142
|
rake (~> 13.2)
|
|
143
|
-
rdoc (= 6.
|
|
143
|
+
rdoc (= 6.17.0)
|
|
144
144
|
rubocop (~> 1.74)
|
|
145
145
|
rubocop-minitest (~> 0.38)
|
|
146
146
|
rubocop-performance (~> 1.25)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../factbase'
|
|
7
|
+
require_relative 'taped'
|
|
8
|
+
require_relative 'lazy_taped_hash'
|
|
9
|
+
|
|
10
|
+
# A lazy decorator of an Array with HashMaps that defers copying until modification.
|
|
11
|
+
class Factbase::LazyTaped
|
|
12
|
+
def initialize(origin)
|
|
13
|
+
@origin = origin
|
|
14
|
+
@copied = false
|
|
15
|
+
@maps = nil
|
|
16
|
+
@pairs = nil
|
|
17
|
+
@inserted = []
|
|
18
|
+
@deleted = []
|
|
19
|
+
@added = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns a hash mapping copied maps to their originals.
|
|
23
|
+
# This is used during transaction commit to identify which original facts
|
|
24
|
+
# were modified, allowing the factbase to update the correct entries.
|
|
25
|
+
def pairs
|
|
26
|
+
return {} unless @pairs
|
|
27
|
+
result = {}.compare_by_identity
|
|
28
|
+
@pairs.each { |copied, original| result[copied] = original }
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the unique object IDs of maps that were inserted (newly created).
|
|
33
|
+
# This is used during transaction commit to identify new facts that need
|
|
34
|
+
# to be added to the factbase.
|
|
35
|
+
def inserted
|
|
36
|
+
@inserted.uniq
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the unique object IDs of maps that were deleted.
|
|
40
|
+
# This is used during transaction commit to identify facts that need
|
|
41
|
+
# to be removed from the factbase.
|
|
42
|
+
def deleted
|
|
43
|
+
@deleted.uniq
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the unique object IDs of maps that were modified (properties added).
|
|
47
|
+
# This is used during transaction commit to track the churn (number of changes).
|
|
48
|
+
def added
|
|
49
|
+
@added.uniq
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_by_object_id(oid)
|
|
53
|
+
(@maps || @origin).find { |m| m.object_id == oid }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def size
|
|
57
|
+
(@maps || @origin).size
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def empty?
|
|
61
|
+
(@maps || @origin).empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def <<(map)
|
|
65
|
+
ensure_copied
|
|
66
|
+
@maps << map
|
|
67
|
+
@inserted.append(map.object_id)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def each
|
|
71
|
+
return to_enum(__method__) unless block_given?
|
|
72
|
+
if @copied
|
|
73
|
+
@maps.each do |m|
|
|
74
|
+
yield Factbase::Taped::TapedHash.new(m, @added)
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
@origin.each do |m|
|
|
78
|
+
yield LazyTapedHash.new(m, self, @added)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def delete_if
|
|
84
|
+
ensure_copied
|
|
85
|
+
@maps.delete_if do |m|
|
|
86
|
+
r = yield m
|
|
87
|
+
@deleted.append(@pairs[m].object_id) if r
|
|
88
|
+
r
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_a
|
|
93
|
+
(@maps || @origin).to_a
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def &(other)
|
|
97
|
+
if other == [] || (@maps || @origin).empty?
|
|
98
|
+
return Factbase::Taped.new([], inserted: @inserted, deleted: @deleted, added: @added)
|
|
99
|
+
end
|
|
100
|
+
join(other, &:&)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def |(other)
|
|
104
|
+
return Factbase::Taped.new(to_a, inserted: @inserted, deleted: @deleted, added: @added) if other == []
|
|
105
|
+
if (@maps || @origin).empty?
|
|
106
|
+
return Factbase::Taped.new(other, inserted: @inserted, deleted: @deleted, added: @added)
|
|
107
|
+
end
|
|
108
|
+
join(other, &:|)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def ensure_copied
|
|
112
|
+
return if @copied
|
|
113
|
+
@pairs = {}.compare_by_identity
|
|
114
|
+
@maps =
|
|
115
|
+
@origin.map do |m|
|
|
116
|
+
n = m.transform_values(&:dup)
|
|
117
|
+
@pairs[n] = m
|
|
118
|
+
n
|
|
119
|
+
end
|
|
120
|
+
@copied = true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def get_copied_map(original_map)
|
|
124
|
+
ensure_copied
|
|
125
|
+
@maps.find { |m| @pairs[m].equal?(original_map) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def join(other)
|
|
131
|
+
n = yield (@maps || @origin).to_a, other.to_a
|
|
132
|
+
raise 'Cannot join with another Taped' if other.respond_to?(:inserted)
|
|
133
|
+
raise 'Can only join with array' unless other.is_a?(Array)
|
|
134
|
+
Factbase::Taped.new(
|
|
135
|
+
n,
|
|
136
|
+
inserted: @inserted,
|
|
137
|
+
deleted: @deleted,
|
|
138
|
+
added: @added
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../factbase'
|
|
7
|
+
|
|
8
|
+
class Factbase::LazyTaped
|
|
9
|
+
# Decorator of Array that triggers copy-on-write.
|
|
10
|
+
# @todo #424:30min Add dedicated unit tests for LazyTapedArray class.
|
|
11
|
+
# Currently this class is tested indirectly through LazyTaped tests.
|
|
12
|
+
# We should add explicit tests for all public methods including each, [],
|
|
13
|
+
# to_a, any?, <<, and uniq! to ensure proper copy-on-write behavior.
|
|
14
|
+
class LazyTapedArray
|
|
15
|
+
# Creates a new lazy array wrapper.
|
|
16
|
+
# @param origin [Array] The original array to wrap
|
|
17
|
+
# @param key [String] The key in the parent hash where this array is stored
|
|
18
|
+
# @param taped_hash [LazyTapedHash] The parent hash wrapper that owns this array
|
|
19
|
+
# @param added [Array] Accumulator for tracking object IDs of modified facts
|
|
20
|
+
def initialize(origin, key, taped_hash, added)
|
|
21
|
+
@origin = origin
|
|
22
|
+
@key = key
|
|
23
|
+
@taped_hash = taped_hash
|
|
24
|
+
@added = added
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def each(&)
|
|
28
|
+
return to_enum(__method__) unless block_given?
|
|
29
|
+
current_array.each(&)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def [](idx)
|
|
33
|
+
current_array[idx]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_a
|
|
37
|
+
current_array.to_a
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def any?(pattern = nil, &)
|
|
41
|
+
pattern ? current_array.any?(pattern) : current_array.any?(&)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def <<(item)
|
|
45
|
+
@taped_hash.ensure_copied_map
|
|
46
|
+
@added.append(@taped_hash.tracking_id)
|
|
47
|
+
@taped_hash.get_copied_array(@key) << item
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def uniq!
|
|
51
|
+
@taped_hash.ensure_copied_map
|
|
52
|
+
@added.append(@taped_hash.tracking_id)
|
|
53
|
+
@taped_hash.get_copied_array(@key).uniq!
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def current_array
|
|
59
|
+
@taped_hash.copied? ? @taped_hash.get_copied_array(@key) : @origin
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../factbase'
|
|
7
|
+
require_relative 'lazy_taped_array'
|
|
8
|
+
|
|
9
|
+
class Factbase::LazyTaped
|
|
10
|
+
# Decorator of Hash that triggers copy-on-write.
|
|
11
|
+
# @todo #424:30min Add dedicated unit tests for LazyTapedHash class.
|
|
12
|
+
# Currently this class is tested indirectly through LazyTaped tests.
|
|
13
|
+
# We should add explicit tests for all public methods including keys, map,
|
|
14
|
+
# bracket access, bracket assignment, and the copy-on-write behavior.
|
|
15
|
+
class LazyTapedHash
|
|
16
|
+
# Creates a new LazyTapedHash decorator.
|
|
17
|
+
# @param origin [Hash] The original hash being wrapped (not yet copied)
|
|
18
|
+
# @param lazy_taped [Factbase::LazyTaped] The parent LazyTaped instance that manages copy-on-write
|
|
19
|
+
# @param added [Array] Array to track object IDs of maps that have been modified
|
|
20
|
+
def initialize(origin, lazy_taped, added)
|
|
21
|
+
@origin = origin
|
|
22
|
+
@lazy_taped = lazy_taped
|
|
23
|
+
@added = added
|
|
24
|
+
@copied_map = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def keys
|
|
28
|
+
current_map.keys
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def map(&)
|
|
32
|
+
current_map.map(&)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def [](key)
|
|
36
|
+
v = current_map[key]
|
|
37
|
+
v = LazyTapedArray.new(v, key, self, @added) if v.is_a?(Array)
|
|
38
|
+
v
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def []=(key, value)
|
|
42
|
+
ensure_copied_map
|
|
43
|
+
@copied_map[key] = value
|
|
44
|
+
@added.append(@copied_map.object_id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def ensure_copied_map
|
|
48
|
+
return if @copied_map
|
|
49
|
+
@copied_map = @lazy_taped.get_copied_map(@origin)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_copied_array(key)
|
|
53
|
+
ensure_copied_map
|
|
54
|
+
@copied_map[key]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tracking_id
|
|
58
|
+
@copied_map ? @copied_map.object_id : @origin.object_id
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def copied?
|
|
62
|
+
!@copied_map.nil?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def current_map
|
|
68
|
+
@copied_map || @origin
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def method_missing(method, *, &)
|
|
72
|
+
current_map.send(method, *, &)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def respond_to_missing?(method, include_private = false)
|
|
76
|
+
current_map.respond_to?(method, include_private)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/factbase/term.rb
CHANGED
|
@@ -86,7 +86,7 @@ require_relative 'terms/max'
|
|
|
86
86
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
87
87
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
88
88
|
# License:: MIT
|
|
89
|
-
class Factbase::Term
|
|
89
|
+
class Factbase::Term < Factbase::TermBase
|
|
90
90
|
# The operator of this term
|
|
91
91
|
# @return [Symbol] The operator
|
|
92
92
|
attr_reader :op
|
|
@@ -95,13 +95,11 @@ class Factbase::Term
|
|
|
95
95
|
# @return [Array] The operands
|
|
96
96
|
attr_reader :operands
|
|
97
97
|
|
|
98
|
-
require_relative 'terms/shared'
|
|
99
|
-
include Factbase::TermShared
|
|
100
|
-
|
|
101
98
|
# Ctor.
|
|
102
99
|
# @param [Symbol] operator Operator
|
|
103
100
|
# @param [Array] operands Operands
|
|
104
101
|
def initialize(operator, operands)
|
|
102
|
+
super()
|
|
105
103
|
@op = operator
|
|
106
104
|
@operands = operands
|
|
107
105
|
@terms = {
|
data/lib/factbase/terms/agg.rb
CHANGED
|
@@ -26,7 +26,7 @@ class Factbase::Agg < Factbase::TermBase
|
|
|
26
26
|
raise "A term is expected, but '#{selector}' provided"
|
|
27
27
|
end
|
|
28
28
|
term = @operands[1]
|
|
29
|
-
unless term.is_a?(Factbase::Term) ||
|
|
29
|
+
unless term.is_a?(Factbase::Term) || term.is_a?(Factbase::TermBase)
|
|
30
30
|
raise "A term is expected, but '#{term}' provided"
|
|
31
31
|
end
|
|
32
32
|
subset = fb.query(selector, maps).each(fb, fact).to_a
|
data/lib/factbase/terms/base.rb
CHANGED
|
@@ -10,8 +10,61 @@
|
|
|
10
10
|
|
|
11
11
|
# Base class for all terms.
|
|
12
12
|
class Factbase::TermBase
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# Turns it into a string.
|
|
14
|
+
# @return [String] The string of it
|
|
15
|
+
def to_s
|
|
16
|
+
@to_s ||=
|
|
17
|
+
begin
|
|
18
|
+
items = []
|
|
19
|
+
items << @op
|
|
20
|
+
items +=
|
|
21
|
+
@operands.map do |o|
|
|
22
|
+
if o.is_a?(String)
|
|
23
|
+
"'#{o.gsub("'", "\\\\'").gsub('"', '\\\\"')}'"
|
|
24
|
+
elsif o.is_a?(Time)
|
|
25
|
+
o.utc.iso8601
|
|
26
|
+
else
|
|
27
|
+
o.to_s
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
"(#{items.join(' ')})"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def assert_args(num)
|
|
37
|
+
c = @operands.size
|
|
38
|
+
raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
|
|
39
|
+
raise "Too few (#{c}) operands for '#{@op}' (#{num} expected)" if c < num
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def _by_symbol(pos, fact)
|
|
43
|
+
o = @operands[pos]
|
|
44
|
+
raise "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided" unless o.is_a?(Symbol)
|
|
45
|
+
k = o.to_s
|
|
46
|
+
fact[k]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Array|nil] Either array of values or NIL
|
|
50
|
+
def _values(pos, fact, maps, fb)
|
|
51
|
+
v = @operands[pos]
|
|
52
|
+
v = v.evaluate(fact, maps, fb) if v.is_a?(Factbase::Term)
|
|
53
|
+
v = v.evaluate(fact, maps, fb) if v.is_a?(Factbase::TermBase)
|
|
54
|
+
v = fact[v.to_s] if v.is_a?(Symbol)
|
|
55
|
+
return v if v.nil?
|
|
56
|
+
unless v.is_a?(Array)
|
|
57
|
+
v =
|
|
58
|
+
if v.respond_to?(:each)
|
|
59
|
+
v.to_a
|
|
60
|
+
else
|
|
61
|
+
[v]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
raise 'Why not array?' unless v.is_a?(Array)
|
|
65
|
+
unless v.all? { |i| [Float, Integer, String, Time, TrueClass, FalseClass].any? { |t| i.is_a?(t) } }
|
|
66
|
+
raise 'Wrong type inside'
|
|
67
|
+
end
|
|
68
|
+
v
|
|
69
|
+
end
|
|
17
70
|
end
|
data/lib/factbase/version.rb
CHANGED
data/lib/factbase.rb
CHANGED
|
@@ -164,17 +164,8 @@ class Factbase
|
|
|
164
164
|
#
|
|
165
165
|
# @return [Factbase::Churn] How many facts have been changed (zero if rolled back)
|
|
166
166
|
def txn
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
@maps.map do |m|
|
|
170
|
-
n = m.transform_values(&:dup)
|
|
171
|
-
# rubocop:disable Lint/HashCompareByIdentity
|
|
172
|
-
pairs[n.object_id] = m.object_id
|
|
173
|
-
# rubocop:enable Lint/HashCompareByIdentity
|
|
174
|
-
n
|
|
175
|
-
end
|
|
176
|
-
require_relative 'factbase/taped'
|
|
177
|
-
taped = Factbase::Taped.new(before)
|
|
167
|
+
require_relative 'factbase/lazy_taped'
|
|
168
|
+
taped = Factbase::LazyTaped.new(@maps)
|
|
178
169
|
require_relative 'factbase/churn'
|
|
179
170
|
churn = Factbase::Churn.new
|
|
180
171
|
catch :commit do
|
|
@@ -188,30 +179,32 @@ class Factbase
|
|
|
188
179
|
rescue Factbase::Rollback
|
|
189
180
|
return churn
|
|
190
181
|
end
|
|
191
|
-
seen =
|
|
192
|
-
garbage =
|
|
182
|
+
seen = {}.compare_by_identity
|
|
183
|
+
garbage = {}.compare_by_identity
|
|
184
|
+
pairs = taped.pairs
|
|
193
185
|
taped.deleted.each do |oid|
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
original = @maps.find { |m| m.object_id == oid }
|
|
187
|
+
next if original.nil?
|
|
188
|
+
garbage[original] = true
|
|
196
189
|
churn.append(0, 1, 0)
|
|
197
190
|
end
|
|
198
191
|
taped.inserted.each do |oid|
|
|
199
|
-
next if seen.include?(oid)
|
|
200
192
|
b = taped.find_by_object_id(oid)
|
|
201
193
|
next if b.nil?
|
|
202
|
-
|
|
194
|
+
next if seen.key?(b)
|
|
195
|
+
seen[b] = true
|
|
203
196
|
@maps << b
|
|
204
197
|
churn.append(1, 0, 0)
|
|
205
198
|
end
|
|
206
199
|
taped.added.each do |oid|
|
|
207
|
-
next if seen.include?(oid)
|
|
208
200
|
b = taped.find_by_object_id(oid)
|
|
209
201
|
next if b.nil?
|
|
210
|
-
|
|
202
|
+
next if seen.key?(b)
|
|
203
|
+
garbage[pairs[b]] = true
|
|
211
204
|
@maps << b
|
|
212
205
|
churn.append(0, 0, 1)
|
|
213
206
|
end
|
|
214
|
-
@maps.delete_if { |m| garbage.
|
|
207
|
+
@maps.delete_if { |m| garbage.key?(m) } unless garbage.empty?
|
|
215
208
|
churn
|
|
216
209
|
end
|
|
217
210
|
|
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.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -195,6 +195,9 @@ files:
|
|
|
195
195
|
- lib/factbase/indexed/indexed_term.rb
|
|
196
196
|
- lib/factbase/indexed/indexed_unique.rb
|
|
197
197
|
- lib/factbase/inv.rb
|
|
198
|
+
- lib/factbase/lazy_taped.rb
|
|
199
|
+
- lib/factbase/lazy_taped_array.rb
|
|
200
|
+
- lib/factbase/lazy_taped_hash.rb
|
|
198
201
|
- lib/factbase/light.rb
|
|
199
202
|
- lib/factbase/logged.rb
|
|
200
203
|
- lib/factbase/pre.rb
|
|
@@ -248,7 +251,6 @@ files:
|
|
|
248
251
|
- lib/factbase/terms/or.rb
|
|
249
252
|
- lib/factbase/terms/plus.rb
|
|
250
253
|
- lib/factbase/terms/prev.rb
|
|
251
|
-
- lib/factbase/terms/shared.rb
|
|
252
254
|
- lib/factbase/terms/simplified.rb
|
|
253
255
|
- lib/factbase/terms/size.rb
|
|
254
256
|
- lib/factbase/terms/sorted.rb
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
-
# SPDX-License-Identifier: MIT
|
|
5
|
-
|
|
6
|
-
# This module provides shared methods for Factbase terms, including argument validation,
|
|
7
|
-
# symbol-based lookups, and handling of operand values.
|
|
8
|
-
# @todo #302:30min Remove this module and move its methods to Factbase::TermBase.
|
|
9
|
-
# Currently, we use it because we are required to inject all thesse methods into Factbase::Term.
|
|
10
|
-
# When all the terms will inherit from Factbase::TermBase, we can remove this module.
|
|
11
|
-
module Factbase::TermShared
|
|
12
|
-
# Turns it into a string.
|
|
13
|
-
# @return [String] The string of it
|
|
14
|
-
def to_s
|
|
15
|
-
@to_s ||=
|
|
16
|
-
begin
|
|
17
|
-
items = []
|
|
18
|
-
items << @op
|
|
19
|
-
items +=
|
|
20
|
-
@operands.map do |o|
|
|
21
|
-
if o.is_a?(String)
|
|
22
|
-
"'#{o.gsub("'", "\\\\'").gsub('"', '\\\\"')}'"
|
|
23
|
-
elsif o.is_a?(Time)
|
|
24
|
-
o.utc.iso8601
|
|
25
|
-
else
|
|
26
|
-
o.to_s
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
"(#{items.join(' ')})"
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def assert_args(num)
|
|
36
|
-
c = @operands.size
|
|
37
|
-
raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
|
|
38
|
-
raise "Too few (#{c}) operands for '#{@op}' (#{num} expected)" if c < num
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def _by_symbol(pos, fact)
|
|
42
|
-
o = @operands[pos]
|
|
43
|
-
raise "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided" unless o.is_a?(Symbol)
|
|
44
|
-
k = o.to_s
|
|
45
|
-
fact[k]
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# @return [Array|nil] Either array of values or NIL
|
|
49
|
-
def _values(pos, fact, maps, fb)
|
|
50
|
-
v = @operands[pos]
|
|
51
|
-
v = v.evaluate(fact, maps, fb) if v.is_a?(Factbase::Term)
|
|
52
|
-
v = v.evaluate(fact, maps, fb) if v.is_a?(Factbase::TermBase)
|
|
53
|
-
v = fact[v.to_s] if v.is_a?(Symbol)
|
|
54
|
-
return v if v.nil?
|
|
55
|
-
unless v.is_a?(Array)
|
|
56
|
-
v =
|
|
57
|
-
if v.respond_to?(:each)
|
|
58
|
-
v.to_a
|
|
59
|
-
else
|
|
60
|
-
[v]
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
raise 'Why not array?' unless v.is_a?(Array)
|
|
64
|
-
unless v.all? { |i| [Float, Integer, String, Time, TrueClass, FalseClass].any? { |t| i.is_a?(t) } }
|
|
65
|
-
raise 'Wrong type inside'
|
|
66
|
-
end
|
|
67
|
-
v
|
|
68
|
-
end
|
|
69
|
-
end
|