ruby-prolog 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ == 0.0.4 / 2009-02-09
2
+
3
+ * Fixing packaging error from previous build.
4
+
5
+ == 0.0.3 / 2009-02-09
6
+
7
+ * Renaming files due to RubyForge stupidity.
8
+ * Adding usage examples.
9
+
10
+ == 0.0.2 / 2009-02-04
11
+
12
+ * Refactored the code into an object-oriented state.
13
+ * Changed #query to not print results and return goals.
14
+ * Made spec tests more meaningful.
15
+ * Addressed small TODO items.
16
+
17
+ == 0.0.1 / 2009-01-14
18
+
19
+ * First release. Basically a glorified packaging of tiny_prolog and various extensions.
@@ -0,0 +1,10 @@
1
+ examples/acls.rb
2
+ examples/hanoi.rb
3
+ History.txt
4
+ lib/ruby-prolog/ruby-prolog.rb
5
+ lib/ruby-prolog.rb
6
+ Manifest
7
+ Rakefile
8
+ README.txt
9
+ spec/ruby-prolog_spec.rb
10
+ spec/spec_helper.rb
@@ -0,0 +1,57 @@
1
+ ruby_prolog
2
+ by Preston Lee
3
+ http://openrain.com
4
+ The core engine is largely based on tiny_prolog, though numerous additional enhancements have been made
5
+ such as object-oriented refactorings and integration of ideas from the interwebs. Unfortunately I cannot
6
+ read Japanese and cannot give proper attribution to the original tiny_prolog author. If *you* can, let
7
+ me know and I'll update this document!
8
+
9
+ == DESCRIPTION:
10
+
11
+ An object-oriented pure Ruby implementation of a Prolog-like DSL for easy AI and logical programming.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Pure Ruby.
16
+ * Tested with Ruby 1.8.7 (MRI).
17
+ * Object-oriented.
18
+ * Multiple Prolog environments can be created and manipulated simultaneously.
19
+ * Concurrent access to different core instances should be safe.
20
+ * Concurrent access to a single core instance might probably explode in odd ways.
21
+
22
+ == SYNOPSIS:
23
+
24
+ See ruby_prolog_spec.rb for usage examples.
25
+
26
+ == REQUIREMENTS:
27
+
28
+ * Should work under all popular Ruby interpreters. Please report compatibility problems.
29
+
30
+ == INSTALL:
31
+
32
+ * sudo gem install ruby_prolog
33
+
34
+ == LICENSE:
35
+
36
+ (The MIT License)
37
+
38
+ Copyright (c) 2008 OpenRain, LLC
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining
41
+ a copy of this software and associated documentation files (the
42
+ 'Software'), to deal in the Software without restriction, including
43
+ without limitation the rights to use, copy, modify, merge, publish,
44
+ distribute, sublicense, and/or sell copies of the Software, and to
45
+ permit persons to whom the Software is furnished to do so, subject to
46
+ the following conditions:
47
+
48
+ The above copyright notice and this permission notice shall be
49
+ included in all copies or substantial portions of the Software.
50
+
51
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
52
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
53
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
54
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
55
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
56
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
57
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,10 @@
1
+ require 'echoe'
2
+ Echoe.new("ruby-prolog") do |p|
3
+ p.author = "Preston Lee"
4
+ p.summary = "A Prolog-ish Ruby DSL."
5
+ p.url = "http://www.openrain.com.com"
6
+ # p.docs_host = "uncapitalizer.com:~/www/files/doc/"
7
+ # p.runtime_dependencies = ["string_tools >=1.4.0"]
8
+ p.version = '0.0.5'
9
+ p.email = "preston.lee@openrain.com"
10
+ end
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'ruby-prolog'
3
+
4
+ c = RubyProlog::Core.new
5
+ c.instance_eval do
6
+
7
+ # Let's put together an issue tracking system for Microsoft.
8
+
9
+ # We'll start by declaring a few projects...
10
+ project_status['me', 'live'].fact
11
+ project_status['xp', 'live'].fact
12
+ project_status['vista', 'live'].fact
13
+ project_status['7', 'in_progress'].fact
14
+
15
+
16
+ # Now we'll create a custom ACL system...
17
+ role_can['user', 'create'].fact
18
+ role_can['user', 'read'].fact
19
+
20
+ role_can['qa', 'create'].fact
21
+ role_can['qa', 'read'].fact
22
+ role_can['qa', 'update'].fact
23
+
24
+ role_can['admin', 'create'].fact
25
+ role_can['admin', 'read'].fact
26
+ role_can['admin', 'update'].fact
27
+ role_can['admin', 'delete'].fact
28
+
29
+
30
+ # Let's put people on different projects
31
+ assigned['alice', 'me', 'user'].fact
32
+ assigned['bob', 'me', 'qa'].fact
33
+ assigned['charlie', 'me', 'qa'].fact
34
+
35
+ assigned['alice', 'xp', 'user'].fact
36
+ assigned['bob', 'xp', 'user'].fact
37
+ assigned['charlie', 'xp', 'admin'].fact
38
+
39
+ assigned['alice', 'vista', 'qa'].fact
40
+ assigned['bob', 'vista', 'admin'].fact
41
+ assigned['charlie', 'vista', 'admin'].fact
42
+
43
+ assigned['alice', '7', 'user'].fact
44
+ assigned['bob', '7', 'qa'].fact
45
+ assigned['charlie', '7', 'qa'].fact
46
+ assigned['dale', '7', 'admin'].fact
47
+
48
+
49
+ # can_read_on_project[:U, :P] <<= [assigned[:U, :P, :R], role_can[:R, 'read']]
50
+ can_on_project[:U, :X, :P] <<= [assigned[:U, :P, :R], role_can[:R, :X]]
51
+ is_role_on_multiple_projects[:U, :R] <<= [assigned[:U, :X, :R], assigned[:U, :Y, :R], noteq[:X, :Y]]
52
+ # , noteq[:P1, :P2]
53
+
54
+ puts 'Who does QA?'
55
+ p query(assigned[:U, :P, 'qa'])
56
+
57
+ puts "Who can access the 'vista' project?"
58
+ p query(can_on_project[:U, 'read', 'vista'])
59
+
60
+ puts "Does Alice have delete privileges on Vista?"
61
+ puts query(can_on_project['alice', 'delete', 'vista']).empty? ? "Yes" : "No"
62
+
63
+ puts "Does Bob have delete privileges on Vista?"
64
+ puts query(can_on_project['bob', 'delete', 'vista']).empty? ? "Yes" : "No"
65
+
66
+ puts "Who is an admin on multiple projects?"
67
+ # p query(is_role_on_multiple_projects[:U, 'admin'])
68
+
69
+ s = Array.new
70
+ query(is_role_on_multiple_projects[:U, 'admin']).each do |r|
71
+ s |= [r[0].args[0]] # Put each result into the array, if not already present.
72
+ end
73
+ s.each do |n| puts n end # Print all unique results!
74
+
75
+
76
+
77
+ end
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'ruby-prolog'
3
+
4
+ # require File.join(File.dirname(__FILE__), %w[.. lib ruby-prolog])
5
+
6
+ # Inspired by..
7
+ # http://www.csupomona.edu/~jrfisher/www/prolog_tutorial/2_3.html
8
+ # http://eigenclass.org/hiki.rb?tiny+prolog+in+ruby
9
+
10
+ c = RubyProlog::Core.new
11
+ c.instance_eval do
12
+
13
+ move[0,:X,:Y,:Z] <<= :CUT # There are no more moves left
14
+ move[:N,:A,:B,:C] <<= [
15
+ is(:M,:N){|n| n - 1}, # reads as "M IS N - 1"
16
+ move[:M,:A,:C,:B],
17
+ write_info[:A,:B],
18
+ move[:M,:C,:B,:A]
19
+ ]
20
+ write_info[:X,:Y] <<= [
21
+ write["move a disc from the "],
22
+ write[:X], write[" pole to the "],
23
+ write[:Y], writenl[" pole "]
24
+ ]
25
+
26
+ hanoi[:N] <<= move[:N,"left","right","center"]
27
+
28
+ puts "\nWhat's the solution for a single disc?"
29
+ query(hanoi[1])
30
+
31
+ puts "\n\nWhat's the solution for 5 discs?"
32
+ query(hanoi[5])
33
+
34
+ # do_stuff[:STUFF].calls{|env| print env[:STUFF]; true}
35
+
36
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module RubyProlog
3
+
4
+ # :stopdoc:
5
+ VERSION = '0.0.5'
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ # :startdoc:
9
+
10
+ # Returns the version string for the library.
11
+ #
12
+ def self.version
13
+ VERSION
14
+ end
15
+
16
+ # Returns the library path for the module. If any arguments are given,
17
+ # they will be joined to the end of the libray path using
18
+ # <tt>File.join</tt>.
19
+ #
20
+ def self.libpath( *args )
21
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
22
+ end
23
+
24
+ # Returns the lpath for the module. If any arguments are given,
25
+ # they will be joined to the end of the path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.path( *args )
29
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
30
+ end
31
+
32
+ # Utility method used to rquire all files ending in .rb that lie in the
33
+ # directory below this file that has the same name as the filename passed
34
+ # in. Optionally, a specific _directory_ name can be passed in such that
35
+ # the _filename_ does not have to be equivalent to the directory.
36
+ #
37
+ def self.require_all_libs_relative_to( fname, dir = nil )
38
+ dir ||= ::File.basename(fname, '.*')
39
+ search_me = ::File.expand_path(
40
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
41
+
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+
45
+ end # module RubyProlog
46
+
47
+ RubyProlog.require_all_libs_relative_to(__FILE__)
48
+
49
+ # EOF
@@ -0,0 +1,366 @@
1
+ # Based on tiny_prolog h18.9/8
2
+
3
+ module RubyProlog
4
+
5
+
6
+ class Predicate
7
+
8
+ attr_reader :defs
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ @defs = []
13
+ end
14
+
15
+ def inspect
16
+ return @name.to_s
17
+ end
18
+
19
+ def [](*args)
20
+ return Goal.new(self, args)
21
+ end
22
+
23
+ def []=(*a); end
24
+
25
+ end
26
+
27
+
28
+ class Goal
29
+
30
+ attr_reader :pred, :args
31
+
32
+ def list(*x)
33
+ y = nil
34
+ x.reverse_each {|e| y = Cons.new(e, y)}
35
+ return y
36
+ end
37
+
38
+ def initialize(pred, args)
39
+ @pred, @args = pred, args
40
+ end
41
+
42
+ def si(*rhs) # ラテン語の「もしも」
43
+ @pred.defs << [self, list(*rhs)]
44
+ end
45
+
46
+ def fact
47
+ si
48
+ end
49
+
50
+ def <<(rhs)
51
+ case rhs
52
+ when Array
53
+ si(*rhs)
54
+ else
55
+ si(rhs)
56
+ end
57
+ end
58
+
59
+ def calls(&callback)
60
+ @pred.defs << [self, callback]
61
+ end
62
+
63
+ def inspect
64
+ return @pred.inspect.to_s + @args.inspect.to_s
65
+ end
66
+
67
+ end
68
+
69
+
70
+ # Lisp のリスト風の二項組
71
+ class Cons < Array
72
+
73
+ def initialize(car, cdr)
74
+ super(2)
75
+ self[0], self[1] = car, cdr
76
+ end
77
+
78
+ def inspect
79
+ repr = proc {|x|
80
+ car, cdr = x[0], x[1]
81
+ if cdr.nil? then [car.inspect]
82
+ elsif Cons === cdr then repr[cdr].unshift(car.inspect)
83
+ else [car.inspect, '.', cdr.inspect]
84
+ end
85
+ }
86
+ return '(' + repr[self].join(' ') + ')'
87
+ end
88
+
89
+ end
90
+
91
+
92
+
93
+ class Environment
94
+
95
+ def initialize
96
+ @table = {}
97
+ end
98
+
99
+ def put(x, pair)
100
+ @table[x] = pair
101
+ end
102
+
103
+ def get(x)
104
+ return @table[x]
105
+ end
106
+
107
+ def delete(x)
108
+ @table.delete(x) {|k| raise "#{k} not found in #{inspect}"}
109
+ end
110
+
111
+ def clear
112
+ @table.clear
113
+ end
114
+
115
+ def dereference(t)
116
+ env = self
117
+ while Symbol === t
118
+ p = env.get(t)
119
+ break if p.nil?
120
+ t, env = p
121
+ end
122
+ return [t, env]
123
+ end
124
+
125
+ def [](t)
126
+ t, env = dereference(t)
127
+ return case t
128
+ when Goal then Goal.new(t.pred, env[t.args])
129
+ when Cons then Cons.new(env[t[0]], env[t[1]])
130
+ when Array then t.collect {|e| env[e]}
131
+ else t
132
+ end
133
+ end
134
+
135
+
136
+ end
137
+
138
+
139
+ class CallbackEnvironment
140
+
141
+ def initialize(env, trail, core)
142
+ @env, @trail, @core = env, trail, core
143
+ end
144
+
145
+ def [](t)
146
+ return @env[t]
147
+ end
148
+
149
+ def unify(t, u)
150
+ # pp "CORE " + @core
151
+ return @core._unify(t, @env, u, @env, @trail, @env)
152
+ end
153
+
154
+ end
155
+
156
+
157
+ class Core
158
+
159
+ def _unify(x, x_env, y, y_env, trail, tmp_env)
160
+
161
+ loop {
162
+ if Symbol === x
163
+ xp = x_env.get(x)
164
+ if xp.nil?
165
+ y, y_env = y_env.dereference(y)
166
+ unless x == y and x_env == y_env
167
+ x_env.put(x, [y, y_env])
168
+ trail << [x, x_env] unless x_env == tmp_env
169
+ end
170
+ return true
171
+ else
172
+ x, x_env = xp
173
+ x, x_env = x_env.dereference(x)
174
+ end
175
+ elsif Symbol === y
176
+ x, x_env, y, y_env = y, y_env, x, x_env
177
+ else
178
+ break
179
+ end
180
+ }
181
+
182
+ if Goal === x and Goal === y
183
+ return false unless x.pred == y.pred
184
+ x, y = x.args, y.args
185
+ end
186
+
187
+ if Array === x and Array === y
188
+ return false unless x.length == y.length
189
+ for i in 0 ... x.length # x.each_index do |i| も可
190
+ return false unless _unify(x[i], x_env, y[i], y_env, trail, tmp_env)
191
+ end
192
+ return true
193
+ else
194
+ return x == y
195
+ end
196
+
197
+ end
198
+
199
+
200
+ def list(*x)
201
+ y = nil
202
+ x.reverse_each {|e| y = Cons.new(e, y)}
203
+ return y
204
+ end
205
+
206
+
207
+ def resolve(*goals)
208
+ env = Environment.new
209
+ _resolve_body(list(*goals), env, [false]) {
210
+ yield env
211
+ }
212
+ end
213
+
214
+
215
+ def _resolve_body(body, env, cut)
216
+ if body.nil?
217
+ yield
218
+ else
219
+ goal, rest = body
220
+ if goal == :CUT
221
+ _resolve_body(rest, env, cut) {
222
+ yield
223
+ }
224
+ cut[0] = true
225
+ else
226
+ d_env = Environment.new
227
+ d_cut = [false]
228
+ require 'pp'
229
+ # pp 'G ' + goal.class.to_s
230
+ # pp goal.pred
231
+ for d_head, d_body in goal.pred.defs
232
+ # for d_head, d_body in goal.defs
233
+ break if d_cut[0] or cut[0]
234
+ trail = []
235
+ if _unify_(goal, env, d_head, d_env, trail, d_env)
236
+ if Proc === d_body
237
+ if d_body[CallbackEnvironment.new(d_env, trail, self)]
238
+ _resolve_body(rest, env, cut) {
239
+ yield
240
+ }
241
+ end
242
+ else
243
+ _resolve_body(d_body, d_env, d_cut) {
244
+ _resolve_body(rest, env, cut) {
245
+ yield
246
+ }
247
+ d_cut[0] ||= cut[0]
248
+ }
249
+ end
250
+ end
251
+ for x, x_env in trail
252
+ x_env.delete(x)
253
+ end
254
+ d_env.clear
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+
261
+ $_trace = false
262
+ def trace(flag)
263
+ $_trace = flag
264
+ end
265
+
266
+
267
+ def _unify_(x, x_env, y, y_env, trail, tmp_env)
268
+ lhs, rhs = x_env[x].inspect, y.inspect if $_trace
269
+ unified = _unify(x, x_env, y, y_env, trail, tmp_env)
270
+ printf("\t%s %s %s\n", lhs, (unified ? "~" : "!~"), rhs) if $_trace
271
+ return unified
272
+ end
273
+
274
+
275
+ def query(*goals)
276
+ count = 0
277
+ results = Array.new
278
+ # printout = proc {|x|
279
+ # x = x[0] if x.length == 1
280
+ # printf "%d %s\n", count, x.inspect
281
+ # }
282
+ resolve(*goals) {|env|
283
+ count += 1
284
+ results << env[goals]
285
+ # printout[env[goals]]
286
+ }
287
+ # printout[goals] if count == 0
288
+ return results
289
+ end
290
+
291
+
292
+ def is(*syms,&block)
293
+ $is_cnt ||= 0
294
+ is = Predicate.new "IS_#{$is_cnt += 1}"
295
+ raise "At least one symbol needed" unless syms.size > 0
296
+ is[*syms].calls do |env|
297
+ value = block.call(*syms[1..-1].map{|x| env[x]})
298
+ env.unify(syms.first, value)
299
+ end
300
+ is[*syms]
301
+ end
302
+
303
+ def method_missing(meth, *args)
304
+ # puts "NEW PRED #{meth} #{meth.class}"
305
+ pred = Predicate.new(meth)
306
+ # proc = Proc.new {pred}
307
+
308
+
309
+ # We only want to define the method on this specific object instance to avoid polluting global namespaces.
310
+
311
+ # You can't do this..
312
+ # class << self
313
+ # module_eval do
314
+ # send(:define_method, m, proc)
315
+ # end
316
+ # end
317
+
318
+ # Nor this..
319
+ # define_method(meth) {pred}
320
+
321
+ # Nor this..
322
+ # self.send(:define_method, meth, proc)
323
+
324
+ # And you don't want to pollute the global namespace like this...
325
+ # Object.class_eval{ define_method(meth){pr} }
326
+
327
+
328
+ # Sooooo... I know this doesn't really make intuitive sense,
329
+ # but you need to get the eigenclass and then define
330
+ # the method within that context in such a way that we
331
+ # have access to local variables, like this...
332
+ class << self; self; end.module_eval do
333
+ define_method meth, Proc.new{pred}
334
+ end
335
+ # ...which is major fuglytown, but I don't know how to do it any other way.
336
+
337
+ return pred
338
+ end
339
+
340
+
341
+ def initialize
342
+ # We do not need to predefine predicates like this because they will automatically be defined for us.
343
+ # write = Predicate.new "write"
344
+ write[:X].calls{|env| print env[:X]; true}
345
+ writenl[:X].calls{|env| puts env[:X]; true}
346
+ nl[:X].calls{|e| puts; true}
347
+ eq[:X,:Y].calls{|env| env.unify(env[:X], env[:Y])}
348
+ noteq[:X,:Y].calls{|env| env[:X] != env[:Y]}
349
+ atomic[:X].calls do |env|
350
+ case env[:X]
351
+ when Symbol, Predicate, Goal; false
352
+ else true
353
+ end
354
+ end
355
+ notatomic[:X].calls do |env|
356
+ case env[:X]
357
+ when Symbol, Predicate, Goal; true
358
+ else false
359
+ end
360
+ end
361
+ numeric[:X].calls{|env| Numeric === env[:X] }
362
+ end
363
+
364
+ end
365
+
366
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ruby-prolog}
5
+ s.version = "0.0.5"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Preston Lee"]
9
+ s.date = %q{2009-02-09}
10
+ s.description = %q{A Prolog-ish Ruby DSL.}
11
+ s.email = %q{preston.lee@openrain.com}
12
+ s.extra_rdoc_files = ["lib/ruby-prolog/ruby-prolog.rb", "lib/ruby-prolog.rb", "README.txt"]
13
+ s.files = ["examples/acls.rb", "examples/hanoi.rb", "History.txt", "lib/ruby-prolog/ruby-prolog.rb", "lib/ruby-prolog.rb", "Manifest", "Rakefile", "README.txt", "spec/ruby-prolog_spec.rb", "spec/spec_helper.rb", "ruby-prolog.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://www.openrain.com.com}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ruby-prolog", "--main", "README.txt"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{ruby-prolog}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{A Prolog-ish Ruby DSL.}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
@@ -0,0 +1,222 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+
5
+ describe RubyProlog do
6
+
7
+
8
+ # before :each do
9
+ # end
10
+
11
+
12
+ it 'should not pollute the global namespace with predicates.' do
13
+
14
+ # We'll create numerous instances of the engine and assert they do not interfere with each other.
15
+ one = RubyProlog::Core.new
16
+ one.instance_eval do
17
+ query(male[:X]).length.should == 0
18
+ end
19
+
20
+ two = RubyProlog::Core.new
21
+ two.instance_eval do
22
+ male[:preston].fact
23
+ query(male[:X]).length.should == 1
24
+ end
25
+
26
+ three = RubyProlog::Core.new
27
+ three.instance_eval do
28
+ query(male[:X]).length.should == 0
29
+ end
30
+
31
+ one.instance_eval do
32
+ query(male[:X]).length.should == 0
33
+ end
34
+
35
+ end
36
+
37
+
38
+
39
+ it 'should be able to query simple family trees.' do
40
+
41
+ c = RubyProlog::Core.new
42
+ c.instance_eval do
43
+ # Basic family tree relationships..
44
+ sibling[:X,:Y] <<= [ parent[:Z,:X], parent[:Z,:Y], noteq[:X,:Y] ]
45
+ mother[:X,:Y] <<= [parent[:X, :Y], female[:X]]
46
+ father[:X,:Y] <<= [parent[:X, :Y], male[:X]]
47
+ grandparent[:G,:C] <<= [ parent[:G,:P], parent[:P,:C]]
48
+ ancestor[:A, :C] <<= [parent[:A, :X], parent[:X, :B]]
49
+ mothers[:M, :C] <<= mother[:M, :C]
50
+ mothers[:M, :C] <<= [mother[:M, :X], mothers[:X, :C]]
51
+ fathers[:F, :C] <<= father[:F, :C]
52
+ fathers[:F, :C] <<= [father[:F, :X], fathers[:X, :C]]
53
+ widower[:W] <<= [married[:W, :X], deceased[:X], nl[deceased[:W]]]
54
+ widower[:W] <<= [married[:X, :W], deceased[:X], nl[deceased[:W]]]
55
+
56
+ # Basic parents relationships as could be stored in a typical relational database.
57
+ parent['Ms. Old', 'Marge'].fact
58
+
59
+ parent['Carol', 'Ron'].fact
60
+ parent['Kent', 'Ron'].fact
61
+ parent['Marge', 'Marcia'].fact
62
+ parent['Pappy', 'Marcia'].fact
63
+
64
+ parent['Marcia', 'Karen'].fact
65
+ parent['Marcia', 'Julie'].fact
66
+ parent['Ron', 'Karen'].fact
67
+ parent['Ron', 'Julie'].fact
68
+
69
+ parent['Matt', 'Silas'].fact
70
+ parent['Julie', 'Silas'].fact
71
+ parent['Preston', 'Cirrus'].fact # Technically our dog.. but whatever :)
72
+ parent['Karen', 'Cirrus'].fact
73
+
74
+
75
+ # Gender facts..
76
+ male['Preston'].fact
77
+ male['Kent'].fact
78
+ male['Pappy'].fact
79
+ male['Ron'].fact
80
+ male['Matt'].fact
81
+ female['Ms. Old'].fact
82
+ female['Carol'].fact
83
+ female['Marge'].fact
84
+ female['Marcia'].fact
85
+ female['Julie'].fact
86
+ female['Karen'].fact
87
+
88
+
89
+ # People die :(
90
+ deceased['Pappy'].fact
91
+
92
+
93
+ # Let's marry some people..
94
+ married['Carol', 'Kent'].fact
95
+ married['Marge', 'Pappy'].fact
96
+ married['Ron', 'Marcia'].fact
97
+ married['Matt', 'Julie'].fact
98
+ married['Preston', 'Karen'].fact
99
+
100
+
101
+ # And add some facts on personal interests..
102
+ interest['Karen', 'Music'].fact
103
+ interest['Karen', 'Movies'].fact
104
+ interest['Karen', 'Games'].fact
105
+ interest['Karen', 'Walks'].fact
106
+ interest['Preston', 'Music'].fact
107
+ interest['Preston', 'Movies'].fact
108
+ interest['Preston', 'Games'].fact
109
+
110
+ interest['Silas', 'Games'].fact
111
+ interest['Cirrus', 'Games'].fact
112
+ interest['Karen', 'Walks'].fact
113
+ interest['Ron', 'Walks'].fact
114
+ interest['Marcia', 'Walks'].fact
115
+
116
+ # Runs some queries..
117
+
118
+ # p "Who are Silas's parents?"
119
+ # Silas should have two parents: Matt and Julie.
120
+ r = query(parent[:P, 'Silas'])
121
+ r.length.should == 2
122
+ r[0][0].args[0].should == 'Matt'
123
+ r[1][0].args[0].should == 'Julie'
124
+
125
+ # p "Who is married?"
126
+ # We defined 5 married facts.
127
+ query(married[:A, :B]).length.should == 5
128
+
129
+ # p 'Are Karen and Julie siblings?'
130
+ # Yes, through two parents.
131
+ query(sibling['Karen', 'Julie']).length.should == 2
132
+
133
+
134
+ # p "Who likes to play games?"
135
+ # Four people.
136
+ query(interest[:X, 'Games']).length.should == 4
137
+
138
+
139
+ # p "Who likes to play checkers?"
140
+ # Nobody.
141
+ query(interest[:X, 'Checkers']).length.should == 0
142
+
143
+ # p "Who are Karen's ancestors?"
144
+ # query(ancestor[:A, 'Karen'])
145
+
146
+ # p "What grandparents are also widowers?"
147
+ # Marge, twice, because of two grandchildren.
148
+ query(widower[:X], grandparent[:X, :G]).length.should == 2
149
+ end
150
+
151
+ end
152
+
153
+
154
+ it 'should be able to query simple family trees.' do
155
+
156
+ c = RubyProlog::Core.new
157
+ c.instance_eval do
158
+
159
+ vendor['dell'].fact
160
+ vendor['apple'].fact
161
+
162
+ model['ultrasharp'].fact
163
+ model['xps'].fact
164
+ model['macbook'].fact
165
+ model['iphone'].fact
166
+
167
+ manufactures['dell', 'ultrasharp'].fact
168
+ manufactures['dell', 'xps'].fact
169
+ manufactures['apple', 'macbook'].fact
170
+ manufactures['apple', 'iphone'].fact
171
+
172
+ is_a['xps', 'laptop'].fact
173
+ is_a['macbook', 'laptop'].fact
174
+ is_a['ultrasharp', 'monitor'].fact
175
+ is_a['iphone', 'phone'].fact
176
+
177
+ kind['laptop']
178
+ kind['monitor']
179
+ kind['phone']
180
+
181
+ model[:M] <<= [manfactures[:V, :M]]
182
+
183
+ vendor_of[:V, :K] <<= [vendor[:V], manufactures[:V, :M], is_a[:M, :K]]
184
+ # not_vendor_of[:V, :K] <<= [vendor[:V], nl[vendor_of[:V, :K]]]
185
+
186
+ query(is_a[:K, 'laptop']).length == 2
187
+ query(vendor_of[:V, 'phone']) == 1
188
+ # pp query(not_vendor_of[:V, 'phone'])
189
+ end
190
+
191
+ end
192
+
193
+
194
+ it 'should solve the Towers of Hanoi problem.' do
195
+ c = RubyProlog::Core.new
196
+ c.instance_eval do
197
+
198
+ move[0,:X,:Y,:Z] <<= :CUT # There are no more moves left
199
+ move[:N,:A,:B,:C] <<= [
200
+ is(:M,:N){|n| n - 1}, # reads as "M IS N - 1"
201
+ move[:M,:A,:C,:B],
202
+ # write_info[:A,:B],
203
+ move[:M,:C,:B,:A]
204
+ ]
205
+ write_info[:X,:Y] <<= [
206
+ # write["move a disc from the "],
207
+ # write[:X], write[" pole to the "],
208
+ # write[:Y], writenl[" pole "]
209
+ ]
210
+
211
+ hanoi[:N] <<= move[:N,"left","right","center"]
212
+ query(hanoi[5]).length.should == 1
213
+
214
+ # do_stuff[:STUFF].calls{|env| print env[:STUFF]; true}
215
+
216
+ end
217
+
218
+ end
219
+
220
+
221
+
222
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib ruby-prolog]))
4
+
5
+ Spec::Runner.configure do |config|
6
+ # == Mock Framework
7
+ #
8
+ # RSpec uses it's own mocking framework by default. If you prefer to
9
+ # use mocha, flexmock or RR, uncomment the appropriate line:
10
+ #
11
+ # config.mock_with :mocha
12
+ # config.mock_with :flexmock
13
+ # config.mock_with :rr
14
+ end
15
+
16
+ # EOF
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-prolog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Preston Lee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Prolog-ish Ruby DSL.
17
+ email: preston.lee@openrain.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - lib/ruby-prolog/ruby-prolog.rb
24
+ - lib/ruby-prolog.rb
25
+ - README.txt
26
+ files:
27
+ - examples/acls.rb
28
+ - examples/hanoi.rb
29
+ - History.txt
30
+ - lib/ruby-prolog/ruby-prolog.rb
31
+ - lib/ruby-prolog.rb
32
+ - Manifest
33
+ - Rakefile
34
+ - README.txt
35
+ - spec/ruby-prolog_spec.rb
36
+ - spec/spec_helper.rb
37
+ - ruby-prolog.gemspec
38
+ has_rdoc: true
39
+ homepage: http://www.openrain.com.com
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --line-numbers
43
+ - --inline-source
44
+ - --title
45
+ - Ruby-prolog
46
+ - --main
47
+ - README.txt
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "1.2"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: ruby-prolog
65
+ rubygems_version: 1.3.1
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: A Prolog-ish Ruby DSL.
69
+ test_files: []
70
+