Module: Ruler

Included in:
TeaDrinker
Defined in:
lib/ruler.rb

Instance Method Summary collapse

Instance Method Details

#default_rule(&blk) ⇒ Object

the default_rule is simple a rule that is always true. This is mostly syntactic-sugar to represent a rule that should fire if no others fire. You cannot have a default rule if you allow more than one match. The BadDefaultRule exception is raised.

Raises:



160
161
162
163
164
165
166
167
# File 'lib/ruler.rb', line 160

def default_rule &blk
  raise BadDefaultRule.new("Can't have a default rule when multiple matches are allowed") unless Thread.current[@rulesetids.last][:singletary]
  if Thread.current[@rulesetids.last][:singletary] && !Thread.current[@rulesetids.last][:rulematched]
    yield
  else
    Thread.current[@rulesetids.last][:rulematched]
  end
end

#dynamic_fact(name, &blk) ⇒ Object

a dynamic_fact is evaulated every time the fact is checked. Unlike a normal fact, which is only evaluated once, dynamic facts are evaluated once for each rule they appear in



110
111
112
# File 'lib/ruler.rb', line 110

def dynamic_fact name, &blk
  Thread.current[@rulesetids.last][:working_memory][name] = {:transient => true, :block => blk }
end

#fact(name, dval = nil, &blk) ⇒ Object

a fact takes a symbol name and either a value or a block. for example:

fact :factname, true
fact :otherfactname do
          some_computation_that_evaluates_to_true_or_false
end

Facts may be defined anywhere in the ruleset. due to the evaluation order, facts that appear after the last rule will never be used.



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ruler.rb', line 95

def fact name, dval = nil, &blk
  if dval.nil?
    _res = begin 
             yield 
           rescue 
             raise BadFact.new($!)
           end
    Thread.current[@rulesetids.last][:working_memory][name] = {:value =>  _res }
  else
    Thread.current[@rulesetids.last][:working_memory][name] = {:value => dval }
  end
end

#multi_ruleset(&blk) ⇒ Object

multi_ruleset is a helper function to define rulesets that allow more than one rule to fire



81
82
83
# File 'lib/ruler.rb', line 81

def multi_ruleset &blk
  ruleset false,&blk
end

#notf(name) ⇒ Object

allows for a fact to be NOT another fact. notf cannot be used with a dynamic_fact for example:

fact :one, 10 == 10
fact :notfone, not(:one)


118
119
120
121
122
123
124
# File 'lib/ruler.rb', line 118

def notf name
  if Thread.current[@rulesetids.last][:working_memory][name][:transient]
    raise BadNotCall.new("Cannot call notf on dynamic fact")
  else
    not(Thread.current[@rulesetids.last][:working_memory][name][:value])
  end
end

#rule(vlist, docstr = nil, &blk) ⇒ Object

a rule takes a list of fact names and a block. Rules are evaluated in the order they appear in the ruleset and are evaluated at execution time. If all of the facts are true, then that rule is fired. If the ruleset is singletary, only one rule (the first rule to be true) may fire.

rules may also have docstrings. These aren’t really used yet, but they will be and they provide a nice alternative to commenting. For example:

rule [:one_fact, :two_facts], "this is the docstring" do
    some_method
end

there is no check to see if fact names are valid, and facts can be (re)defined inside of rules. Fact names are false if they are not defined.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ruler.rb', line 138

def rule vlist,docstr = nil,&blk
  conditional_call = lambda {|n| begin Thread.current[@rulesetids.last][:working_memory][n][:transient].nil? ? Thread.current[@rulesetids.last][:working_memory][n][:value] : Thread.current[@rulesetids.last][:working_memory][n][:block].call() rescue false end }
  dbg = lambda {|va|  puts begin "|=-\t#{va} = #{conditional_call.call(va)}" rescue "|>=- ERROR: #{$!}" end }
  if @DEBUG
    puts "---------------------------------------"
    puts vlist.join(" & ")
    puts "======================================="
    vlist.each {|v| dbg.call(v) }
    puts "---------------------------------------"
  end
  if Thread.current[@rulesetids.last][:singletary] && Thread.current[@rulesetids.last][:rulematched]
    Thread.current[@rulesetids.last][:rulematched]
  else
    Thread.current[@rulesetids.last][:rulematched] = if vlist.inject(true) {|k,v| raise UnknownFact.new("Fact: #{v} is unknown") if Thread.current[@rulesetids.last][:working_memory][v].nil? ;k ? k && conditional_call.call(v) : false }
                                     yield 
                                   end
  end
end

#ruleset(singletary = true, &blk) ⇒ Object

puts result



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ruler.rb', line 65

def ruleset singletary = true,&blk
  @rulesetids ||= []
  @rulesetids << UUID.generate
  puts "Ruleset: #{@rulesetids.last} of #{@rulesetids.length}" if @DEBUG
  Thread.current[@rulesetids.last] = {:singletary => singletary, :rulematched => nil, :working_memory => {}}
  _rval = nil
  begin
    _rval = yield
  ensure
    @rulesetids.pop
  end
  _rval
end