Class: Factbase::Term

Inherits:
TermBase show all
Defined in:
lib/factbase/term.rb

Overview

Term.

This is an internal class, it is not supposed to be instantiated directly.

It is possible to use for testing directly, for example to make a term with two arguments:

require 'factbase/fact'
require 'factbase/term'
f = Factbase::Fact.new({ 'foo' => [42, 256, 'Hello, world!'] })
t = Factbase::Term.new(:lt, [:foo, 50])
assert(t.evaluate(f))

The design of this class may look ugly, since it has a large number of methods, each of which corresponds to a different type of a Term. A much better design would definitely involve many classes, one per each type of a term. It’s not done this way because of an experimental nature of the project. Most probably we should keep current design intact, since it works well and is rather simple to extend (by adding new term types). Moreover, it looks like the number of possible term types is rather limited and currently we implement most of them.

It is NOT thread-safe!

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2024-2026 Yegor Bugayenko

License

MIT

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from TermBase

#to_s

Constructor Details

#initialize(operator, operands) ⇒ Term

Ctor.

Parameters:

  • operator (Symbol)

    Operator

  • operands (Array)

    Operands



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/factbase/term.rb', line 104

def initialize(operator, operands)
  super()
  @op = operator
  @operands = operands
  @terms = {
    unique: Factbase::Unique.new(operands),
    prev: Factbase::Prev.new(operands),
    concat: Factbase::Concat.new(operands),
    sprintf: Factbase::Sprintf.new(operands),
    matches: Factbase::Matches.new(operands),
    contains: Factbase::Contains.new(operands),
    starts_with: Factbase::StartsWith.new(operands),
    ends_with: Factbase::EndsWith.new(operands),
    traced: Factbase::Traced.new(operands),
    assert: Factbase::Assert.new(operands),
    env: Factbase::Env.new(operands),
    defn: Factbase::Defn.new(operands),
    undef: Factbase::Undef.new(operands),
    as: Factbase::As.new(operands),
    join: Factbase::Join.new(operands),
    exists: Factbase::Exists.new(operands),
    absent: Factbase::Absent.new(operands),
    size: Factbase::Size.new(operands),
    type: Factbase::Type.new(operands),
    nil: Factbase::Nil.new(operands),
    many: Factbase::Many.new(operands),
    one: Factbase::One.new(operands),
    to_string: Factbase::ToString.new(operands),
    to_integer: Factbase::ToInteger.new(operands),
    to_float: Factbase::ToFloat.new(operands),
    to_time: Factbase::ToTime.new(operands),
    sorted: Factbase::Sorted.new(operands),
    inverted: Factbase::Inverted.new(operands),
    head: Factbase::Head.new(operands),
    plus: Factbase::Plus.new(operands),
    minus: Factbase::Minus.new(operands),
    times: Factbase::Times.new(operands),
    div: Factbase::Div.new(operands),
    zero: Factbase::Zero.new(operands),
    eq: Factbase::Eq.new(operands),
    lt: Factbase::Lt.new(operands),
    lte: Factbase::Lte.new(operands),
    gt: Factbase::Gt.new(operands),
    gte: Factbase::Gte.new(operands),
    always: Factbase::Always.new(operands),
    never: Factbase::Never.new(operands),
    not: Factbase::Not.new(operands),
    or: Factbase::Or.new(operands),
    and: Factbase::And.new(operands),
    when: Factbase::When.new(operands),
    either: Factbase::Either.new(operands),
    count: Factbase::Count.new(operands),
    first: Factbase::First.new(operands),
    nth: Factbase::Nth.new(operands),
    sum: Factbase::Sum.new(operands),
    agg: Factbase::Agg.new(operands),
    empty: Factbase::Empty.new(operands),
    min: Factbase::Min.new(operands),
    max: Factbase::Max.new(operands)
  }
end

Instance Attribute Details

#opSymbol (readonly)

The operator of this term

Returns:

  • (Symbol)

    The operator



95
96
97
# File 'lib/factbase/term.rb', line 95

def op
  @op
end

#operandsArray (readonly)

The operands of this term

Returns:

  • (Array)

    The operands



99
100
101
# File 'lib/factbase/term.rb', line 99

def operands
  @operands
end

Instance Method Details

#abstract?Boolean

Does it have any variables (+$foo+, for example) inside?

Returns:



254
255
256
257
258
259
260
# File 'lib/factbase/term.rb', line 254

def abstract?
  @operands.each do |o|
    return true if o.is_a?(Factbase::Term) && o.abstract?
    return true if o.is_a?(Symbol) && o.to_s.start_with?('$')
  end
  false
end

#at(fact, maps, fb) ⇒ Object



262
263
264
265
266
267
268
269
270
271
# File 'lib/factbase/term.rb', line 262

def at(fact, maps, fb)
  assert_args(2)
  i = _values(0, fact, maps, fb)
  raise "Too many values (#{i.size}) at first position, one expected" unless i.size == 1
  i = i[0]
  return nil if i.nil?
  v = _values(1, fact, maps, fb)
  return nil if v.nil?
  v[i]
end

#evaluate(fact, maps, fb) ⇒ Object

Evaluate term on a fact

Parameters:

Returns:

  • (Object)

    The result of evaluation



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/factbase/term.rb', line 209

def evaluate(fact, maps, fb)
  if @terms.key?(@op)
    @terms[@op].evaluate(fact, maps, fb)
  else
    send(@op, fact, maps, fb)
  end
rescue NoMethodError => e
  raise "Probably the term '#{@op}' is not defined at #{self}: #{e.message}"
rescue StandardError => e
  raise "#{e.message.inspect} at #{self} at #{e.backtrace[0]}"
end

#predict(maps, fb, params) ⇒ Array<Hash>

Try to predict which facts from the provided list should be evaluated. If no prediction can be made, the same list is returned.

Parameters:

  • maps (Array<Hash>, Factbase::Taped, Factbase::LazyTaped)

    Records to iterate, maybe

  • params (Hash)

    Params to use (keys must be strings, not symbols, with values as arrays)

Returns:

  • (Array<Hash>)

    Records to iterate



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/factbase/term.rb', line 188

def predict(maps, fb, params)
  m = :"#{@op}_predict"
  if @terms.key?(@op)
    t = @terms[@op]
    if t.respond_to?(:predict)
      t.predict(maps, fb, params)
    else
      maps
    end
  elsif respond_to?(m)
    send(m, maps, fb, params)
  else
    maps
  end
end

#redress!(type, **args) ⇒ Object

Extend it with the module.

Parameters:

  • type (Module)

    The type to extend with

  • args (Hash)

    Attributes to set



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/factbase/term.rb', line 169

def redress!(type, **args)
  extend type

  args.each { |k, v| send(:instance_variable_set, :"@#{k}", v) }
  @operands.map do |op|
    if op.is_a?(Factbase::Term)
      op.redress!(type, **args)
    else
      op
    end
  end
end

#simplifyFactbase::Term

Simplify it if possible.

Returns:



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/factbase/term.rb', line 223

def simplify
  if @terms.key?(@op) && @terms[@op].respond_to?(:simplify)
    @terms[@op].simplify
  else
    m = "#{@op}_simplify"
    if respond_to?(m, true)
      send(m)
    else
      self
    end
  end
end

#static?Boolean

Does it have any dependencies on a fact?

If a term is static, it will return the same value for evaluate, no matter what is the fact given.

Returns:



242
243
244
245
246
247
248
249
# File 'lib/factbase/term.rb', line 242

def static?
  return true if @op == :agg
  @operands.each do |o|
    return false if o.is_a?(Factbase::Term) && !o.static?
    return false if o.is_a?(Symbol) && !o.to_s.start_with?('$')
  end
  true
end