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

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



151
152
153
154
155
156
157
# File 'lib/factbase/term.rb', line 151

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



177
178
179
180
181
182
183
184
185
186
# File 'lib/factbase/term.rb', line 177

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



114
115
116
117
118
119
120
# File 'lib/factbase/term.rb', line 114

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



105
106
107
# File 'lib/factbase/term.rb', line 105

def predict(maps, _params)
  maps
end

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



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/factbase/term.rb', line 88

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:



124
125
126
127
128
129
130
131
# File 'lib/factbase/term.rb', line 124

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



139
140
141
142
143
144
145
146
# File 'lib/factbase/term.rb', line 139

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



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

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