Class: Factbase::Term

Inherits:
Object
  • Object
show all
Includes:
Aggregates, Logical, TermShared
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 ([email protected])

Copyright

Copyright © 2024-2025 Yegor Bugayenko

License

MIT

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from TermShared

#to_s

Methods included from Aggregates

#_best, #agg, #empty, #max, #min, #nth, #sum

Methods included from Logical

#and_or_simplify, #and_simplify, #or_simplify

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
# File 'lib/factbase/term.rb', line 104

def initialize(operator, operands)
  @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),
    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)
  }
end

Instance Attribute Details

#opSymbol (readonly)

The operator of this term

Returns:

  • (Symbol)

    The operator



86
87
88
# File 'lib/factbase/term.rb', line 86

def op
  @op
end

#operandsArray (readonly)

The operands of this term

Returns:

  • (Array)

    The operands



90
91
92
# File 'lib/factbase/term.rb', line 90

def operands
  @operands
end

Instance Method Details

#abstract?Boolean

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

Returns:



240
241
242
243
244
245
246
# File 'lib/factbase/term.rb', line 240

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



248
249
250
251
252
253
254
255
256
257
# File 'lib/factbase/term.rb', line 248

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



199
200
201
202
203
204
205
206
207
208
209
# File 'lib/factbase/term.rb', line 199

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>)

    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



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/factbase/term.rb', line 178

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



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/factbase/term.rb', line 159

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:



213
214
215
216
217
218
219
220
# File 'lib/factbase/term.rb', line 213

def simplify
  m = "#{@op}_simplify"
  if respond_to?(m, true)
    send(m)
  else
    self
  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:



228
229
230
231
232
233
234
235
# File 'lib/factbase/term.rb', line 228

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