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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -4
  3. data/Gemfile.lock +18 -14
  4. data/README.md +127 -20
  5. data/Rakefile +2 -7
  6. data/factbase.gemspec +11 -11
  7. data/lib/factbase/accum.rb +1 -1
  8. data/lib/factbase/cached/cached_fact.rb +1 -2
  9. data/lib/factbase/cached/cached_factbase.rb +3 -3
  10. data/lib/factbase/cached/cached_query.rb +4 -6
  11. data/lib/factbase/cached/cached_term.rb +1 -2
  12. data/lib/factbase/churn.rb +4 -8
  13. data/lib/factbase/fact.rb +12 -9
  14. data/lib/factbase/flatten.rb +2 -2
  15. data/lib/factbase/impatient.rb +14 -13
  16. data/lib/factbase/indexed/indexed_and.rb +14 -20
  17. data/lib/factbase/indexed/indexed_eq.rb +5 -1
  18. data/lib/factbase/indexed/indexed_fact.rb +1 -4
  19. data/lib/factbase/indexed/indexed_factbase.rb +4 -4
  20. data/lib/factbase/indexed/indexed_gt.rb +3 -1
  21. data/lib/factbase/indexed/indexed_gte.rb +51 -0
  22. data/lib/factbase/indexed/indexed_lt.rb +3 -1
  23. data/lib/factbase/indexed/indexed_lte.rb +51 -0
  24. data/lib/factbase/indexed/indexed_not.rb +1 -1
  25. data/lib/factbase/indexed/indexed_or.rb +2 -2
  26. data/lib/factbase/indexed/indexed_query.rb +6 -7
  27. data/lib/factbase/indexed/indexed_term.rb +10 -6
  28. data/lib/factbase/indexed/indexed_unique.rb +4 -2
  29. data/lib/factbase/inv.rb +3 -3
  30. data/lib/factbase/lazy_taped.rb +10 -13
  31. data/lib/factbase/lazy_taped_hash.rb +2 -1
  32. data/lib/factbase/light.rb +1 -1
  33. data/lib/factbase/logged.rb +37 -34
  34. data/lib/factbase/pre.rb +3 -3
  35. data/lib/factbase/query.rb +4 -5
  36. data/lib/factbase/rules.rb +8 -8
  37. data/lib/factbase/sync/sync_factbase.rb +2 -2
  38. data/lib/factbase/syntax.rb +19 -20
  39. data/lib/factbase/tallied.rb +7 -8
  40. data/lib/factbase/taped.rb +5 -11
  41. data/lib/factbase/tee.rb +2 -2
  42. data/lib/factbase/term.rb +58 -59
  43. data/lib/factbase/terms/agg.rb +3 -4
  44. data/lib/factbase/terms/arithmetic.rb +7 -7
  45. data/lib/factbase/terms/as.rb +2 -2
  46. data/lib/factbase/terms/assert.rb +5 -13
  47. data/lib/factbase/terms/base.rb +7 -10
  48. data/lib/factbase/terms/best.rb +1 -1
  49. data/lib/factbase/terms/boolean.rb +1 -1
  50. data/lib/factbase/terms/compare.rb +17 -1
  51. data/lib/factbase/terms/contains.rb +28 -0
  52. data/lib/factbase/terms/defn.rb +8 -6
  53. data/lib/factbase/terms/empty.rb +1 -1
  54. data/lib/factbase/terms/ends_with.rb +27 -0
  55. data/lib/factbase/terms/first.rb +2 -2
  56. data/lib/factbase/terms/head.rb +3 -3
  57. data/lib/factbase/terms/inverted.rb +2 -2
  58. data/lib/factbase/terms/join.rb +8 -7
  59. data/lib/factbase/terms/matches.rb +14 -4
  60. data/lib/factbase/terms/max.rb +1 -1
  61. data/lib/factbase/terms/min.rb +1 -1
  62. data/lib/factbase/terms/nth.rb +3 -3
  63. data/lib/factbase/terms/plus.rb +1 -1
  64. data/lib/factbase/terms/prev.rb +3 -6
  65. data/lib/factbase/terms/sorted.rb +2 -2
  66. data/lib/factbase/terms/sprintf.rb +11 -2
  67. data/lib/factbase/terms/starts_with.rb +27 -0
  68. data/lib/factbase/terms/sum.rb +2 -2
  69. data/lib/factbase/terms/to_float.rb +2 -2
  70. data/lib/factbase/terms/to_integer.rb +2 -2
  71. data/lib/factbase/terms/to_string.rb +1 -1
  72. data/lib/factbase/terms/to_time.rb +10 -2
  73. data/lib/factbase/terms/traced.rb +2 -2
  74. data/lib/factbase/terms/undef.rb +2 -2
  75. data/lib/factbase/terms/unique.rb +3 -7
  76. data/lib/factbase/terms/when.rb +2 -3
  77. data/lib/factbase/to_json.rb +2 -2
  78. data/lib/factbase/to_xml.rb +6 -10
  79. data/lib/factbase/to_yaml.rb +1 -1
  80. data/lib/factbase/version.rb +1 -2
  81. data/lib/factbase.rb +27 -13
  82. data/lib/fuzz.rb +3 -3
  83. 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 Factbase::Pre.new(fbt, &@block)
42
+ yield(Factbase::Pre.new(fbt, &@block))
43
43
  end
44
44
  end
45
45
  end
@@ -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
- a = Factbase::Accum.new(f, extras, false)
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 Factbase::Accum.new(f, extras, true)
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
@@ -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 Factbase::Tallied.new(Factbase::Rules.new(fbt, @rules, @check, uid: @uid), churn)
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 Fact.new(f, @check, fb)
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 'sync_query'
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 Factbase::SyncFactbase.new(fbt, @monitor)
56
+ yield(Factbase::SyncFactbase.new(fbt, @monitor))
57
57
  end
58
58
  end
59
59
  end
@@ -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 Broken, err
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
- t = Factbase::Term.new(op, operands)
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
- # ignore it
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.to_i
145
- elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e\+[0-9]+)?$/)
146
- t.to_f
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
@@ -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 count all operations and then returns
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
- f
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 :rollback do
43
- yield Factbase::Tallied.new(fbt, @churn)
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 e
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 Fact.new(f, @churn)
98
+ yield(Fact.new(f, @churn))
100
99
  end
101
100
  end
102
101
 
@@ -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 TapedHash.new(m, @added)
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 m
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
- n = yield @origin.to_a, other.to_a
157
- raise 'Cannot join with another Taped' if other.respond_to?(:inserted)
158
- raise 'Can only join with array' unless other.is_a?(Array)
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/unique'
11
- require_relative 'terms/prev'
12
- require_relative 'terms/concat'
13
- require_relative 'terms/sprintf'
14
- require_relative 'terms/matches'
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/env'
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/zero'
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/lt'
43
- require_relative 'terms/lte'
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/always'
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/agg'
58
- require_relative 'terms/empty'
59
- require_relative 'terms/min'
60
- require_relative 'terms/max'
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
- # The operator of this term
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 type
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
- send(m, maps, fb, params)
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
- send(@op, fact, maps, fb)
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
- send(m)
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 nil if i.nil?
260
+ return if i.nil?
262
261
  v = _values(1, fact, maps, fb)
263
- return nil if v.nil?
262
+ return if v.nil?
264
263
  v[i]
265
264
  end
266
265
  end
@@ -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
- subset = fb.query(selector, maps).each(fb, fact).to_a
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 nil if lefts.nil?
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 nil if rights.nil?
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.to_i
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.send(@op, r)
53
+ v.__send__(@op, r)
54
54
  end
55
55
  end
@@ -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.send(:"#{a}=", v) }
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 ArgumentError,
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 ArgumentError,
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
- # Convert result to boolean-like evaluation
39
- # Arrays are truthy if they contain at least one truthy element
40
- truthy =
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
@@ -3,12 +3,10 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- # Test for unique term.
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
- k = o.to_s
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
@@ -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]