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 ([email protected])

Copyright

Copyright © 2024-2025 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



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

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



92
93
94
# File 'lib/factbase/term.rb', line 92

def op
  @op
end

#operandsArray (readonly)

The operands of this term

Returns:

  • (Array)

    The operands



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

def operands
  @operands
end

Instance Method Details

#abstract?Boolean

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

Returns:



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

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



256
257
258
259
260
261
262
263
264
265
# File 'lib/factbase/term.rb', line 256

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



203
204
205
206
207
208
209
210
211
212
213
# File 'lib/factbase/term.rb', line 203

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



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/factbase/term.rb', line 182

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



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/factbase/term.rb', line 163

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:



217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/factbase/term.rb', line 217

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:



236
237
238
239
240
241
242
243
# File 'lib/factbase/term.rb', line 236

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