ruleby 0.3 → 0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/core/atoms.rb +76 -27
- data/lib/core/engine.rb +11 -5
- data/lib/core/nodes.rb +175 -71
- data/lib/core/patterns.rb +21 -18
- data/lib/dsl/ferrari.rb +50 -33
- data/lib/dsl/letigre.rb +43 -17
- data/lib/dsl/steel.rb +1 -0
- data/lib/dsl/yaml_dsl.rb +23 -0
- data/lib/rulebook.rb +18 -3
- data/lib/ruleby.rb +37 -0
- data/tests/test.rb +15 -0
- metadata +53 -57
- data/benchmarks/basic_rules.rb +0 -66
- data/benchmarks/joined_rules.rb +0 -73
- data/benchmarks/miss_manners/data.rb +0 -146
- data/benchmarks/miss_manners/miss_manners.rb +0 -33
- data/benchmarks/miss_manners/model.rb +0 -193
- data/benchmarks/miss_manners/rules.rb +0 -104
- data/benchmarks/model.rb +0 -36
- data/examples/example_diagnosis.rb +0 -117
- data/examples/example_hello.rb +0 -46
- data/examples/example_politician.rb +0 -97
- data/examples/example_ticket.rb +0 -113
- data/examples/fibonacci_example1.rb +0 -44
- data/examples/fibonacci_example2.rb +0 -40
- data/examples/fibonacci_rulebook.rb +0 -84
- data/examples/test_self_reference.rb +0 -77
@@ -1,193 +0,0 @@
|
|
1
|
-
# This file is part of the Ruleby project (http://ruleby.org)
|
2
|
-
#
|
3
|
-
# This application is free software; you can redistribute it and/or
|
4
|
-
# modify it under the terms of the Ruby license defined in the
|
5
|
-
# LICENSE.txt file.
|
6
|
-
#
|
7
|
-
# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
|
8
|
-
#
|
9
|
-
# * Authors: Joe Kutner, Matt Smith
|
10
|
-
#
|
11
|
-
|
12
|
-
module MissManners
|
13
|
-
|
14
|
-
class Chosen
|
15
|
-
def initialize(id,guestName,hobby)
|
16
|
-
@id = id
|
17
|
-
@guestName = guestName
|
18
|
-
@hobby = hobby
|
19
|
-
end
|
20
|
-
attr_reader :id, :guestName, :hobby
|
21
|
-
def to_s
|
22
|
-
"{Chosen id=#{@id}, name=#{@guestName}, hobbies=#{@hobby}}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class Context
|
27
|
-
START_UP = 0
|
28
|
-
ASSIGN_SEATS = 1
|
29
|
-
MAKE_PATH = 2
|
30
|
-
CHECK_DONE = 3
|
31
|
-
PRINT_RESULTS = 4
|
32
|
-
STATE_STRINGS = ["START_UP","ASSIGN_SEATS","MAKE_PATH","CHECK_DONE","PRINT_RESULTS"]
|
33
|
-
def initialize(state)
|
34
|
-
if state == :start
|
35
|
-
@state = START_UP
|
36
|
-
else
|
37
|
-
raise "Context #{state.to_s} does not exist for Context Enum"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
attr :state, true
|
41
|
-
def string_value
|
42
|
-
return STATE_STRINGS[@state]
|
43
|
-
end
|
44
|
-
def is_state(state)
|
45
|
-
return @state == state
|
46
|
-
end
|
47
|
-
def to_s
|
48
|
-
return "[Context state=" + string_value.to_s + "]";
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Count
|
53
|
-
def initialize(value)
|
54
|
-
@value = value
|
55
|
-
end
|
56
|
-
attr :value, true
|
57
|
-
def ==(object)
|
58
|
-
if object.object_id == self.object_id
|
59
|
-
return true
|
60
|
-
elsif object == nil || !(object.kind_of?(Count))
|
61
|
-
return false
|
62
|
-
end
|
63
|
-
return @value == object.value;
|
64
|
-
end
|
65
|
-
def to_s
|
66
|
-
return "[Count value=#{@value.to_s}]"
|
67
|
-
end
|
68
|
-
def to_hash
|
69
|
-
return value.to_hash
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class Guest
|
74
|
-
def initialize(name,sex,hobby)
|
75
|
-
@name = name
|
76
|
-
@sex = sex
|
77
|
-
@hobby = hobby
|
78
|
-
end
|
79
|
-
attr_reader :name,:sex,:hobby
|
80
|
-
def to_s
|
81
|
-
return "[Guest name=" + @name.to_s + ", sex=" + @sex.to_s + ", hobbies=" + @hobby.to_s + "]";
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class Hobby
|
86
|
-
@stringH1 = "h1"
|
87
|
-
@stringH2 = "h2"
|
88
|
-
@stringH3 = "h3"
|
89
|
-
@stringH4 = "h4"
|
90
|
-
@stringH5 = "h5"
|
91
|
-
HOBBY_STRINGS = [@stringH1,@stringH2,@stringH3,@stringH4,@stringH5]
|
92
|
-
def initialize(hobby)
|
93
|
-
@hobbyIndex = hobby-1
|
94
|
-
@hobby = HOBBY_STRINGS[@hobbyIndex]
|
95
|
-
end
|
96
|
-
H1 = Hobby.new(1)
|
97
|
-
H2 = Hobby.new(2)
|
98
|
-
H3 = Hobby.new(3)
|
99
|
-
H4 = Hobby.new(4)
|
100
|
-
H5 = Hobby.new(5)
|
101
|
-
attr_reader :hobby
|
102
|
-
def resolve(hobby)
|
103
|
-
if @stringH1 == hobby
|
104
|
-
return H1
|
105
|
-
elsif @stringH2 == hobby
|
106
|
-
return H2
|
107
|
-
elsif @stringH3 == hobby
|
108
|
-
return H3
|
109
|
-
elsif @stringH4 == hobby
|
110
|
-
return H4
|
111
|
-
elsif @stringH5 == hobby
|
112
|
-
return H5
|
113
|
-
else
|
114
|
-
raise "Hobby '" + @hobby.to_s + "' does not exist for Hobby Enum"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
def to_hash
|
118
|
-
return @hobbyIndex.to_hash
|
119
|
-
end
|
120
|
-
def to_s
|
121
|
-
return @hobby
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class LastSeat
|
126
|
-
def initialize(seat)
|
127
|
-
@seat = seat
|
128
|
-
end
|
129
|
-
attr_reader :seat
|
130
|
-
def to_s
|
131
|
-
return "[LastSeat seat=#{@seat.to_s}]"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
class Path
|
136
|
-
def initialize(id,seat,guestName)
|
137
|
-
@id = id
|
138
|
-
@guestName = guestName
|
139
|
-
@seat = seat
|
140
|
-
end
|
141
|
-
attr_reader :id, :guestName, :seat
|
142
|
-
def to_s
|
143
|
-
"[Path id=#{@id.to_s}, name=#{@guestName.to_s}, seat=#{@seat.to_s}]"
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
class Seating
|
148
|
-
def initialize(id,pid,path,leftSeat,leftGuestName,rightSeat,rightGuestName)
|
149
|
-
@id = id
|
150
|
-
@pid = pid
|
151
|
-
@path = path
|
152
|
-
@leftSeat = leftSeat
|
153
|
-
@leftGuestName = leftGuestName
|
154
|
-
@rightSeat = rightSeat
|
155
|
-
@rightGuestName = rightGuestName
|
156
|
-
end
|
157
|
-
attr :path, true
|
158
|
-
attr_reader :id,:pid,:leftSeat,:leftGuestName,:rightSeat,:rightGuestName
|
159
|
-
def to_s
|
160
|
-
return "[Seating id=#{@id.to_s} , pid=#{pid.to_s} , pathDone=#{@path.to_s} , leftSeat=#{@leftSeat.to_s}, leftGuestName=#{@leftGuestName.to_s}, rightSeat=#{@rightSeat.to_s}, rightGuestName=#{@rightGuestName.to_s}]";
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
class Sex
|
165
|
-
STRING_M = 'm'
|
166
|
-
STRING_F = 'f'
|
167
|
-
SEX_LIST = [ STRING_M, STRING_F ]
|
168
|
-
def initialize(sex)
|
169
|
-
@sex = sex
|
170
|
-
end
|
171
|
-
M = Sex.new( 0 )
|
172
|
-
F = Sex.new( 1 )
|
173
|
-
def sex
|
174
|
-
return SEX_LIST[sex]
|
175
|
-
end
|
176
|
-
def resolve(sex)
|
177
|
-
if STRING_M == sex
|
178
|
-
return M
|
179
|
-
elsif STRING_F == sex
|
180
|
-
return F
|
181
|
-
else
|
182
|
-
raise "Sex '#{@sex.to_s}' does not exist for Sex Enum"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
def to_s
|
186
|
-
return @sex.to_s
|
187
|
-
end
|
188
|
-
def to_hash
|
189
|
-
return @sex.to_hash
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
# This file is part of the Ruleby project (http://ruleby.org)
|
2
|
-
#
|
3
|
-
# This application is free software; you can redistribute it and/or
|
4
|
-
# modify it under the terms of the Ruby license defined in the
|
5
|
-
# LICENSE.txt file.
|
6
|
-
#
|
7
|
-
# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
|
8
|
-
#
|
9
|
-
# * Authors: Joe Kutner, Matt Smith
|
10
|
-
#
|
11
|
-
|
12
|
-
require 'model'
|
13
|
-
|
14
|
-
module MissManners
|
15
|
-
|
16
|
-
class MannersRulebook < Ruleby::Rulebook
|
17
|
-
def rules
|
18
|
-
rule :assignFirstSeat,
|
19
|
-
[Context,:context, m.state == Context::START_UP],
|
20
|
-
[Guest,:guest],
|
21
|
-
[Count,:count] do |e,vars|
|
22
|
-
guestName = vars[:guest].name
|
23
|
-
seating = Seating.new(vars[:count].value,1,true,1,guestName,1,guestName)
|
24
|
-
e.assert seating
|
25
|
-
path = Path.new(vars[:count].value, 1, guestName)
|
26
|
-
e.assert path
|
27
|
-
e.retract vars[:count]
|
28
|
-
vars[:count].value = vars[:count].value + 1
|
29
|
-
e.assert vars[:count]
|
30
|
-
puts "assign first seat : #{seating.to_s} : #{path.to_s}"
|
31
|
-
vars[:context].state = Context::ASSIGN_SEATS
|
32
|
-
e.modify vars[:context]
|
33
|
-
end
|
34
|
-
|
35
|
-
rule :findSeating,
|
36
|
-
[Context,:context, {m.state == Context::ASSIGN_SEATS => :state}],
|
37
|
-
[Count,:count, {m.value => :countValue}],
|
38
|
-
[Seating,:seating, {m.path == true =>:p, m.id=>:seatingId, m.pid=>:seatingPid, m.rightSeat=>:seatingRightSeat, m.rightGuestName=>:seatingRightGuestName}],
|
39
|
-
[Guest,:g, {m.name=>:name, m.sex=>:rightGuestSex, m.hobby=>:rightGuestHobby}, m.name == b(:seatingRightGuestName)],
|
40
|
-
[Guest,:lg, {m.name=>:leftGuestName, m.sex=>:sex, m.hobby == b(:rightGuestHobby) => :hobby}, m.sex(:rightGuestSex, &c{|s,rgs| s != rgs} )],
|
41
|
-
[:~,Path, m.id == b(:seatingId), m.guestName == b(:leftGuestName)],
|
42
|
-
[:~,Chosen, m.id == b(:seatingId), m.guestName == b(:leftGuestName), m.hobby == b(:leftGuestHobby)] do |e,vars|
|
43
|
-
rightSeat = vars[:seatingRightSeat]
|
44
|
-
seatId = vars[:seatingId]
|
45
|
-
countValue = vars[:count].value
|
46
|
-
seating = Seating.new(countValue,seatId,false,rightSeat,vars[:seatingRightGuestName],rightSeat+1,vars[:leftGuestName])
|
47
|
-
path = Path.new(countValue,rightSeat+1,vars[:leftGuestName])
|
48
|
-
chosen = Chosen.new(seatId, vars[:leftGuestName], vars[:rightGuestHobby] )
|
49
|
-
puts "find seating : #{seating} : #{path} : #{chosen}"
|
50
|
-
e.assert seating
|
51
|
-
e.assert path
|
52
|
-
e.assert chosen
|
53
|
-
vars[:count].value = countValue + 1
|
54
|
-
e.modify vars[:count]
|
55
|
-
vars[:context].state = Context::MAKE_PATH
|
56
|
-
e.modify vars[:context]
|
57
|
-
end
|
58
|
-
|
59
|
-
rule :makePath,
|
60
|
-
[Context,:context, {m.state == Context::MAKE_PATH => :s}],
|
61
|
-
[Seating,:seating, {m.id=>:seatingId, m.pid=>:seatingPid, m.path == false =>:p}],
|
62
|
-
[Path,:path, {m.guestName=>:pathGuestName, m.seat=>:pathSeat}, m.id == b(:seatingPid)],
|
63
|
-
[:~,Path,m.id == b(:seatingId), m.guestName == b(:pathGuestName)] do |e,vars|
|
64
|
-
path = Path.new(vars[:seatingId],vars[:pathSeat],vars[:pathGuestName])
|
65
|
-
e.assert path
|
66
|
-
puts "make Path : #{path}"
|
67
|
-
end
|
68
|
-
|
69
|
-
# NOTE We had to add this because Ruleby's conflict resolution strategy
|
70
|
-
# is not robust enough. If it worked correctly, this priority would not
|
71
|
-
# be nessecary because the 'make path' activations would have more
|
72
|
-
# recent facts supporting it.
|
73
|
-
rule :pathDone, {:priority => -5},
|
74
|
-
[Context,:context, m.state == Context::MAKE_PATH],
|
75
|
-
[Seating,:seating, m.path == false] do |e,vars|
|
76
|
-
vars[:seating].path = true
|
77
|
-
e.modify vars[:seating]
|
78
|
-
vars[:context].state = Context::CHECK_DONE
|
79
|
-
e.modify vars[:context]
|
80
|
-
puts "path Done : #{vars[:seating]}"
|
81
|
-
end
|
82
|
-
|
83
|
-
rule :areWeDone,
|
84
|
-
[Context,:context, m.state == Context::CHECK_DONE],
|
85
|
-
[LastSeat,:ls, {m.seat => :lastSeat}],
|
86
|
-
[Seating,:seating, m.rightSeat == b(:lastSeat)] do |e,vars|
|
87
|
-
vars[:context].state = Context::PRINT_RESULTS
|
88
|
-
e.modify vars[:context]
|
89
|
-
end
|
90
|
-
|
91
|
-
rule :continue, {:priority => -5},
|
92
|
-
[Context,:context,m.state == Context::CHECK_DONE] do |e,vars|
|
93
|
-
vars[:context].state = Context::ASSIGN_SEATS
|
94
|
-
e.modify vars[:context]
|
95
|
-
end
|
96
|
-
|
97
|
-
rule :allDone,
|
98
|
-
[Context,:context,m.state == Context::PRINT_RESULTS] do |e,vars|
|
99
|
-
puts 'All done'
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
data/benchmarks/model.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
# This file is part of the Ruleby project (http://ruleby.org)
|
2
|
-
#
|
3
|
-
# This application is free software; you can redistribute it and/or
|
4
|
-
# modify it under the terms of the Ruby license defined in the
|
5
|
-
# LICENSE.txt file.
|
6
|
-
#
|
7
|
-
# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
|
8
|
-
#
|
9
|
-
# * Authors: Joe Kutner, Matt Smith
|
10
|
-
#
|
11
|
-
|
12
|
-
class Account
|
13
|
-
def initialize(status, title, account_id)
|
14
|
-
@status = status
|
15
|
-
@title = title
|
16
|
-
@account_id = account_id
|
17
|
-
end
|
18
|
-
|
19
|
-
attr :status, true
|
20
|
-
attr :title, true
|
21
|
-
attr :account_id, true
|
22
|
-
end
|
23
|
-
|
24
|
-
class Address
|
25
|
-
def initialize(addr_id, city, state, zip)
|
26
|
-
@addr_id = addr_id
|
27
|
-
@city = city
|
28
|
-
@state = state
|
29
|
-
@zip = zip
|
30
|
-
end
|
31
|
-
|
32
|
-
attr :addr_id, true
|
33
|
-
attr :city, true
|
34
|
-
attr :state, true
|
35
|
-
attr :zip, true
|
36
|
-
end
|
@@ -1,117 +0,0 @@
|
|
1
|
-
# This file is part of the Ruleby project (http://ruleby.org)
|
2
|
-
#
|
3
|
-
# This application is free software; you can redistribute it and/or
|
4
|
-
# modify it under the terms of the Ruby license defined in the
|
5
|
-
# LICENSE.txt file.
|
6
|
-
#
|
7
|
-
# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
|
8
|
-
#
|
9
|
-
# * Authors: Joe Kutner, Matt Smith
|
10
|
-
#
|
11
|
-
|
12
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
|
13
|
-
require 'ruleby'
|
14
|
-
|
15
|
-
class Patient
|
16
|
-
def initialize(name,fever,spots,rash,sore_throat,innoculated)
|
17
|
-
@name = name
|
18
|
-
@fever = fever
|
19
|
-
@spots= spots
|
20
|
-
@rash = rash
|
21
|
-
@sore_throat = sore_throat
|
22
|
-
@innoculated = innoculated
|
23
|
-
end
|
24
|
-
attr:name, true
|
25
|
-
attr:fever, true
|
26
|
-
attr:spots, true
|
27
|
-
attr:rash, true
|
28
|
-
attr:sore_throat, true
|
29
|
-
attr:innoculated, true
|
30
|
-
end
|
31
|
-
|
32
|
-
class Diagnosis
|
33
|
-
def initialize(name,diagnosis)
|
34
|
-
@name=name
|
35
|
-
@diagnosis=diagnosis
|
36
|
-
end
|
37
|
-
attr:name,true
|
38
|
-
attr:diagnosis,true
|
39
|
-
end
|
40
|
-
|
41
|
-
class Treatment
|
42
|
-
def initialize(name,treatment)
|
43
|
-
@name=name
|
44
|
-
@treatment=treatment
|
45
|
-
end
|
46
|
-
attr:name,true
|
47
|
-
attr:treatment,true
|
48
|
-
end
|
49
|
-
|
50
|
-
class DiagnosisRulebook < Ruleby::Rulebook
|
51
|
-
def rules
|
52
|
-
rule :Measles, {:priority => 100},
|
53
|
-
[Patient,:p,{m.name=>:n},m.fever==:high,m.spots==true,m.innoculated==true] do |e,v|
|
54
|
-
name = v[:n]
|
55
|
-
e.assert Diagnosis.new(name, :measles)
|
56
|
-
puts "Measles diagnosed for #{name}"
|
57
|
-
end
|
58
|
-
|
59
|
-
rule :Allergy1,
|
60
|
-
[Patient,:p, {m.name=>:n}, m.spots==true],
|
61
|
-
[:not, Diagnosis, m.name==b(:n), m.diagnosis==:measles] do |e,v|
|
62
|
-
name = v[:n]
|
63
|
-
e.assert Diagnosis.new(name, :allergy)
|
64
|
-
puts "Allergy diagnosed for #{name} from spots and lack of measles"
|
65
|
-
end
|
66
|
-
|
67
|
-
rule :Allergy2,
|
68
|
-
[Patient,:p, {m.name=>:n}, m.rash==true] do |e,v|
|
69
|
-
name = v[:n]
|
70
|
-
e.assert Diagnosis.new(name, :allergy)
|
71
|
-
puts "Allergy diagnosed from rash for #{name}"
|
72
|
-
end
|
73
|
-
|
74
|
-
rule :Flu,
|
75
|
-
[Patient,:p, {m.name=>:n}, m.sore_throat==true, m.fever(&c{|f| f==:mild || f==:high})] do |e,v|
|
76
|
-
name = v[:n]
|
77
|
-
e.assert Diagnosis.new(name, :flu)
|
78
|
-
puts "Flu diagnosed for #{name}"
|
79
|
-
end
|
80
|
-
|
81
|
-
rule :Penicillin,
|
82
|
-
[Diagnosis, :d, {m.name => :n}, m.diagnosis==:measles] do |e,v|
|
83
|
-
name = v[:n]
|
84
|
-
e.assert Treatment.new(name, :penicillin)
|
85
|
-
puts "Penicillin prescribed for #{name}"
|
86
|
-
end
|
87
|
-
|
88
|
-
rule :Allergy_pills,
|
89
|
-
[Diagnosis, :d, {m.name => :n}, m.diagnosis==:allergy] do |e,v|
|
90
|
-
name = v[:n]
|
91
|
-
e.assert Treatment.new(name, :allergy_shot)
|
92
|
-
puts "Allergy shot prescribed for #{name}"
|
93
|
-
end
|
94
|
-
|
95
|
-
rule :Bed_rest,
|
96
|
-
[Diagnosis, :d, {m.name => :n}, m.diagnosis==:flu] do |e,v|
|
97
|
-
name = v[:n]
|
98
|
-
e.assert Treatment.new(name, :bed_rest)
|
99
|
-
puts "Bed rest prescribed for #{name}"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
include Ruleby
|
105
|
-
|
106
|
-
engine :engine do |e|
|
107
|
-
|
108
|
-
DiagnosisRulebook.new(e).rules
|
109
|
-
|
110
|
-
e.assert Patient.new('Fred',:none,true,false,false,false)
|
111
|
-
e.assert Patient.new('Joe',:high,false,false,true,false)
|
112
|
-
e.assert Patient.new('Bob',:high,true,false,false,true)
|
113
|
-
e.assert Patient.new('Tom',:none,false,true,false,false)
|
114
|
-
|
115
|
-
e.match
|
116
|
-
|
117
|
-
end
|