Class: Factbase::Term

Inherits:
Object
  • Object
show all
Includes:
Aggregates, Aliases, Casting, Debug, Defn, Logical, Math, Meta, Ordering, Strings, System
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-2025 Yegor Bugayenko

License

MIT

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Debug

#traced

Methods included from System

#env

Methods included from Defn

#defn, #undef

Methods included from Ordering

#prev, #unique

Methods included from Aliases

#as, #join

Methods included from Meta

#absent, #exists, #many, #nil, #one, #size, #type

Methods included from Casting

#to_float, #to_integer, #to_string, #to_time

Methods included from Strings

#concat, #matches, #sprintf

Methods included from Aggregates

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

Methods included from Logical

#_only_bool, #always, #and, #and_or_simplify, #and_simplify, #either, #never, #not, #or, #or_simplify, #when

Methods included from Math

#_arithmetic, #_cmp, #div, #eq, #gt, #gte, #lt, #lte, #minus, #plus, #times, #zero

Constructor Details

#initialize(operator, operands) ⇒ Term

Ctor.

Parameters:

  • operator (Symbol)

    Operator

  • operands (Array)

    Operands


83
84
85
86
# File 'lib/factbase/term.rb', line 83

def initialize(operator, operands)
  @op = operator
  @operands = operands
end

Instance Attribute Details

#opSymbol (readonly)

The operator of this term

Returns:

  • (Symbol)

    The operator


41
42
43
# File 'lib/factbase/term.rb', line 41

def op
  @op
end

#operandsArray (readonly)

The operands of this term

Returns:

  • (Array)

    The operands


45
46
47
# File 'lib/factbase/term.rb', line 45

def operands
  @operands
end

Instance Method Details

#abstract?Boolean

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

Returns:

  • (Boolean)

    TRUE if abstract


154
155
156
157
158
159
160
# File 'lib/factbase/term.rb', line 154

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


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

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

Does it match the fact?

Parameters:

Returns:

  • (Boolean)

    TRUE if matches


117
118
119
120
121
122
123
# File 'lib/factbase/term.rb', line 117

def evaluate(fact, maps, fb)
  send(@op, fact, maps, fb)
rescue NoMethodError => e
  raise "Probably the term '#{@op}' is not defined at #{self}:\n#{Backtrace.new(e)}"
rescue StandardError => e
  raise "#{e.message} at #{self}:\n#{Backtrace.new(e)}"
end

#predict(maps, _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

Returns:

  • (Array<Hash>)

    Records to iterate


108
109
110
# File 'lib/factbase/term.rb', line 108

def predict(maps, _params)
  maps
end

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

Extend it with the module.

Parameters:

  • type (Module)

    The type to extend with

  • args (Hash)

    Attributes to set


91
92
93
94
95
96
97
98
99
100
101
# File 'lib/factbase/term.rb', line 91

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:


127
128
129
130
131
132
133
134
# File 'lib/factbase/term.rb', line 127

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:

  • (Boolean)

    TRUE if static


142
143
144
145
146
147
148
149
# File 'lib/factbase/term.rb', line 142

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

#to_sString

Turns it into a string.

Returns:

  • (String)

    The string of it


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

def to_s
  items = []
  items << @op
  items +=
    @operands.map do |o|
      if o.is_a?(String)
        "'#{o.gsub("'", "\\\\'").gsub('"', '\\\\"')}'"
      elsif o.is_a?(Time)
        o.utc.iso8601
      else
        o.to_s
      end
    end
  "(#{items.join(' ')})"
end