factbase 0.19.10 → 0.19.12
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 +5 -4
- data/Gemfile.lock +18 -14
- data/README.md +127 -20
- data/Rakefile +2 -7
- data/factbase.gemspec +11 -11
- data/lib/factbase/accum.rb +1 -1
- data/lib/factbase/cached/cached_fact.rb +1 -2
- data/lib/factbase/cached/cached_factbase.rb +3 -3
- data/lib/factbase/cached/cached_query.rb +4 -6
- data/lib/factbase/cached/cached_term.rb +1 -2
- data/lib/factbase/churn.rb +4 -8
- data/lib/factbase/fact.rb +12 -9
- data/lib/factbase/flatten.rb +2 -2
- data/lib/factbase/impatient.rb +14 -13
- data/lib/factbase/indexed/indexed_and.rb +14 -20
- data/lib/factbase/indexed/indexed_eq.rb +5 -1
- data/lib/factbase/indexed/indexed_fact.rb +1 -4
- data/lib/factbase/indexed/indexed_factbase.rb +4 -4
- data/lib/factbase/indexed/indexed_gt.rb +3 -1
- data/lib/factbase/indexed/indexed_gte.rb +51 -0
- data/lib/factbase/indexed/indexed_lt.rb +3 -1
- data/lib/factbase/indexed/indexed_lte.rb +51 -0
- data/lib/factbase/indexed/indexed_not.rb +1 -1
- data/lib/factbase/indexed/indexed_or.rb +2 -2
- data/lib/factbase/indexed/indexed_query.rb +6 -7
- data/lib/factbase/indexed/indexed_term.rb +10 -6
- data/lib/factbase/indexed/indexed_unique.rb +4 -2
- data/lib/factbase/inv.rb +3 -3
- data/lib/factbase/lazy_taped.rb +10 -13
- data/lib/factbase/lazy_taped_hash.rb +2 -1
- data/lib/factbase/light.rb +1 -1
- data/lib/factbase/logged.rb +37 -34
- data/lib/factbase/pre.rb +3 -3
- data/lib/factbase/query.rb +4 -5
- data/lib/factbase/rules.rb +8 -8
- data/lib/factbase/sync/sync_factbase.rb +2 -2
- data/lib/factbase/syntax.rb +19 -20
- data/lib/factbase/tallied.rb +7 -8
- data/lib/factbase/taped.rb +5 -11
- data/lib/factbase/tee.rb +2 -2
- data/lib/factbase/term.rb +58 -59
- data/lib/factbase/terms/agg.rb +3 -4
- data/lib/factbase/terms/arithmetic.rb +7 -7
- data/lib/factbase/terms/as.rb +2 -2
- data/lib/factbase/terms/assert.rb +5 -13
- data/lib/factbase/terms/base.rb +7 -10
- data/lib/factbase/terms/best.rb +1 -1
- data/lib/factbase/terms/boolean.rb +1 -1
- data/lib/factbase/terms/compare.rb +17 -1
- data/lib/factbase/terms/contains.rb +28 -0
- data/lib/factbase/terms/defn.rb +8 -6
- data/lib/factbase/terms/empty.rb +1 -1
- data/lib/factbase/terms/ends_with.rb +27 -0
- data/lib/factbase/terms/first.rb +2 -2
- data/lib/factbase/terms/head.rb +3 -3
- data/lib/factbase/terms/inverted.rb +2 -2
- data/lib/factbase/terms/join.rb +8 -7
- data/lib/factbase/terms/matches.rb +14 -4
- data/lib/factbase/terms/max.rb +1 -1
- data/lib/factbase/terms/min.rb +1 -1
- data/lib/factbase/terms/nth.rb +3 -3
- data/lib/factbase/terms/plus.rb +1 -1
- data/lib/factbase/terms/prev.rb +3 -6
- data/lib/factbase/terms/sorted.rb +2 -2
- data/lib/factbase/terms/sprintf.rb +11 -2
- data/lib/factbase/terms/starts_with.rb +27 -0
- data/lib/factbase/terms/sum.rb +2 -2
- data/lib/factbase/terms/to_float.rb +2 -2
- data/lib/factbase/terms/to_integer.rb +2 -2
- data/lib/factbase/terms/to_string.rb +1 -1
- data/lib/factbase/terms/to_time.rb +10 -2
- data/lib/factbase/terms/traced.rb +2 -2
- data/lib/factbase/terms/undef.rb +2 -2
- data/lib/factbase/terms/unique.rb +3 -7
- data/lib/factbase/terms/when.rb +2 -3
- data/lib/factbase/to_json.rb +2 -2
- data/lib/factbase/to_xml.rb +6 -10
- data/lib/factbase/to_yaml.rb +1 -1
- data/lib/factbase/version.rb +1 -2
- data/lib/factbase.rb +27 -13
- data/lib/fuzz.rb +3 -3
- metadata +6 -1
data/lib/factbase/pre.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require 'loog'
|
|
7
6
|
require 'decoor'
|
|
7
|
+
require 'loog'
|
|
8
8
|
require_relative '../factbase'
|
|
9
9
|
|
|
10
10
|
# A decorator of a +Factbase+, that runs a provided block on every +insert+.
|
|
@@ -26,7 +26,7 @@ class Factbase::Pre
|
|
|
26
26
|
decoor(:fb)
|
|
27
27
|
|
|
28
28
|
def initialize(fb, &block)
|
|
29
|
-
raise 'The "fb" is nil' if fb.nil?
|
|
29
|
+
raise(ArgumentError, 'The "fb" is nil') if fb.nil?
|
|
30
30
|
@fb = fb
|
|
31
31
|
@block = block
|
|
32
32
|
end
|
|
@@ -39,7 +39,7 @@ class Factbase::Pre
|
|
|
39
39
|
|
|
40
40
|
def txn
|
|
41
41
|
@fb.txn do |fbt|
|
|
42
|
-
yield
|
|
42
|
+
yield(Factbase::Pre.new(fbt, &@block))
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
data/lib/factbase/query.rb
CHANGED
|
@@ -52,13 +52,12 @@ class Factbase::Query
|
|
|
52
52
|
extras = {}
|
|
53
53
|
f = Factbase::Fact.new(m)
|
|
54
54
|
f = Factbase::Tee.new(f, params)
|
|
55
|
-
|
|
56
|
-
r = @term.evaluate(a, @maps, fb)
|
|
55
|
+
r = @term.evaluate(Factbase::Accum.new(f, extras, false), @maps, fb)
|
|
57
56
|
unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
|
58
|
-
raise "Unexpected evaluation result of type #{r.class}, must be Boolean at #{@term.inspect}"
|
|
57
|
+
raise(ArgumentError, "Unexpected evaluation result of type #{r.class}, must be Boolean at #{@term.inspect}")
|
|
59
58
|
end
|
|
60
59
|
next unless r
|
|
61
|
-
yield
|
|
60
|
+
yield(Factbase::Accum.new(f, extras, true))
|
|
62
61
|
yielded += 1
|
|
63
62
|
end
|
|
64
63
|
yielded
|
|
@@ -72,7 +71,7 @@ class Factbase::Query
|
|
|
72
71
|
params = params.transform_keys(&:to_s) if params.is_a?(Hash)
|
|
73
72
|
r = @term.evaluate(Factbase::Tee.new(Factbase::Fact.new({}), params), @maps, fb)
|
|
74
73
|
unless %w[String Integer Float Time Array NilClass].include?(r.class.to_s)
|
|
75
|
-
raise "Incorrect type #{r.class} returned by #{@term.inspect}"
|
|
74
|
+
raise(StandardError, "Incorrect type #{r.class} returned by #{@term.inspect}")
|
|
76
75
|
end
|
|
77
76
|
r
|
|
78
77
|
end
|
data/lib/factbase/rules.rb
CHANGED
|
@@ -28,11 +28,11 @@ class Factbase::Rules
|
|
|
28
28
|
decoor(:fb)
|
|
29
29
|
|
|
30
30
|
def initialize(fb, rules, check = Check.new(rules), uid: nil)
|
|
31
|
-
raise 'The "fb" is nil' if fb.nil?
|
|
31
|
+
raise(ArgumentError, 'The "fb" is nil') if fb.nil?
|
|
32
32
|
@fb = fb
|
|
33
|
-
raise 'The "rules" is nil' if rules.nil?
|
|
33
|
+
raise(ArgumentError, 'The "rules" is nil') if rules.nil?
|
|
34
34
|
@rules = rules
|
|
35
|
-
raise 'The "check" is nil' if check.nil?
|
|
35
|
+
raise(ArgumentError, 'The "check" is nil') if check.nil?
|
|
36
36
|
@check = check
|
|
37
37
|
@uid = uid
|
|
38
38
|
end
|
|
@@ -51,7 +51,7 @@ class Factbase::Rules
|
|
|
51
51
|
@check = later
|
|
52
52
|
@fb.txn do |fbt|
|
|
53
53
|
churn = Factbase::Churn.new
|
|
54
|
-
yield
|
|
54
|
+
yield(Factbase::Tallied.new(Factbase::Rules.new(fbt, @rules, @check, uid: @uid), churn))
|
|
55
55
|
@check = before
|
|
56
56
|
unless churn.zero?
|
|
57
57
|
fbt.query('(always)').each do |f|
|
|
@@ -107,7 +107,7 @@ class Factbase::Rules
|
|
|
107
107
|
def each(fb = @fb, params = {})
|
|
108
108
|
return to_enum(__method__, fb, params) unless block_given?
|
|
109
109
|
@query.each(fb, params) do |f|
|
|
110
|
-
yield
|
|
110
|
+
yield(Fact.new(f, @check, fb))
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
end
|
|
@@ -123,7 +123,7 @@ class Factbase::Rules
|
|
|
123
123
|
def it(fact, fb)
|
|
124
124
|
return if Factbase::Syntax.new(@expr).to_term.evaluate(fact, [], fb)
|
|
125
125
|
e = "#{@expr[0..32]}..." if @expr.length > 32
|
|
126
|
-
raise "The fact doesn't match the #{e.inspect} rule: #{fact}"
|
|
126
|
+
raise(ArgumentError, "The fact doesn't match the #{e.inspect} rule: #{fact}")
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
@@ -140,7 +140,7 @@ class Factbase::Rules
|
|
|
140
140
|
return if @uid.nil?
|
|
141
141
|
a = fact[@uid]
|
|
142
142
|
return if a.nil?
|
|
143
|
-
raise "More than one #{@uid.inspect} in the fact: #{a}" if a.size > 1
|
|
143
|
+
raise(ArgumentError, "More than one #{@uid.inspect} in the fact: #{a}") if a.size > 1
|
|
144
144
|
@facts << a.first
|
|
145
145
|
end
|
|
146
146
|
|
|
@@ -148,7 +148,7 @@ class Factbase::Rules
|
|
|
148
148
|
return true if @uid.nil?
|
|
149
149
|
a = fact[@uid]
|
|
150
150
|
return true if a.nil?
|
|
151
|
-
raise "More than one #{@uid.inspect} in the fact: #{a}" if a.size > 1
|
|
151
|
+
raise(ArgumentError, "More than one #{@uid.inspect} in the fact: #{a}") if a.size > 1
|
|
152
152
|
@facts.include?(a.first)
|
|
153
153
|
end
|
|
154
154
|
end
|
|
@@ -43,7 +43,7 @@ class Factbase::SyncFactbase
|
|
|
43
43
|
# @param [Array<Hash>] maps Possible maps to use
|
|
44
44
|
def query(term, maps = nil)
|
|
45
45
|
term = to_term(term) if term.is_a?(String)
|
|
46
|
-
require_relative
|
|
46
|
+
require_relative('sync_query')
|
|
47
47
|
Factbase::SyncQuery.new(@origin.query(term, maps), @monitor, self)
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -53,7 +53,7 @@ class Factbase::SyncFactbase
|
|
|
53
53
|
def txn
|
|
54
54
|
try_lock do
|
|
55
55
|
@origin.txn do |fbt|
|
|
56
|
-
yield
|
|
56
|
+
yield(Factbase::SyncFactbase.new(fbt, @monitor))
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
data/lib/factbase/syntax.rb
CHANGED
|
@@ -43,7 +43,7 @@ class Factbase::Syntax
|
|
|
43
43
|
rescue StandardError => e
|
|
44
44
|
err = "#{e.message} (#{Backtrace.new(e)}) in \"#{@query}\""
|
|
45
45
|
err = "#{err}, tokens: #{@tokens}" unless @tokens.nil?
|
|
46
|
-
raise
|
|
46
|
+
raise(Broken, err)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
private
|
|
@@ -52,12 +52,12 @@ class Factbase::Syntax
|
|
|
52
52
|
# @return [Term] The term detected
|
|
53
53
|
def build
|
|
54
54
|
@tokens ||= to_tokens
|
|
55
|
-
raise 'No tokens' if @tokens.empty?
|
|
55
|
+
raise(StandardError, 'No tokens') if @tokens.empty?
|
|
56
56
|
@ast ||= to_ast(@tokens, 0)
|
|
57
|
-
raise "Too many terms (#{@ast[1]} != #{@tokens.size})" if @ast[1] != @tokens.size
|
|
57
|
+
raise(ArgumentError, "Too many terms (#{@ast[1]} != #{@tokens.size})") if @ast[1] != @tokens.size
|
|
58
58
|
t = @ast[0]
|
|
59
|
-
raise 'No terms found in the AST' if t.nil?
|
|
60
|
-
raise "#{t.class.name} is not an instance of Term" unless t.is_a?(Factbase::Term)
|
|
59
|
+
raise(StandardError, 'No terms found in the AST') if t.nil?
|
|
60
|
+
raise(ArgumentError, "#{t.class.name} is not an instance of Term") unless t.is_a?(Factbase::Term)
|
|
61
61
|
t
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -73,25 +73,24 @@ class Factbase::Syntax
|
|
|
73
73
|
# @param [Integer] at Position to start parsing from
|
|
74
74
|
# @return [Array<Factbase::Term,Integer>] The term detected and ending position
|
|
75
75
|
def to_ast(tokens, at)
|
|
76
|
-
raise "Closing too soon at ##{at}" if tokens[at] == :close
|
|
76
|
+
raise(StandardError, "Closing too soon at ##{at}") if tokens[at] == :close
|
|
77
77
|
return [tokens[at], at + 1] unless tokens[at] == :open
|
|
78
78
|
at += 1
|
|
79
79
|
op = tokens[at]
|
|
80
|
-
raise 'No token found' if op == :close
|
|
80
|
+
raise(StandardError, 'No token found') if op == :close
|
|
81
81
|
operands = []
|
|
82
82
|
at += 1
|
|
83
83
|
loop do
|
|
84
|
-
raise "End of token stream at ##{at}" if tokens[at].nil?
|
|
84
|
+
raise(StandardError, "End of token stream at ##{at}") if tokens[at].nil?
|
|
85
85
|
break if tokens[at] == :close
|
|
86
86
|
(operand, at1) = to_ast(tokens, at)
|
|
87
|
-
raise "Stuck at position ##{at}" if at == at1
|
|
88
|
-
raise "Jump back at position ##{at}" if at1 < at
|
|
87
|
+
raise(StandardError, "Stuck at position ##{at}") if at == at1
|
|
88
|
+
raise(StandardError, "Jump back at position ##{at}") if at1 < at
|
|
89
89
|
at = at1
|
|
90
90
|
operands << operand
|
|
91
91
|
break if tokens[at] == :close
|
|
92
92
|
end
|
|
93
|
-
|
|
94
|
-
[t, at + 1]
|
|
93
|
+
[Factbase::Term.new(op, operands), at + 1]
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
# Turns a query into an array of tokens.
|
|
@@ -100,7 +99,7 @@ class Factbase::Syntax
|
|
|
100
99
|
list = []
|
|
101
100
|
acc = ''
|
|
102
101
|
quotes = ['\'', '"']
|
|
103
|
-
spaces = [' ', ')']
|
|
102
|
+
spaces = [' ', ')', "\n", "\t", "\r"]
|
|
104
103
|
string = false
|
|
105
104
|
comment = false
|
|
106
105
|
@query.to_s.chars.each do |c|
|
|
@@ -128,26 +127,26 @@ class Factbase::Syntax
|
|
|
128
127
|
when ')'
|
|
129
128
|
list << :close
|
|
130
129
|
when ' ', "\n", "\t", "\r"
|
|
131
|
-
|
|
130
|
+
next
|
|
132
131
|
else
|
|
133
132
|
acc += c
|
|
134
133
|
end
|
|
135
134
|
end
|
|
136
|
-
raise 'String not closed' if string
|
|
135
|
+
raise(StandardError, 'String not closed') if string
|
|
137
136
|
list.map do |t|
|
|
138
137
|
if t.is_a?(Symbol)
|
|
139
138
|
t
|
|
140
139
|
elsif t.start_with?('\'', '"')
|
|
141
|
-
raise 'String literal can\'t be empty' if t.length <= 2
|
|
140
|
+
raise(ArgumentError, 'String literal can\'t be empty') if t.length <= 2
|
|
142
141
|
t[1..-2]
|
|
143
142
|
elsif t.match?(/^(\+|-)?[0-9]+$/)
|
|
144
|
-
t
|
|
145
|
-
elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e
|
|
146
|
-
t
|
|
143
|
+
Integer(t, 10)
|
|
144
|
+
elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e(\+|-)[0-9]+)?$/)
|
|
145
|
+
Float(t)
|
|
147
146
|
elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
|
|
148
147
|
Time.parse(t)
|
|
149
148
|
else
|
|
150
|
-
raise "Wrong symbol format (#{t})" unless t.match?(/^([_a-z][a-zA-Z0-9_]*|\$[_a-z]+)$/)
|
|
149
|
+
raise(ArgumentError, "Wrong symbol format (#{t})") unless t.match?(/^([_a-z][a-zA-Z0-9_]*|\$[_a-z]+)$/)
|
|
151
150
|
t.to_sym
|
|
152
151
|
end
|
|
153
152
|
end
|
data/lib/factbase/tallied.rb
CHANGED
|
@@ -8,7 +8,7 @@ require 'others'
|
|
|
8
8
|
require_relative '../factbase'
|
|
9
9
|
require_relative 'churn'
|
|
10
10
|
|
|
11
|
-
# A decorator of a Factbase, that
|
|
11
|
+
# A decorator of a Factbase, that counts all operations and then returns
|
|
12
12
|
# an instance of Factbase::Churn.
|
|
13
13
|
#
|
|
14
14
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
@@ -18,7 +18,7 @@ class Factbase::Tallied
|
|
|
18
18
|
attr_reader :churn
|
|
19
19
|
|
|
20
20
|
def initialize(fb, churn = Factbase::Churn.new)
|
|
21
|
-
raise 'The "fb" is nil' if fb.nil?
|
|
21
|
+
raise(ArgumentError, 'The "fb" is nil') if fb.nil?
|
|
22
22
|
@fb = fb
|
|
23
23
|
@churn = churn
|
|
24
24
|
end
|
|
@@ -26,9 +26,8 @@ class Factbase::Tallied
|
|
|
26
26
|
decoor(:fb)
|
|
27
27
|
|
|
28
28
|
def insert
|
|
29
|
-
f = Fact.new(@fb.insert, @churn)
|
|
30
29
|
@churn.append(1, 0, 0)
|
|
31
|
-
|
|
30
|
+
Fact.new(@fb.insert, @churn)
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
def query(query, maps = nil)
|
|
@@ -39,13 +38,13 @@ class Factbase::Tallied
|
|
|
39
38
|
before = @churn.dup
|
|
40
39
|
commit = false
|
|
41
40
|
@fb.txn do |fbt|
|
|
42
|
-
catch
|
|
43
|
-
yield
|
|
41
|
+
catch(:rollback) do
|
|
42
|
+
yield(Factbase::Tallied.new(fbt, @churn))
|
|
44
43
|
commit = true
|
|
45
44
|
end
|
|
46
45
|
rescue Factbase::Rollback => e
|
|
47
46
|
@churn = before
|
|
48
|
-
raise
|
|
47
|
+
raise(e)
|
|
49
48
|
ensure
|
|
50
49
|
@churn = before unless commit
|
|
51
50
|
end
|
|
@@ -96,7 +95,7 @@ class Factbase::Tallied
|
|
|
96
95
|
def each(fb = @fb, params = {}, &)
|
|
97
96
|
return to_enum(__method__, fb, params) unless block_given?
|
|
98
97
|
@query.each(fb, params) do |f|
|
|
99
|
-
yield
|
|
98
|
+
yield(Fact.new(f, @churn))
|
|
100
99
|
end
|
|
101
100
|
end
|
|
102
101
|
|
data/lib/factbase/taped.rb
CHANGED
|
@@ -52,13 +52,13 @@ class Factbase::Taped
|
|
|
52
52
|
def each
|
|
53
53
|
return to_enum(__method__) unless block_given?
|
|
54
54
|
@origin.each do |m|
|
|
55
|
-
yield
|
|
55
|
+
yield(TapedHash.new(m, @added))
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def delete_if
|
|
60
60
|
@origin.delete_if do |m|
|
|
61
|
-
r = yield
|
|
61
|
+
r = yield(m)
|
|
62
62
|
@deleted.append(m.object_id) if r
|
|
63
63
|
r
|
|
64
64
|
end
|
|
@@ -153,14 +153,8 @@ class Factbase::Taped
|
|
|
153
153
|
private
|
|
154
154
|
|
|
155
155
|
def join(other)
|
|
156
|
-
|
|
157
|
-
raise '
|
|
158
|
-
|
|
159
|
-
Factbase::Taped.new(
|
|
160
|
-
n,
|
|
161
|
-
inserted: @inserted,
|
|
162
|
-
deleted: @deleted,
|
|
163
|
-
added: @added
|
|
164
|
-
)
|
|
156
|
+
raise(ArgumentError, 'Cannot join with another Taped') if other.respond_to?(:inserted)
|
|
157
|
+
raise(ArgumentError, 'Can only join with array') unless other.is_a?(Array)
|
|
158
|
+
Factbase::Taped.new(yield(@origin.to_a, other.to_a), inserted: @inserted, deleted: @deleted, added: @added)
|
|
165
159
|
end
|
|
166
160
|
end
|
data/lib/factbase/tee.rb
CHANGED
|
@@ -16,9 +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
|
+
raise(ArgumentError, 'Fact is nil') if fact.nil?
|
|
20
20
|
@fact = fact
|
|
21
|
-
raise 'Upper is nil' if upper.nil?
|
|
21
|
+
raise(ArgumentError, 'Upper is nil') if upper.nil?
|
|
22
22
|
@upper = upper
|
|
23
23
|
end
|
|
24
24
|
|
data/lib/factbase/term.rb
CHANGED
|
@@ -7,57 +7,60 @@ require 'backtrace'
|
|
|
7
7
|
require_relative '../factbase'
|
|
8
8
|
require_relative 'fact'
|
|
9
9
|
require_relative 'tee'
|
|
10
|
-
require_relative 'terms/
|
|
11
|
-
require_relative 'terms/
|
|
12
|
-
require_relative 'terms/
|
|
13
|
-
require_relative 'terms/
|
|
14
|
-
require_relative 'terms/
|
|
15
|
-
require_relative 'terms/traced'
|
|
10
|
+
require_relative 'terms/absent'
|
|
11
|
+
require_relative 'terms/agg'
|
|
12
|
+
require_relative 'terms/always'
|
|
13
|
+
require_relative 'terms/and'
|
|
14
|
+
require_relative 'terms/as'
|
|
16
15
|
require_relative 'terms/assert'
|
|
17
|
-
require_relative 'terms/
|
|
16
|
+
require_relative 'terms/concat'
|
|
17
|
+
require_relative 'terms/contains'
|
|
18
|
+
require_relative 'terms/count'
|
|
18
19
|
require_relative 'terms/defn'
|
|
19
|
-
require_relative 'terms/undef'
|
|
20
|
-
require_relative 'terms/as'
|
|
21
|
-
require_relative 'terms/join'
|
|
22
|
-
require_relative 'terms/exists'
|
|
23
|
-
require_relative 'terms/absent'
|
|
24
|
-
require_relative 'terms/size'
|
|
25
|
-
require_relative 'terms/type'
|
|
26
|
-
require_relative 'terms/nil'
|
|
27
|
-
require_relative 'terms/many'
|
|
28
|
-
require_relative 'terms/one'
|
|
29
|
-
require_relative 'terms/to_string'
|
|
30
|
-
require_relative 'terms/to_integer'
|
|
31
|
-
require_relative 'terms/to_float'
|
|
32
|
-
require_relative 'terms/to_time'
|
|
33
|
-
require_relative 'terms/sorted'
|
|
34
|
-
require_relative 'terms/inverted'
|
|
35
|
-
require_relative 'terms/head'
|
|
36
|
-
require_relative 'terms/plus'
|
|
37
|
-
require_relative 'terms/minus'
|
|
38
|
-
require_relative 'terms/times'
|
|
39
20
|
require_relative 'terms/div'
|
|
40
|
-
require_relative 'terms/
|
|
21
|
+
require_relative 'terms/either'
|
|
22
|
+
require_relative 'terms/empty'
|
|
23
|
+
require_relative 'terms/ends_with'
|
|
24
|
+
require_relative 'terms/env'
|
|
41
25
|
require_relative 'terms/eq'
|
|
42
|
-
require_relative 'terms/
|
|
43
|
-
require_relative 'terms/
|
|
26
|
+
require_relative 'terms/exists'
|
|
27
|
+
require_relative 'terms/first'
|
|
44
28
|
require_relative 'terms/gt'
|
|
45
29
|
require_relative 'terms/gte'
|
|
46
|
-
require_relative 'terms/
|
|
30
|
+
require_relative 'terms/head'
|
|
31
|
+
require_relative 'terms/inverted'
|
|
32
|
+
require_relative 'terms/join'
|
|
33
|
+
require_relative 'terms/lt'
|
|
34
|
+
require_relative 'terms/lte'
|
|
35
|
+
require_relative 'terms/many'
|
|
36
|
+
require_relative 'terms/matches'
|
|
37
|
+
require_relative 'terms/max'
|
|
38
|
+
require_relative 'terms/min'
|
|
39
|
+
require_relative 'terms/minus'
|
|
47
40
|
require_relative 'terms/never'
|
|
41
|
+
require_relative 'terms/nil'
|
|
48
42
|
require_relative 'terms/not'
|
|
49
|
-
require_relative 'terms/or'
|
|
50
|
-
require_relative 'terms/and'
|
|
51
|
-
require_relative 'terms/when'
|
|
52
|
-
require_relative 'terms/either'
|
|
53
|
-
require_relative 'terms/count'
|
|
54
|
-
require_relative 'terms/first'
|
|
55
43
|
require_relative 'terms/nth'
|
|
44
|
+
require_relative 'terms/one'
|
|
45
|
+
require_relative 'terms/or'
|
|
46
|
+
require_relative 'terms/plus'
|
|
47
|
+
require_relative 'terms/prev'
|
|
48
|
+
require_relative 'terms/size'
|
|
49
|
+
require_relative 'terms/sorted'
|
|
50
|
+
require_relative 'terms/sprintf'
|
|
51
|
+
require_relative 'terms/starts_with'
|
|
56
52
|
require_relative 'terms/sum'
|
|
57
|
-
require_relative 'terms/
|
|
58
|
-
require_relative 'terms/
|
|
59
|
-
require_relative 'terms/
|
|
60
|
-
require_relative 'terms/
|
|
53
|
+
require_relative 'terms/times'
|
|
54
|
+
require_relative 'terms/to_float'
|
|
55
|
+
require_relative 'terms/to_integer'
|
|
56
|
+
require_relative 'terms/to_string'
|
|
57
|
+
require_relative 'terms/to_time'
|
|
58
|
+
require_relative 'terms/traced'
|
|
59
|
+
require_relative 'terms/type'
|
|
60
|
+
require_relative 'terms/undef'
|
|
61
|
+
require_relative 'terms/unique'
|
|
62
|
+
require_relative 'terms/when'
|
|
63
|
+
require_relative 'terms/zero'
|
|
61
64
|
|
|
62
65
|
# Term.
|
|
63
66
|
#
|
|
@@ -87,13 +90,7 @@ require_relative 'terms/max'
|
|
|
87
90
|
# Copyright:: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
88
91
|
# License:: MIT
|
|
89
92
|
class Factbase::Term < Factbase::TermBase
|
|
90
|
-
|
|
91
|
-
# @return [Symbol] The operator
|
|
92
|
-
attr_reader :op
|
|
93
|
-
|
|
94
|
-
# The operands of this term
|
|
95
|
-
# @return [Array] The operands
|
|
96
|
-
attr_reader :operands
|
|
93
|
+
attr_reader :op, :operands
|
|
97
94
|
|
|
98
95
|
# Ctor.
|
|
99
96
|
# @param [Symbol] operator Operator
|
|
@@ -108,6 +105,9 @@ class Factbase::Term < Factbase::TermBase
|
|
|
108
105
|
concat: Factbase::Concat.new(operands),
|
|
109
106
|
sprintf: Factbase::Sprintf.new(operands),
|
|
110
107
|
matches: Factbase::Matches.new(operands),
|
|
108
|
+
contains: Factbase::Contains.new(operands),
|
|
109
|
+
starts_with: Factbase::StartsWith.new(operands),
|
|
110
|
+
ends_with: Factbase::EndsWith.new(operands),
|
|
111
111
|
traced: Factbase::Traced.new(operands),
|
|
112
112
|
assert: Factbase::Assert.new(operands),
|
|
113
113
|
env: Factbase::Env.new(operands),
|
|
@@ -161,9 +161,8 @@ class Factbase::Term < Factbase::TermBase
|
|
|
161
161
|
# @param [Module] type The type to extend with
|
|
162
162
|
# @param [Hash] args Attributes to set
|
|
163
163
|
def redress!(type, **args)
|
|
164
|
-
extend
|
|
165
|
-
|
|
166
|
-
args.each { |k, v| send(:instance_variable_set, :"@#{k}", v) }
|
|
164
|
+
extend(type)
|
|
165
|
+
args.each { |k, v| __send__(:instance_variable_set, :"@#{k}", v) }
|
|
167
166
|
@operands.map do |op|
|
|
168
167
|
if op.is_a?(Factbase::Term)
|
|
169
168
|
op.redress!(type, **args)
|
|
@@ -189,7 +188,7 @@ class Factbase::Term < Factbase::TermBase
|
|
|
189
188
|
maps
|
|
190
189
|
end
|
|
191
190
|
elsif respond_to?(m)
|
|
192
|
-
|
|
191
|
+
__send__(m, maps, fb, params)
|
|
193
192
|
else
|
|
194
193
|
maps
|
|
195
194
|
end
|
|
@@ -204,12 +203,12 @@ class Factbase::Term < Factbase::TermBase
|
|
|
204
203
|
if @terms.key?(@op)
|
|
205
204
|
@terms[@op].evaluate(fact, maps, fb)
|
|
206
205
|
else
|
|
207
|
-
|
|
206
|
+
__send__(@op, fact, maps, fb)
|
|
208
207
|
end
|
|
209
208
|
rescue NoMethodError => e
|
|
210
|
-
raise "Probably the term '#{@op}' is not defined at #{self}: #{e.message}"
|
|
209
|
+
raise(RuntimeError, "Probably the term '#{@op}' is not defined at #{self}: #{e.message}")
|
|
211
210
|
rescue StandardError => e
|
|
212
|
-
raise "#{e.message.inspect} at #{self} at #{e.backtrace[0]}"
|
|
211
|
+
raise(RuntimeError, "#{e.message.inspect} at #{self} at #{e.backtrace[0]}")
|
|
213
212
|
end
|
|
214
213
|
|
|
215
214
|
# Simplify it if possible.
|
|
@@ -220,7 +219,7 @@ class Factbase::Term < Factbase::TermBase
|
|
|
220
219
|
else
|
|
221
220
|
m = "#{@op}_simplify"
|
|
222
221
|
if respond_to?(m, true)
|
|
223
|
-
|
|
222
|
+
__send__(m)
|
|
224
223
|
else
|
|
225
224
|
self
|
|
226
225
|
end
|
|
@@ -256,11 +255,11 @@ class Factbase::Term < Factbase::TermBase
|
|
|
256
255
|
def at(fact, maps, fb)
|
|
257
256
|
assert_args(2)
|
|
258
257
|
i = _values(0, fact, maps, fb)
|
|
259
|
-
raise "Too many values (#{i.size}) at first position, one expected" unless i.size == 1
|
|
258
|
+
raise(RuntimeError, "Too many values (#{i.size}) at first position, one expected") unless i.size == 1
|
|
260
259
|
i = i[0]
|
|
261
|
-
return
|
|
260
|
+
return if i.nil?
|
|
262
261
|
v = _values(1, fact, maps, fb)
|
|
263
|
-
return
|
|
262
|
+
return if v.nil?
|
|
264
263
|
v[i]
|
|
265
264
|
end
|
|
266
265
|
end
|
data/lib/factbase/terms/agg.rb
CHANGED
|
@@ -23,13 +23,12 @@ class Factbase::Agg < Factbase::TermBase
|
|
|
23
23
|
assert_args(2)
|
|
24
24
|
selector = @operands[0]
|
|
25
25
|
unless selector.is_a?(Factbase::Term) || selector.is_a?(Factbase::TermBase)
|
|
26
|
-
raise "A term is expected, but '#{selector}' provided"
|
|
26
|
+
raise(ArgumentError, "A term is expected, but '#{selector}' provided")
|
|
27
27
|
end
|
|
28
28
|
term = @operands[1]
|
|
29
29
|
unless term.is_a?(Factbase::Term) || term.is_a?(Factbase::TermBase)
|
|
30
|
-
raise "A term is expected, but '#{term}' provided"
|
|
30
|
+
raise(ArgumentError, "A term is expected, but '#{term}' provided")
|
|
31
31
|
end
|
|
32
|
-
|
|
33
|
-
term.evaluate(nil, subset, fb)
|
|
32
|
+
term.evaluate(nil, fb.query(selector, maps).each(fb, fact).to_a, fb)
|
|
34
33
|
end
|
|
35
34
|
end
|
|
@@ -24,16 +24,16 @@ class Factbase::Arithmetic < Factbase::TermBase
|
|
|
24
24
|
def evaluate(fact, maps, fb)
|
|
25
25
|
assert_args(2)
|
|
26
26
|
lefts = _values(0, fact, maps, fb)
|
|
27
|
-
return
|
|
28
|
-
raise 'Too many values at first position, one expected' unless lefts.size == 1
|
|
27
|
+
return if lefts.nil?
|
|
28
|
+
raise(ArgumentError, 'Too many values at first position, one expected') unless lefts.size == 1
|
|
29
29
|
rights = _values(1, fact, maps, fb)
|
|
30
|
-
return
|
|
31
|
-
raise 'Too many values at second position, one expected' unless rights.size == 1
|
|
30
|
+
return if rights.nil?
|
|
31
|
+
raise(ArgumentError, 'Too many values at second position, one expected') unless rights.size == 1
|
|
32
32
|
v = lefts[0]
|
|
33
33
|
r = rights[0]
|
|
34
34
|
if v.is_a?(Time) && r.is_a?(String)
|
|
35
35
|
(num, units) = r.split
|
|
36
|
-
num = num
|
|
36
|
+
num = Integer(num, 10)
|
|
37
37
|
r =
|
|
38
38
|
case units
|
|
39
39
|
when 'seconds', 'second'
|
|
@@ -47,9 +47,9 @@ class Factbase::Arithmetic < Factbase::TermBase
|
|
|
47
47
|
when 'weeks', 'week'
|
|
48
48
|
num * 60 * 60 * 24 * 7
|
|
49
49
|
else
|
|
50
|
-
raise "Unknown time unit '#{units}' in '#{r}"
|
|
50
|
+
raise(ArgumentError, "Unknown time unit '#{units}' in '#{r}")
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
-
v.
|
|
53
|
+
v.__send__(@op, r)
|
|
54
54
|
end
|
|
55
55
|
end
|
data/lib/factbase/terms/as.rb
CHANGED
|
@@ -23,9 +23,9 @@ class Factbase::As < Factbase::TermBase
|
|
|
23
23
|
def evaluate(fact, maps, fb)
|
|
24
24
|
assert_args(2)
|
|
25
25
|
a = @operands[0]
|
|
26
|
-
raise "A symbol is expected as first argument of 'as'" unless a.is_a?(Symbol)
|
|
26
|
+
raise(ArgumentError, "A symbol is expected as first argument of 'as'") unless a.is_a?(Symbol)
|
|
27
27
|
vv = _values(1, fact, maps, fb)
|
|
28
|
-
vv&.each { |v| fact.
|
|
28
|
+
vv&.each { |v| fact.__send__(:"#{a}=", v) }
|
|
29
29
|
true
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -26,24 +26,16 @@ class Factbase::Assert < Factbase::TermBase
|
|
|
26
26
|
assert_args(2)
|
|
27
27
|
message = @operands[0]
|
|
28
28
|
unless message.is_a?(String)
|
|
29
|
-
raise
|
|
30
|
-
"A string is expected as first argument of 'assert', but '#{message}' provided"
|
|
29
|
+
raise(ArgumentError, "A string is expected as first argument of 'assert', but '#{message}' provided")
|
|
31
30
|
end
|
|
32
31
|
t = @operands[1]
|
|
33
32
|
unless t.is_a?(Factbase::Term)
|
|
34
|
-
raise
|
|
35
|
-
"A term is expected as second argument of 'assert', but '#{t}' provided"
|
|
33
|
+
raise(ArgumentError, "A term is expected as second argument of 'assert', but '#{t}' provided")
|
|
36
34
|
end
|
|
37
35
|
result = t.evaluate(fact, maps, fb)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if result.is_a?(Array)
|
|
42
|
-
result.any? { |v| v && v != 0 }
|
|
43
|
-
else
|
|
44
|
-
result && result != 0
|
|
45
|
-
end
|
|
46
|
-
raise message unless truthy
|
|
36
|
+
unless result.is_a?(Array) ? result.any? { |v| v && v != 0 } : (result && result != 0)
|
|
37
|
+
raise(StandardError, message)
|
|
38
|
+
end
|
|
47
39
|
true
|
|
48
40
|
end
|
|
49
41
|
end
|
data/lib/factbase/terms/base.rb
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Base class for all terms.
|
|
7
7
|
# Author:: Volodya Lombrozo (volodya.lombrozo@gmail.com)
|
|
8
8
|
# Copyright:: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
9
9
|
# License:: MIT
|
|
10
|
-
|
|
11
|
-
# Base class for all terms.
|
|
12
10
|
class Factbase::TermBase
|
|
13
11
|
# Turns it into a string.
|
|
14
12
|
# @return [String] The string of it
|
|
@@ -35,15 +33,14 @@ class Factbase::TermBase
|
|
|
35
33
|
|
|
36
34
|
def assert_args(num)
|
|
37
35
|
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
|
|
36
|
+
raise(ArgumentError, "Too many (#{c}) operands for '#{@op}' (#{num} expected)") if c > num
|
|
37
|
+
raise(ArgumentError, "Too few (#{c}) operands for '#{@op}' (#{num} expected)") if c < num
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
def _by_symbol(pos, fact)
|
|
43
41
|
o = @operands[pos]
|
|
44
|
-
raise "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided" unless o.is_a?(Symbol)
|
|
45
|
-
|
|
46
|
-
fact[k]
|
|
42
|
+
raise(ArgumentError, "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided") unless o.is_a?(Symbol)
|
|
43
|
+
fact[o.to_s]
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
# @return [Array|nil] Either array of values or NIL
|
|
@@ -61,9 +58,9 @@ class Factbase::TermBase
|
|
|
61
58
|
[v]
|
|
62
59
|
end
|
|
63
60
|
end
|
|
64
|
-
raise 'Why not array?' unless v.is_a?(Array)
|
|
61
|
+
raise(ArgumentError, 'Why not array?') unless v.is_a?(Array)
|
|
65
62
|
unless v.all? { |i| [Float, Integer, String, Time, TrueClass, FalseClass].any? { |t| i.is_a?(t) } }
|
|
66
|
-
raise 'Wrong type inside'
|
|
63
|
+
raise(ArgumentError, 'Wrong type inside')
|
|
67
64
|
end
|
|
68
65
|
v
|
|
69
66
|
end
|
data/lib/factbase/terms/best.rb
CHANGED
|
@@ -10,7 +10,7 @@ class Factbase::Best
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def evaluate(key, maps)
|
|
13
|
-
raise "A symbol is expected, but #{key} provided" unless key.is_a?(Symbol)
|
|
13
|
+
raise(ArgumentError, "A symbol is expected, but #{key} provided") unless key.is_a?(Symbol)
|
|
14
14
|
best = nil
|
|
15
15
|
maps.each do |m|
|
|
16
16
|
vv = m[key.to_s]
|