ruby-prolog 0.0.5

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.
@@ -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
+