Permalink
Browse files

addition of rulehelper to enable rules to live outside of a rulebook

  • Loading branch information...
1 parent e3e38ff commit 4a00eed4816f57ef206f233d7b56d54d322b3b4f @amattsmith amattsmith committed Jun 20, 2010
Showing with 205 additions and 88 deletions.
  1. +79 −0 examples/fibonacci_example3.rb
  2. +11 −5 lib/core/engine.rb
  3. +42 −40 lib/dsl/ferrari.rb
  4. +64 −0 lib/rule_helper.rb
  5. +8 −42 lib/rulebook.rb
  6. +1 −1 lib/ruleby.rb
@@ -0,0 +1,79 @@
+# This file is part of the Ruleby project (http://ruleby.org)
+#
+# This application is free software; you can redistribute it and/or
+# modify it under the terms of the Ruby license defined in the
+# LICENSE.txt file.
+#
+# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
+#
+# * Authors: Joe Kutner, Matt Smith
+#
+
+$LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
+require 'ruleby'
+require 'rule_helper'
+class Fibonacci
+ def initialize(sequence,value=-1)
+ @sequence = sequence
+ @value = value
+ end
+
+ attr_reader :sequence
+ attr :value, true
+
+ def to_s
+ return super + "::sequence=" + @sequence.to_s + ",value=" + @value.to_s
+ end
+end
+
+include Ruleby::RuleHelper
+#RULES
+# Bootstrap1
+bootstrap1_rule = rule :Bootstrap1, {:priority => 4},
+ [Fibonacci, :f, m.value == -1, m.sequence == 1 ] do |vars, engine|
+ vars[:f].value = 1
+ engine.modify vars[:f]
+ puts vars[:f].sequence.to_s + ' == ' + vars[:f].value.to_s
+ end
+
+ # Recurse
+recurse_rule = rule :Recurse, {:priority => 3},
+ [Fibonacci, :f, m.value == -1] do |vars, engine|
+ f2 = Fibonacci.new(vars[:f].sequence - 1)
+ engine.assert f2
+ puts 'recurse for ' + f2.sequence.to_s
+ end
+
+ # Bootstrap2
+bootstrap2_rule = rule :Bootstrap2,
+ [Fibonacci, :f, m.value == -1 , m.sequence == 2] do |vars, engine|
+ vars[:f].value = 1
+ engine.modify vars[:f]
+ puts vars[:f].sequence.to_s + ' == ' + vars[:f].value.to_s
+ end
+
+ # Calculate
+calculate_rule = rule :Calculate,
+ [Fibonacci,:f1, m.value.not== -1, {m.sequence => :s1}],
+ [Fibonacci,:f2, m.value.not== -1, {m.sequence( :s1, &c{ |s2,s1| s2 == s1 + 1 } ) => :s2}],
+ [Fibonacci,:f3, m.value == -1, m.sequence(:s2, &c{ |s3,s2| s3 == s2 + 1 }) ] do |vars, engine|
+ vars[:f3].value = vars[:f1].value + vars[:f2].value
+ engine.modify vars[:f3]
+ engine.retract vars[:f1]
+ puts vars[:f3].sequence.to_s + ' == ' + vars[:f3].value.to_s
+ end
+
+# FACTS
+fib1 = Fibonacci.new(150)
+
+include Ruleby
+
+engine :engine do |e|
+ #FibonacciRulebook2.new(e).rules
+ e.assert_rule bootstrap1_rule
+ e.assert_rule recurse_rule
+ e.assert_rule bootstrap2_rule
+ e.assert_rule calculate_rule
+ e.assert fib1
+ e.match
+end
View
@@ -23,15 +23,20 @@ class Action
attr_accessor :priority
attr_accessor :name
attr_reader :matches
+ attr_reader :proc
def initialize(&block)
@name = nil
@proc = Proc.new(&block) if block_given?
@priority = 0
end
- def fire(match)
- @proc.call(match)
+ def fire(match, engine=nil)
+ if @proc.arity == 2
+ @proc.call(match, engine)
+ else
+ @proc.call(match)
+ end
end
def ==(a2)
@@ -55,9 +60,9 @@ def initialize(action, match, counter=0)
@used = false
end
- def fire()
+ def fire(engine=nil)
@used = true
- @action.fire @match
+ @action.fire @match, engine
end
def <=>(a2)
@@ -177,6 +182,7 @@ def print
# instantiating it. Each rule engine has one inference engine, one rule set
# and one working memory.
class Engine
+
def initialize(wm=WorkingMemory.new,cr=RulebyConflictResolver.new)
@root = nil
@working_memory = wm
@@ -231,7 +237,7 @@ def match(agenda=nil, used_agenda=[])
agenda = @conflict_resolver.resolve agenda
activation = agenda.pop
used_agenda.push activation
- activation.fire
+ activation.fire self
if @wm_altered
agenda = @root.matches(false)
@root.increment_counter
View
@@ -21,59 +21,61 @@ def initialize(engine)
def rule(name, *args, &block)
options = args[0].kind_of?(Hash) ? args.shift : {}
- parse_containers(args, RulesContainer.new).build(name,options,@engine,&block)
- end
+ r = Ruleby::Ferrari.parse_containers(args, RulesContainer.new).build(name,options,@engine,&block)
+ engine.assert_rule(r)
+ end
+ end
- private
- def parse_containers(args, container=AndContainer.new)
- or_builders = []
- and_container = AndContainer.new
- args.each do |arg|
- if arg.kind_of? Array
- and_container << PatternContainer.new(arg)
- elsif arg.kind_of? AndBuilder
- and_container << parse_containers(arg.conditions)
- elsif arg.kind_of? OrBuilder
- or_builders << arg
- else
- raise 'Invalid condition. Must be an OR, AND or an Array.'
- end
+ #private
+ def self.parse_containers(args, container=AndContainer.new)
+ or_builders = []
+ and_container = AndContainer.new
+ args.each do |arg|
+ if arg.kind_of? Array
+ and_container << PatternContainer.new(arg)
+ elsif arg.kind_of? AndBuilder
+ and_container << parse_containers(arg.conditions)
+ elsif arg.kind_of? OrBuilder
+ or_builders << arg
+ else
+ raise 'Invalid condition. Must be an OR, AND or an Array.'
end
-
- if or_builders.empty?
- container << and_container
- else
- while !or_builders.empty?
- or_builder = or_builders.pop
- parse_containers(or_builder.conditions, OrContainer.new).each do |or_container|
- or_container.each do |or_container_child|
- rule = AndContainer.new
- rule.push or_container_child
+ end
+
+ if or_builders.empty?
+ container << and_container
+ else
+ while !or_builders.empty?
+ or_builder = or_builders.pop
+ parse_containers(or_builder.conditions, OrContainer.new).each do |or_container|
+ or_container.each do |or_container_child|
+ rule = AndContainer.new
+ rule.push or_container_child
- or_builders.each do |sub_or_builder|
- parse_containers(sub_or_builder.conditions).each do |sub_or_container|
- rule.push *sub_or_container
- end
+ or_builders.each do |sub_or_builder|
+ parse_containers(sub_or_builder.conditions).each do |sub_or_container|
+ rule.push *sub_or_container
end
-
- rule.push and_container
- container << rule
end
- end
- end
- end
- return container
- end
- end
+ rule.push and_container
+ container << rule
+ end
+ end
+ end
+ end
+ return container
+ end
+
class RulesContainer < Array
def build(name,options,engine,&block)
self.each do |x|
r = RuleBuilder.new name
x.build r
r.then(&block)
r.priority = options[:priority] if options[:priority]
- engine.assert_rule(r.build_rule)
+ #engine.assert_rule(r.build_rule)
+ return r.build_rule
end
end
end
View
@@ -0,0 +1,64 @@
+# This file is part of the Ruleby project (http://ruleby.org)
+#
+# This application is free software; you can redistribute it and/or
+# modify it under the terms of the Ruby license defined in the
+# LICENSE.txt file.
+#
+# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
+#
+# * Authors: Joe Kutner, Matt Smith
+#
+
+require 'core/engine'
+
+module Ruleby
+ module RuleHelper
+ def rule(*args, &block)
+ name = nil
+ unless args.empty?
+ name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
+ end
+ options = args[0].kind_of?(Hash) ? args.shift : {}
+
+ r = Ruleby::Ferrari.parse_containers(args, Ruleby::Ferrari::RulesContainer.new).build(name,options,@engine,&block)
+ r
+ end
+
+ def m
+ Ruleby::Ferrari::MethodBuilder.new
+ end
+
+ def method
+ m
+ end
+
+ def b(variable_name)
+ Ruleby::Ferrari::BindingBuilder.new(variable_name)
+ end
+
+ def binding(variable_name)
+ b variable_name
+ end
+
+ def c(&block)
+ return lambda(&block)
+ end
+
+ def condition(&block)
+ return lambda(&block)
+ end
+
+ def OR(*args)
+ Ruleby::Ferrari::OrBuilder.new args
+ end
+
+ def AND(*args)
+ Ruleby::Ferrari::AndBuilder.new args
+ end
+
+ def __eval__(x)
+ eval(x)
+ end
+
+ end
+end
View
@@ -10,13 +10,15 @@
#
require 'ruleby'
+require 'rule_helper'
require 'dsl/ferrari'
require 'dsl/letigre'
require 'dsl/steel'
module Ruleby
class Rulebook
include Ruleby
+ include Ruleby::RuleHelper
def initialize(engine, &block)
@engine = engine
yield self if block_given?
@@ -57,66 +59,30 @@ def rule(*args, &block)
end
end
end
-
- def m
- Ruleby::Ferrari::MethodBuilder.new
- end
-
- def method
- m
- end
-
- def b(variable_name)
- Ruleby::Ferrari::BindingBuilder.new(variable_name)
- end
-
- def binding(variable_name)
- b variable_name
- end
-
- def c(&block)
- return lambda(&block)
- end
-
- def condition(&block)
- return lambda(&block)
- end
-
- def OR(*args)
- Ruleby::Ferrari::OrBuilder.new args
- end
-
- def AND(*args)
- Ruleby::Ferrari::AndBuilder.new args
- end
-
- def __eval__(x)
- eval(x)
- end
- end
+ end
class GeneratedTag
# this counter is incremented for each UniqueTag created, and is
# appended to the end of the unique_seed in order to create a
# string that is unique for each instance of this class.
@@tag_counter = 0
-
+
# every generated tag will be prefixed with this string
@@unique_seed = 'unique_seed'
-
+
def initialize()
@@tag_counter += 1
@tag = @@unique_seed + @@tag_counter.to_s
end
-
+
attr_reader:tag_counter
attr_reader:unique_seed
attr_reader:tag
-
+
def ==(ut)
return ut && ut.kind_of?(GeneratedTag) && @tag == ut.tag
end
-
+
def to_s
return @tag.to_s
end
View
@@ -18,7 +18,7 @@ def engine(name, &block)
e = Core::Engine.new
yield e if block_given?
return e
- end
+ end
end
class String

0 comments on commit 4a00eed

Please sign in to comment.