factbase 0.8.0 → 0.9.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/.rubocop.yml +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -3
- data/README.md +24 -24
- data/REUSE.toml +7 -2
- data/Rakefile +8 -1
- data/benchmark/bench_factbase.rb +1 -1
- data/fixtures/stories/agg.yml +17 -0
- data/fixtures/stories/always.yml +16 -0
- data/fixtures/stories/as.yml +16 -0
- data/fixtures/stories/count.yml +18 -0
- data/fixtures/stories/eq.yml +30 -0
- data/fixtures/stories/gt.yml +18 -0
- data/fixtures/stories/join.yml +19 -0
- data/fixtures/stories/max.yml +14 -0
- data/fixtures/stories/min.yml +14 -0
- data/fixtures/stories/nth.yml +14 -0
- data/fixtures/stories/or.yml +18 -0
- data/fixtures/stories/sprintf.yml +12 -0
- data/fixtures/stories/sum.yml +14 -0
- data/lib/factbase/cached/cached_fact.rb +28 -0
- data/lib/factbase/cached/cached_factbase.rb +64 -0
- data/lib/factbase/cached/cached_query.rb +61 -0
- data/lib/factbase/cached/cached_term.rb +25 -0
- data/lib/factbase/fact.rb +13 -13
- data/lib/factbase/indexed/indexed_fact.rb +28 -0
- data/lib/factbase/indexed/indexed_factbase.rb +64 -0
- data/lib/factbase/indexed/indexed_query.rb +56 -0
- data/lib/factbase/indexed/indexed_term.rb +60 -0
- data/lib/factbase/light.rb +7 -6
- data/lib/factbase/logged.rb +67 -44
- data/lib/factbase/query.rb +29 -34
- data/lib/factbase/rules.rb +15 -14
- data/lib/factbase/sync/sync_factbase.rb +57 -0
- data/lib/factbase/sync/sync_query.rb +61 -0
- data/lib/factbase/syntax.rb +12 -25
- data/lib/factbase/tallied.rb +10 -9
- data/lib/factbase/taped.rb +8 -0
- data/lib/factbase/tee.rb +2 -0
- data/lib/factbase/term.rb +45 -17
- data/lib/factbase/terms/aggregates.rb +17 -15
- data/lib/factbase/terms/aliases.rb +4 -4
- data/lib/factbase/terms/casting.rb +8 -8
- data/lib/factbase/terms/debug.rb +2 -2
- data/lib/factbase/terms/defn.rb +3 -3
- data/lib/factbase/terms/logical.rb +53 -14
- data/lib/factbase/terms/math.rb +26 -26
- data/lib/factbase/terms/meta.rb +14 -14
- data/lib/factbase/terms/ordering.rb +4 -4
- data/lib/factbase/terms/strings.rb +8 -8
- data/lib/factbase/terms/system.rb +3 -3
- data/lib/factbase.rb +67 -55
- data/test/factbase/cached/test_cached_factbase.rb +22 -0
- data/test/factbase/cached/test_cached_query.rb +79 -0
- data/test/factbase/indexed/test_indexed_query.rb +175 -0
- data/test/factbase/sync/test_sync_query.rb +30 -0
- data/test/factbase/terms/test_aggregates.rb +5 -5
- data/test/factbase/terms/test_aliases.rb +7 -7
- data/test/factbase/terms/test_casting.rb +8 -8
- data/test/factbase/terms/test_debug.rb +6 -6
- data/test/factbase/terms/test_defn.rb +14 -14
- data/test/factbase/terms/test_logical.rb +17 -19
- data/test/factbase/terms/test_math.rb +63 -61
- data/test/factbase/terms/test_meta.rb +36 -36
- data/test/factbase/terms/test_ordering.rb +9 -9
- data/test/factbase/terms/test_strings.rb +10 -10
- data/test/factbase/terms/test_system.rb +6 -6
- data/test/factbase/test_accum.rb +5 -5
- data/test/factbase/test_fact.rb +12 -12
- data/test/factbase/test_logged.rb +7 -0
- data/test/factbase/test_query.rb +99 -37
- data/test/factbase/test_rules.rb +1 -1
- data/test/factbase/test_syntax.rb +12 -12
- data/test/factbase/test_tee.rb +8 -8
- data/test/factbase/test_term.rb +39 -30
- data/test/test__helper.rb +2 -2
- data/test/test_factbase.rb +6 -0
- metadata +29 -4
- data/lib/factbase/query_once.rb +0 -54
- data/lib/factbase/term_once.rb +0 -67
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require 'decoor'
|
7
|
+
require_relative '../../factbase'
|
8
|
+
|
9
|
+
# A synchronous thread-safe factbase.
|
10
|
+
#
|
11
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
12
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
13
|
+
# License:: MIT
|
14
|
+
class Factbase::SyncFactbase
|
15
|
+
decoor(:origin)
|
16
|
+
|
17
|
+
# Constructor.
|
18
|
+
# @param [Factbase] origin Original factbase to decorate
|
19
|
+
# @param [Mutex] mutex Mutex to use for synchronization
|
20
|
+
def initialize(origin, mutex = Mutex.new)
|
21
|
+
@origin = origin
|
22
|
+
@mutex = mutex
|
23
|
+
end
|
24
|
+
|
25
|
+
# Insert a new fact and return it.
|
26
|
+
# @return [Factbase::Fact] The fact just inserted
|
27
|
+
def insert
|
28
|
+
@mutex.synchronize do
|
29
|
+
@origin.insert
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Convert a query to a term.
|
34
|
+
# @param [String] query The query to convert
|
35
|
+
# @return [Factbase::Term] The term
|
36
|
+
def to_term(query)
|
37
|
+
@origin.to_term(query)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a query capable of iterating.
|
41
|
+
# @param [String] term The query to use for selections
|
42
|
+
# @param [Array<Hash>] maps Possible maps to use
|
43
|
+
def query(term, maps = nil)
|
44
|
+
term = to_term(term) if term.is_a?(String)
|
45
|
+
require_relative 'sync_query'
|
46
|
+
Factbase::SyncQuery.new(@origin.query(term, maps), @mutex, self)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Run an ACID transaction.
|
50
|
+
# @return [Factbase::Churn] How many facts have been changed (zero if rolled back)
|
51
|
+
# @yield [Factbase] Block to execute in transaction
|
52
|
+
def txn
|
53
|
+
@origin.txn do |fbt|
|
54
|
+
yield Factbase::SyncFactbase.new(fbt, @mutex)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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
|
+
# Synchronized thread-safe query.
|
9
|
+
#
|
10
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
11
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
|
+
# License:: MIT
|
13
|
+
class Factbase::SyncQuery
|
14
|
+
# Constructor.
|
15
|
+
# @param [Factbase::Query] origin Original query
|
16
|
+
# @param [Mutex] mutex The mutex
|
17
|
+
def initialize(origin, mutex, fb)
|
18
|
+
@origin = origin
|
19
|
+
@mutex = mutex
|
20
|
+
@fb = fb
|
21
|
+
end
|
22
|
+
|
23
|
+
# Iterate facts one by one.
|
24
|
+
# @param [Hash] params Optional params accessible in the query via the "$" symbol
|
25
|
+
# @yield [Fact] Facts one-by-one
|
26
|
+
# @return [Integer] Total number of facts yielded
|
27
|
+
def each(fb = @fb, params = {}, &)
|
28
|
+
return to_enum(__method__, fb, params) unless block_given?
|
29
|
+
try_lock do
|
30
|
+
@origin.each(fb, params, &)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Read a single value.
|
35
|
+
# @param [Factbase] fb The factbase
|
36
|
+
# @param [Hash] params Optional params accessible in the query via the "$" symbol
|
37
|
+
# @return [String|Integer|Float|Time|Array|NilClass] The value evaluated
|
38
|
+
def one(fb = @fb, params = {})
|
39
|
+
try_lock do
|
40
|
+
@origin.one(fb, params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Delete all facts that match the query.
|
45
|
+
# @param [Factbase] fb The factbase
|
46
|
+
# @return [Integer] Total number of facts deleted
|
47
|
+
def delete!(fb = @fb)
|
48
|
+
try_lock do
|
49
|
+
@origin.delete!(fb)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def try_lock
|
56
|
+
locked = @mutex.try_lock
|
57
|
+
r = yield
|
58
|
+
@mutex.unlock if locked
|
59
|
+
r
|
60
|
+
end
|
61
|
+
end
|
data/lib/factbase/syntax.rb
CHANGED
@@ -8,22 +8,11 @@ require 'time'
|
|
8
8
|
require_relative '../factbase'
|
9
9
|
require_relative 'fact'
|
10
10
|
require_relative 'term'
|
11
|
-
require_relative 'term_once'
|
12
11
|
|
13
|
-
# Syntax.
|
12
|
+
# Syntax parser.
|
14
13
|
#
|
15
14
|
# This is an internal class, it is not supposed to be instantiated directly.
|
16
15
|
#
|
17
|
-
# However, you can use it directly, if you need a parser of our syntax. You can
|
18
|
-
# create your own "Term" class and let this parser make instances of it for
|
19
|
-
# every term it meets in the query:
|
20
|
-
#
|
21
|
-
# require 'factbase/syntax'
|
22
|
-
# t = Factbase::Syntax.new(Factbase.new, '(hello world)', MyTerm).to_term
|
23
|
-
#
|
24
|
-
# The +MyTerm+ class should have a constructor with two arguments:
|
25
|
-
# the operator and the list of operands (Array).
|
26
|
-
#
|
27
16
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
17
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
29
18
|
# License:: MIT
|
@@ -33,19 +22,13 @@ class Factbase::Syntax
|
|
33
22
|
|
34
23
|
# Ctor.
|
35
24
|
#
|
36
|
-
# The class provided as the +term+ argument must have a
|
37
|
-
#
|
25
|
+
# The class provided as the +term+ argument must have a constructor that accepts
|
26
|
+
# an operator, operands array, and a keyword argument fb. Also, it must be
|
38
27
|
# a child of +Factbase::Term+.
|
39
28
|
#
|
40
|
-
# @param [Factbase] fb Factbase
|
41
29
|
# @param [String] query The query, for example "(eq id 42)"
|
42
|
-
|
43
|
-
def initialize(fb, query, term: Factbase::Term)
|
44
|
-
@fb = fb
|
30
|
+
def initialize(query)
|
45
31
|
@query = query
|
46
|
-
raise "Term must be a Class, while #{term.class.name} provided" unless term.is_a?(Class)
|
47
|
-
raise "The 'term' must be a child of Factbase::Term, while #{term.name} provided" unless term <= Factbase::Term
|
48
|
-
@term = term
|
49
32
|
end
|
50
33
|
|
51
34
|
# Convert it to a term.
|
@@ -74,7 +57,7 @@ class Factbase::Syntax
|
|
74
57
|
raise "Too many terms (#{@ast[1]} != #{@tokens.size})" if @ast[1] != @tokens.size
|
75
58
|
t = @ast[0]
|
76
59
|
raise 'No terms found in the AST' if t.nil?
|
77
|
-
raise "#{t.class.name} is not an instance of
|
60
|
+
raise "#{t.class.name} is not an instance of Term" unless t.is_a?(Factbase::Term)
|
78
61
|
t
|
79
62
|
end
|
80
63
|
|
@@ -82,9 +65,13 @@ class Factbase::Syntax
|
|
82
65
|
# token at the position is not a literal (like 42 or "Hello") but a term,
|
83
66
|
# the function recursively calls itself.
|
84
67
|
#
|
85
|
-
# The function returns
|
68
|
+
# The function returns a two-element array, where the first element
|
86
69
|
# is the term/literal and the second one is the position where the
|
87
70
|
# scanning should continue.
|
71
|
+
#
|
72
|
+
# @param [Array] tokens Array of tokens
|
73
|
+
# @param [Integer] at Position to start parsing from
|
74
|
+
# @return [Array<Factbase::Term,Integer>] The term detected and ending position
|
88
75
|
def to_ast(tokens, at)
|
89
76
|
raise "Closing too soon at ##{at}" if tokens[at] == :close
|
90
77
|
return [tokens[at], at + 1] unless tokens[at] == :open
|
@@ -103,12 +90,12 @@ class Factbase::Syntax
|
|
103
90
|
operands << operand
|
104
91
|
break if tokens[at] == :close
|
105
92
|
end
|
106
|
-
t =
|
107
|
-
t = Factbase::TermOnce.new(t, @fb.cache) if t.instance_of?(Factbase::Term)
|
93
|
+
t = Factbase::Term.new(op, operands)
|
108
94
|
[t, at + 1]
|
109
95
|
end
|
110
96
|
|
111
97
|
# Turns a query into an array of tokens.
|
98
|
+
# @return [Array] Array of tokens
|
112
99
|
def to_tokens
|
113
100
|
list = []
|
114
101
|
acc = ''
|
data/lib/factbase/tallied.rb
CHANGED
@@ -32,7 +32,7 @@ class Factbase::Tallied
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def query(query)
|
35
|
-
Query.new(@fb.query(query), @churn)
|
35
|
+
Query.new(@fb.query(query), @churn, @fb)
|
36
36
|
end
|
37
37
|
|
38
38
|
def txn
|
@@ -69,24 +69,25 @@ class Factbase::Tallied
|
|
69
69
|
#
|
70
70
|
# This is an internal class, it is not supposed to be instantiated directly.
|
71
71
|
class Query
|
72
|
-
def initialize(query, churn)
|
72
|
+
def initialize(query, churn, fb)
|
73
73
|
@query = query
|
74
74
|
@churn = churn
|
75
|
+
@fb = fb
|
75
76
|
end
|
76
77
|
|
77
|
-
def one(params = {})
|
78
|
-
@query.one(params)
|
78
|
+
def one(fb = @fb, params = {})
|
79
|
+
@query.one(fb, params)
|
79
80
|
end
|
80
81
|
|
81
|
-
def each(params = {}, &)
|
82
|
-
return to_enum(__method__, params) unless block_given?
|
83
|
-
@query.each(params) do |f|
|
82
|
+
def each(fb = @fb, params = {}, &)
|
83
|
+
return to_enum(__method__, fb, params) unless block_given?
|
84
|
+
@query.each(fb, params) do |f|
|
84
85
|
yield Fact.new(f, @churn)
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
|
-
def delete!
|
89
|
-
c = @query.delete!
|
89
|
+
def delete!(fb = @fb)
|
90
|
+
c = @query.delete!(fb)
|
90
91
|
@churn.append(0, c, 0)
|
91
92
|
c
|
92
93
|
end
|
data/lib/factbase/taped.rb
CHANGED
data/lib/factbase/tee.rb
CHANGED
@@ -16,7 +16,9 @@ class Factbase::Tee
|
|
16
16
|
# @param [Factbase::Fact] fact Primary fact to use for reading
|
17
17
|
# @param [Factbase::Fact] upper Fact to access with a "$" prefix
|
18
18
|
def initialize(fact, upper)
|
19
|
+
raise 'Fact is nil' if fact.nil?
|
19
20
|
@fact = fact
|
21
|
+
raise 'Upper is nil' if upper.nil?
|
20
22
|
@upper = upper
|
21
23
|
end
|
22
24
|
|
data/lib/factbase/term.rb
CHANGED
@@ -17,8 +17,8 @@ require_relative 'tee'
|
|
17
17
|
#
|
18
18
|
# require 'factbase/fact'
|
19
19
|
# require 'factbase/term'
|
20
|
-
# f = Factbase::Fact.new(
|
21
|
-
# t = Factbase::Term.new(
|
20
|
+
# f = Factbase::Fact.new({ 'foo' => [42, 256, 'Hello, world!'] })
|
21
|
+
# t = Factbase::Term.new(:lt, [:foo, 50])
|
22
22
|
# assert(t.evaluate(f))
|
23
23
|
#
|
24
24
|
# The design of this class may look ugly, since it has a large number of
|
@@ -30,11 +30,19 @@ require_relative 'tee'
|
|
30
30
|
# Moreover, it looks like the number of possible term types is rather limited
|
31
31
|
# and currently we implement most of them.
|
32
32
|
#
|
33
|
+
# It is NOT thread-safe!
|
34
|
+
#
|
33
35
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
34
36
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
35
37
|
# License:: MIT
|
36
38
|
class Factbase::Term
|
37
|
-
|
39
|
+
# The operator of this term
|
40
|
+
# @return [Symbol] The operator
|
41
|
+
attr_reader :op
|
42
|
+
|
43
|
+
# The operands of this term
|
44
|
+
# @return [Array] The operands
|
45
|
+
attr_reader :operands
|
38
46
|
|
39
47
|
require_relative 'terms/math'
|
40
48
|
include Factbase::Term::Math
|
@@ -70,21 +78,41 @@ class Factbase::Term
|
|
70
78
|
include Factbase::Term::Debug
|
71
79
|
|
72
80
|
# Ctor.
|
73
|
-
# @param [Factbase] fb Factbase
|
74
81
|
# @param [Symbol] operator Operator
|
75
82
|
# @param [Array] operands Operands
|
76
|
-
def initialize(
|
77
|
-
@fb = fb
|
83
|
+
def initialize(operator, operands)
|
78
84
|
@op = operator
|
79
85
|
@operands = operands
|
80
86
|
end
|
81
87
|
|
88
|
+
def redress!(type, **args)
|
89
|
+
extend type
|
90
|
+
args.each { |k, v| send(:instance_variable_set, :"@#{k}", v) }
|
91
|
+
@operands.map do |op|
|
92
|
+
if op.is_a?(Factbase::Term)
|
93
|
+
op.redress!(type, **args)
|
94
|
+
else
|
95
|
+
op
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Try to predict which facts from the provided list
|
101
|
+
# should be evaluated. If no prediction can be made,
|
102
|
+
# the same list is returned.
|
103
|
+
# @param [Array<Hash>] maps Records to iterate, maybe
|
104
|
+
# @return [Array<Hash>] Records to iterate
|
105
|
+
def predict(maps, _params)
|
106
|
+
maps
|
107
|
+
end
|
108
|
+
|
82
109
|
# Does it match the fact?
|
83
110
|
# @param [Factbase::Fact] fact The fact
|
84
111
|
# @param [Array<Factbase::Fact>] maps All maps available
|
85
|
-
# @
|
86
|
-
|
87
|
-
|
112
|
+
# @param [Factbase] fb Factbase to use for sub-queries
|
113
|
+
# @return [Boolean] TRUE if matches
|
114
|
+
def evaluate(fact, maps, fb)
|
115
|
+
send(@op, fact, maps, fb)
|
88
116
|
rescue NoMethodError => e
|
89
117
|
raise "Probably the term '#{@op}' is not defined at #{self}:\n#{Backtrace.new(e)}"
|
90
118
|
rescue StandardError => e
|
@@ -146,26 +174,26 @@ class Factbase::Term
|
|
146
174
|
"(#{items.join(' ')})"
|
147
175
|
end
|
148
176
|
|
149
|
-
|
150
|
-
|
151
|
-
def at(fact, maps)
|
177
|
+
def at(fact, maps, fb)
|
152
178
|
assert_args(2)
|
153
|
-
i =
|
179
|
+
i = _values(0, fact, maps, fb)
|
154
180
|
raise "Too many values (#{i.size}) at first position, one expected" unless i.size == 1
|
155
181
|
i = i[0]
|
156
182
|
return nil if i.nil?
|
157
|
-
v =
|
183
|
+
v = _values(1, fact, maps, fb)
|
158
184
|
return nil if v.nil?
|
159
185
|
v[i]
|
160
186
|
end
|
161
187
|
|
188
|
+
private
|
189
|
+
|
162
190
|
def assert_args(num)
|
163
191
|
c = @operands.size
|
164
192
|
raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
|
165
193
|
raise "Too few (#{c}) operands for '#{@op}' (#{num} expected)" if c < num
|
166
194
|
end
|
167
195
|
|
168
|
-
def
|
196
|
+
def _by_symbol(pos, fact)
|
169
197
|
o = @operands[pos]
|
170
198
|
raise "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided" unless o.is_a?(Symbol)
|
171
199
|
k = o.to_s
|
@@ -173,9 +201,9 @@ class Factbase::Term
|
|
173
201
|
end
|
174
202
|
|
175
203
|
# @return [Array|nil] Either array of values or NIL
|
176
|
-
def
|
204
|
+
def _values(pos, fact, maps, fb)
|
177
205
|
v = @operands[pos]
|
178
|
-
v = v.evaluate(fact, maps) if v.is_a?(Factbase::Term)
|
206
|
+
v = v.evaluate(fact, maps, fb) if v.is_a?(Factbase::Term)
|
179
207
|
v = fact[v.to_s] if v.is_a?(Symbol)
|
180
208
|
return v if v.nil?
|
181
209
|
unless v.is_a?(Array)
|
@@ -11,30 +11,32 @@ require_relative '../../factbase'
|
|
11
11
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
12
|
# License:: MIT
|
13
13
|
module Factbase::Term::Aggregates
|
14
|
-
def min(_fact, maps)
|
14
|
+
def min(_fact, maps, _fb)
|
15
15
|
assert_args(1)
|
16
|
-
|
16
|
+
_best(maps) { |v, b| v < b }
|
17
17
|
end
|
18
18
|
|
19
|
-
def max(_fact, maps)
|
19
|
+
def max(_fact, maps, _fb)
|
20
20
|
assert_args(1)
|
21
|
-
|
21
|
+
_best(maps) { |v, b| v > b }
|
22
22
|
end
|
23
23
|
|
24
|
-
def count(_fact, maps)
|
24
|
+
def count(_fact, maps, _fb)
|
25
25
|
maps.size
|
26
26
|
end
|
27
27
|
|
28
|
-
def nth(_fact, maps)
|
28
|
+
def nth(_fact, maps, _fb)
|
29
29
|
assert_args(2)
|
30
30
|
pos = @operands[0]
|
31
31
|
raise "An integer expected, but #{pos} provided" unless pos.is_a?(Integer)
|
32
32
|
k = @operands[1]
|
33
33
|
raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
|
34
|
-
maps[pos]
|
34
|
+
m = maps[pos]
|
35
|
+
return nil if m.nil?
|
36
|
+
m[k.to_s]
|
35
37
|
end
|
36
38
|
|
37
|
-
def first(_fact, maps)
|
39
|
+
def first(_fact, maps, _fb)
|
38
40
|
assert_args(1)
|
39
41
|
k = @operands[0]
|
40
42
|
raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
|
@@ -43,7 +45,7 @@ module Factbase::Term::Aggregates
|
|
43
45
|
first[k.to_s]
|
44
46
|
end
|
45
47
|
|
46
|
-
def sum(_fact, maps)
|
48
|
+
def sum(_fact, maps, _fb)
|
47
49
|
k = @operands[0]
|
48
50
|
raise "A symbol expected, but '#{k}' provided" unless k.is_a?(Symbol)
|
49
51
|
sum = 0
|
@@ -58,24 +60,24 @@ module Factbase::Term::Aggregates
|
|
58
60
|
sum
|
59
61
|
end
|
60
62
|
|
61
|
-
def agg(fact, maps)
|
63
|
+
def agg(fact, maps, fb)
|
62
64
|
assert_args(2)
|
63
65
|
selector = @operands[0]
|
64
66
|
raise "A term expected, but '#{selector}' provided" unless selector.is_a?(Factbase::Term)
|
65
67
|
term = @operands[1]
|
66
68
|
raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
|
67
|
-
subset =
|
68
|
-
term.evaluate(nil, subset)
|
69
|
+
subset = fb.query(selector, maps).each(fb, fact).to_a
|
70
|
+
term.evaluate(nil, subset, fb)
|
69
71
|
end
|
70
72
|
|
71
|
-
def empty(fact, maps)
|
73
|
+
def empty(fact, maps, fb)
|
72
74
|
assert_args(1)
|
73
75
|
term = @operands[0]
|
74
76
|
raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
|
75
|
-
|
77
|
+
fb.query(term, maps).each(fb, fact).to_a.empty?
|
76
78
|
end
|
77
79
|
|
78
|
-
def
|
80
|
+
def _best(maps)
|
79
81
|
k = @operands[0]
|
80
82
|
raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
|
81
83
|
best = nil
|
@@ -11,16 +11,16 @@ require_relative '../../factbase'
|
|
11
11
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
12
|
# License:: MIT
|
13
13
|
module Factbase::Term::Aliases
|
14
|
-
def as(fact, maps)
|
14
|
+
def as(fact, maps, fb)
|
15
15
|
assert_args(2)
|
16
16
|
a = @operands[0]
|
17
17
|
raise "A symbol expected as first argument of 'as'" unless a.is_a?(Symbol)
|
18
|
-
vv =
|
18
|
+
vv = _values(1, fact, maps, fb)
|
19
19
|
vv&.each { |v| fact.send(:"#{a}=", v) }
|
20
20
|
true
|
21
21
|
end
|
22
22
|
|
23
|
-
def join(fact, maps)
|
23
|
+
def join(fact, maps, fb)
|
24
24
|
assert_args(2)
|
25
25
|
jumps = @operands[0]
|
26
26
|
raise "A string expected as first argument of 'join'" unless jumps.is_a?(String)
|
@@ -30,7 +30,7 @@ module Factbase::Term::Aliases
|
|
30
30
|
.map { |j| j.size == 1 ? [j[0], j[0]] : j }
|
31
31
|
term = @operands[1]
|
32
32
|
raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
|
33
|
-
subset =
|
33
|
+
subset = fb.query(term, maps).each(fb, fact).to_a
|
34
34
|
subset.each do |s|
|
35
35
|
jumps.each do |to, from|
|
36
36
|
s[from]&.each do |v|
|
@@ -11,30 +11,30 @@ require_relative '../../factbase'
|
|
11
11
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
12
|
# License:: MIT
|
13
13
|
module Factbase::Term::Casting
|
14
|
-
def to_string(fact, maps)
|
14
|
+
def to_string(fact, maps, fb)
|
15
15
|
assert_args(1)
|
16
|
-
vv =
|
16
|
+
vv = _values(0, fact, maps, fb)
|
17
17
|
return nil if vv.nil?
|
18
18
|
vv[0].to_s
|
19
19
|
end
|
20
20
|
|
21
|
-
def to_integer(fact, maps)
|
21
|
+
def to_integer(fact, maps, fb)
|
22
22
|
assert_args(1)
|
23
|
-
vv =
|
23
|
+
vv = _values(0, fact, maps, fb)
|
24
24
|
return nil if vv.nil?
|
25
25
|
vv[0].to_i
|
26
26
|
end
|
27
27
|
|
28
|
-
def to_float(fact, maps)
|
28
|
+
def to_float(fact, maps, fb)
|
29
29
|
assert_args(1)
|
30
|
-
vv =
|
30
|
+
vv = _values(0, fact, maps, fb)
|
31
31
|
return nil if vv.nil?
|
32
32
|
vv[0].to_f
|
33
33
|
end
|
34
34
|
|
35
|
-
def to_time(fact, maps)
|
35
|
+
def to_time(fact, maps, fb)
|
36
36
|
assert_args(1)
|
37
|
-
vv =
|
37
|
+
vv = _values(0, fact, maps, fb)
|
38
38
|
return nil if vv.nil?
|
39
39
|
Time.parse(vv[0].to_s)
|
40
40
|
end
|
data/lib/factbase/terms/debug.rb
CHANGED
@@ -11,11 +11,11 @@ require_relative '../../factbase'
|
|
11
11
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
12
|
# License:: MIT
|
13
13
|
module Factbase::Term::Debug
|
14
|
-
def traced(fact, maps)
|
14
|
+
def traced(fact, maps, fb)
|
15
15
|
assert_args(1)
|
16
16
|
t = @operands[0]
|
17
17
|
raise "A term expected, but '#{t}' provided" unless t.is_a?(Factbase::Term)
|
18
|
-
r = t.evaluate(fact, maps)
|
18
|
+
r = t.evaluate(fact, maps, fb)
|
19
19
|
puts "#{self} -> #{r}"
|
20
20
|
r
|
21
21
|
end
|
data/lib/factbase/terms/defn.rb
CHANGED
@@ -11,21 +11,21 @@ require_relative '../../factbase'
|
|
11
11
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
12
|
# License:: MIT
|
13
13
|
module Factbase::Term::Defn
|
14
|
-
def defn(_fact, _maps)
|
14
|
+
def defn(_fact, _maps, _fb)
|
15
15
|
assert_args(2)
|
16
16
|
fn = @operands[0]
|
17
17
|
raise "A symbol expected as first argument of 'defn'" unless fn.is_a?(Symbol)
|
18
18
|
raise "Can't use '#{fn}' name as a term" if Factbase::Term.instance_methods(true).include?(fn)
|
19
19
|
raise "Term '#{fn}' is already defined" if Factbase::Term.private_instance_methods(false).include?(fn)
|
20
20
|
raise "The '#{fn}' is a bad name for a term" unless fn.match?(/^[a-z_]+$/)
|
21
|
-
e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps)\n#{@operands[1]}\nend\nend"
|
21
|
+
e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps, fb)\n#{@operands[1]}\nend\nend"
|
22
22
|
# rubocop:disable Security/Eval
|
23
23
|
eval(e)
|
24
24
|
# rubocop:enable Security/Eval
|
25
25
|
true
|
26
26
|
end
|
27
27
|
|
28
|
-
def undef(_fact, _maps)
|
28
|
+
def undef(_fact, _maps, _fb)
|
29
29
|
assert_args(1)
|
30
30
|
fn = @operands[0]
|
31
31
|
raise "A symbol expected as first argument of 'undef'" unless fn.is_a?(Symbol)
|