ruby-prolog 1.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile +5 -1
- data/README.md +104 -20
- data/bin/ruby-prolog-acls +4 -4
- data/bin/ruby-prolog-hanoi +5 -5
- data/lib/ruby-prolog/ruby-prolog.rb +224 -93
- data/lib/ruby-prolog/version.rb +1 -1
- data/ruby-prolog.gemspec +6 -5
- data/test/lib/ruby-prolog/ruby-prolog_test.rb +193 -101
- data/test/test_helper.rb +1 -0
- metadata +34 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ffa39b815b60de57f1865b1c5b2abd00345623c2d90a81c4185f5800bfe957ca
|
4
|
+
data.tar.gz: 2b279f1a9c1e6cfb9320de5c52a6ee484f47e62f1001a58d3bd17c56ff9fca57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6efaec42888b2749232637261b933e3a07c0a889b4ebc73995b7252e02048438b74b11d9c55a24e2687ecb0581e6d3c6d1efaad43444e3fa4601b5bc9165c432
|
7
|
+
data.tar.gz: f73024546d7bfd77b39fbd738bd06eacb0b1b3f025309fc55b3a43e332e7b2b8c8ab714db399886a62003a1b261ceab2ed65ee5d7adff9ac3cb267a399a864de
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,50 +1,134 @@
|
|
1
|
-
|
1
|
+
ruby-prolog
|
2
2
|
====
|
3
3
|
|
4
|
-
|
4
|
+
ruby-prolog allows you to solve complex logic problems on the fly using a dynamic, Prolog-like DSL inline with your normal Ruby code. Basic use is encompassed by stating basic facts using your data, defining rules, and then asking questions. Why is this cool? Because ruby-prolog allows you to leave your normal object-oriented vortex on demand and step into the alternate reality of declarative languages.
|
5
5
|
|
6
|
-
|
7
|
-
such as object-oriented refactorings and integration of ideas from the interwebs. Unfortunately I cannot
|
8
|
-
read Japanese and cannot give proper attribution to the original tiny_prolog author. (If *you* can, let
|
9
|
-
me know and I'll update this document!)
|
6
|
+
With ruby-prolog:
|
10
7
|
|
8
|
+
* There are no classes.
|
9
|
+
* There are no functions.
|
10
|
+
* There are no variables.
|
11
|
+
* There are no control flow statements.
|
12
|
+
|
13
|
+
You *can* use all these wonder things -- it’s still Ruby after all -- but they’re not needed, and mainly useful for getting data and results into/out of the interpreter. Prolog still tends to be favored heavily in artificial intelligence and theorem proving applications and is still relevant to computer science curricula as well, so I hope this updated release proves useful for your logic evaluation needs!
|
14
|
+
|
15
|
+
ruby-prolog is written using object-oriented-ish pure Ruby, and should work under all most popular Ruby interpreters. Please report compatibility problems. The core engine is largely based on tiny_prolog, though numerous additional enhancements have been made such as object-oriented refactorings and integration of ideas from the interwebs. Unfortunately I cannot read Japanese and cannot give proper attribution to the original tiny_prolog author. (If *you* can, let me know and I'll update this document!)
|
11
16
|
|
12
17
|
Usage
|
13
18
|
----
|
14
19
|
|
15
|
-
|
20
|
+
Say you want to write the following Prolog code:
|
16
21
|
|
17
|
-
|
22
|
+
```
|
23
|
+
implication(a, b).
|
24
|
+
implication(b, c).
|
25
|
+
implication(c, d).
|
26
|
+
implication(c, x).
|
27
|
+
|
28
|
+
implies(A, B) :- implication(A, B).
|
29
|
+
implies(A, B) :- implication(A, Something), implies(Something, B).
|
30
|
+
```
|
31
|
+
|
32
|
+
Here's the equivalent Ruby code using this library:
|
33
|
+
|
34
|
+
```rb
|
35
|
+
db = RubyProlog.new do
|
36
|
+
implication['a', 'b'].fact
|
37
|
+
implication['b', 'c'].fact
|
38
|
+
implication['c', 'd'].fact
|
39
|
+
implication['c', 'x'].fact
|
40
|
+
|
41
|
+
implies[:A, :B] << implication[:A, :B]
|
42
|
+
implies[:A, :B] << [
|
43
|
+
implication[:A, :Something],
|
44
|
+
implies[:Something, :B]
|
45
|
+
]
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Now you can run some queries:
|
50
|
+
|
51
|
+
```rb
|
52
|
+
# What are all the direct implications of 'c'?
|
53
|
+
db.query{ implication['c', :X] }
|
54
|
+
#=> [{ X: 'd' }, { X: 'x' }]
|
55
|
+
|
56
|
+
# What are all the things that can directly imply?
|
57
|
+
db.query{ implication[:X, :_] }
|
58
|
+
#=> [{ X: 'a' }, { X: 'b' }, { X: 'c' }, { X: 'c' }]
|
59
|
+
|
60
|
+
# What are all the things 'a' implies?
|
61
|
+
db.query{ implies['a', :X] }
|
62
|
+
#=> [{ X: 'b' }, { X: 'c' }, { X: 'd' }, { X: 'x' }]
|
63
|
+
```
|
64
|
+
|
65
|
+
Unfortunately if you have **two** predicates in a query, you can't just use a comma. There two ways to solve this problem:
|
66
|
+
|
67
|
+
```rb
|
68
|
+
# Solution 1: Use an array
|
69
|
+
db.query{[ implication['b', :S], implies[:S, :B] ]}
|
70
|
+
|
71
|
+
# Solution 2: Use a beneign assignment
|
72
|
+
db.query{_= implication['b', :S], implies[:S, :B] }
|
73
|
+
```
|
74
|
+
|
75
|
+
If you need to add to your database, you can call `instance_eval`:
|
18
76
|
|
19
|
-
|
77
|
+
```rb
|
78
|
+
db = RubyProlog.new do
|
79
|
+
implication['a', 'b'].fact
|
80
|
+
implication['b', 'c'].fact
|
81
|
+
end
|
82
|
+
|
83
|
+
# Later...
|
84
|
+
db.instance_eval do
|
85
|
+
implication['c', 'd'].fact
|
86
|
+
implication['c', 'x'].fact
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
This will mutate your database. If you want to "fork" your database instead, you can call `db.clone`, which will return a new instance with all stored data. Cloning like this is optimized to copy as little as possible.
|
91
|
+
|
92
|
+
Examples
|
93
|
+
----
|
94
|
+
|
95
|
+
gem install ruby-prolog
|
96
|
+
|
97
|
+
Two runnable examples are included in the 'bin' directory. The first..
|
20
98
|
|
21
99
|
ruby-prolog-acls
|
22
100
|
|
23
|
-
..shows the ruby-prolog DSL
|
101
|
+
..shows the ruby-prolog dynamic DSL used to trivially implement access control checks. The second..
|
24
102
|
|
25
103
|
|
104
|
+
ruby-prolog-hanoi
|
105
|
+
|
106
|
+
..is a ruby-prolog solution to the well-known "Towers of Hanoi" problem in computer science. It's not clear, but something Prolog hackers will be interested in. If you have other useful or clever examples, please send a pull request!
|
107
|
+
|
108
|
+
See the test/ directory for additional examples.
|
109
|
+
|
26
110
|
Features
|
27
111
|
----
|
28
112
|
|
29
113
|
* Pure Ruby.
|
30
|
-
*
|
114
|
+
* No wacko dependencies.
|
115
|
+
* Tested with Ruby 2.0.0!
|
31
116
|
* Object-oriented.
|
32
117
|
* Multiple Prolog environments can be created and manipulated simultaneously.
|
33
118
|
* Concurrent access to different core instances should be safe.
|
34
119
|
* Concurrent access to a single core instance might probably explode in odd ways.
|
35
120
|
|
36
|
-
|
37
|
-
Installation
|
121
|
+
Development
|
38
122
|
----
|
39
123
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
124
|
+
```
|
125
|
+
$ git clone https://github.com/preston/ruby-prolog
|
126
|
+
$ cd ruby-prolog
|
127
|
+
$ bundle
|
128
|
+
$ rake test
|
129
|
+
```
|
44
130
|
|
45
131
|
License
|
46
132
|
----
|
47
133
|
|
48
|
-
Released under the Apache 2 license.
|
49
|
-
|
50
|
-
Copyright (c) 2013 Preston Lee. All rights reserved. http://prestonlee.com
|
134
|
+
Released under the Apache 2 license. Copyright (c) 2013 Preston Lee. All rights reserved. http://prestonlee.com
|
data/bin/ruby-prolog-acls
CHANGED
@@ -51,9 +51,9 @@ c.instance_eval do
|
|
51
51
|
assigned['dale', '7', 'admin'].fact
|
52
52
|
|
53
53
|
|
54
|
-
# can_read_on_project[:U, :P]
|
55
|
-
can_on_project[:U, :X, :P]
|
56
|
-
is_role_on_multiple_projects[:U, :R]
|
54
|
+
# can_read_on_project[:U, :P] << [assigned[:U, :P, :R], role_can[:R, 'read']]
|
55
|
+
can_on_project[:U, :X, :P] << [assigned[:U, :P, :R], role_can[:R, :X]]
|
56
|
+
is_role_on_multiple_projects[:U, :R] << [
|
57
57
|
assigned[:U, :X, :R],
|
58
58
|
assigned[:U, :Y, :R],
|
59
59
|
noteq[:X, :Y]]
|
@@ -82,4 +82,4 @@ c.instance_eval do
|
|
82
82
|
|
83
83
|
|
84
84
|
|
85
|
-
end
|
85
|
+
end
|
data/bin/ruby-prolog-hanoi
CHANGED
@@ -12,20 +12,20 @@ require 'ruby-prolog'
|
|
12
12
|
c = RubyProlog::Core.new
|
13
13
|
c.instance_eval do
|
14
14
|
|
15
|
-
move[0,:X,:Y,:Z]
|
16
|
-
move[:N,:A,:B,:C]
|
15
|
+
move[0,:X,:Y,:Z] << :CUT # There are no more moves left
|
16
|
+
move[:N,:A,:B,:C] << [
|
17
17
|
is(:M,:N){|n| n - 1}, # reads as "M IS N - 1"
|
18
18
|
move[:M,:A,:C,:B],
|
19
19
|
write_info[:A,:B],
|
20
20
|
move[:M,:C,:B,:A]
|
21
21
|
]
|
22
|
-
write_info[:X,:Y]
|
22
|
+
write_info[:X,:Y] << [
|
23
23
|
write["move a disc from the "],
|
24
24
|
write[:X], write[" pole to the "],
|
25
25
|
write[:Y], writenl[" pole "]
|
26
26
|
]
|
27
27
|
|
28
|
-
hanoi[:N]
|
28
|
+
hanoi[:N] << move[:N,"left","right","center"]
|
29
29
|
|
30
30
|
puts "\nWhat's the solution for a single disc?"
|
31
31
|
query(hanoi[1])
|
@@ -35,4 +35,4 @@ c.instance_eval do
|
|
35
35
|
|
36
36
|
# do_stuff[:STUFF].calls{|env| print env[:STUFF]; true}
|
37
37
|
|
38
|
-
end
|
38
|
+
end
|
@@ -2,14 +2,23 @@
|
|
2
2
|
# Fuglied by Preston Lee.
|
3
3
|
module RubyProlog
|
4
4
|
|
5
|
-
|
5
|
+
def self.new(&block)
|
6
|
+
c = Core.new
|
7
|
+
c.instance_eval(&block) if block_given?
|
8
|
+
c
|
9
|
+
end
|
10
|
+
|
6
11
|
class Predicate
|
7
|
-
|
8
|
-
|
12
|
+
@@id_counter = 0
|
13
|
+
|
14
|
+
attr_reader :id, :name
|
15
|
+
attr_accessor :db, :clauses
|
9
16
|
|
10
|
-
def initialize(name)
|
17
|
+
def initialize(db, name)
|
18
|
+
@id = (@@id_counter += 1)
|
19
|
+
@db = db
|
11
20
|
@name = name
|
12
|
-
@
|
21
|
+
@clauses = []
|
13
22
|
end
|
14
23
|
|
15
24
|
def inspect
|
@@ -17,30 +26,36 @@ module RubyProlog
|
|
17
26
|
end
|
18
27
|
|
19
28
|
def [](*args)
|
20
|
-
return
|
29
|
+
return TempClause.new(@db, self, args)
|
21
30
|
end
|
22
31
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
class Goal
|
29
|
-
|
30
|
-
attr_reader :pred, :args
|
32
|
+
def to_prolog
|
33
|
+
@clauses.map do |head, body|
|
34
|
+
"#{head.to_prolog}#{body ? " :- #{body.to_prolog}" : ''}."
|
35
|
+
end.join("\n")
|
36
|
+
end
|
31
37
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
38
|
+
def fork(new_db)
|
39
|
+
dupe = self.clone
|
40
|
+
dupe.db = new_db
|
41
|
+
dupe.clauses = dupe.clauses.dup
|
42
|
+
dupe
|
36
43
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
44
|
+
end
|
45
|
+
|
46
|
+
class TempClause
|
47
|
+
def initialize(db, pred, args)
|
48
|
+
@db, @pred, @args = db, pred, args
|
40
49
|
end
|
41
50
|
|
42
51
|
def si(*rhs)
|
43
|
-
|
52
|
+
goals = rhs.map do |x|
|
53
|
+
case x
|
54
|
+
when TempClause then x.to_goal
|
55
|
+
else x
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@db.append(self.to_goal, list(*goals))
|
44
59
|
end
|
45
60
|
|
46
61
|
def fact
|
@@ -57,13 +72,69 @@ module RubyProlog
|
|
57
72
|
end
|
58
73
|
|
59
74
|
def calls(&callback)
|
60
|
-
@
|
75
|
+
@db.append(self.to_goal, callback)
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_goal
|
79
|
+
Goal.new(@pred.id, @pred.name, @args.map do |arg|
|
80
|
+
case arg
|
81
|
+
when TempClause
|
82
|
+
arg.to_goal
|
83
|
+
else
|
84
|
+
arg
|
85
|
+
end
|
86
|
+
end)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def list(*x)
|
92
|
+
y = nil
|
93
|
+
x.reverse_each {|e| y = Cons.new(e, y)}
|
94
|
+
return y
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Goal
|
99
|
+
|
100
|
+
attr_reader :pred_id, :pred_name, :args
|
101
|
+
|
102
|
+
def initialize(pred_id, pred_name, args)
|
103
|
+
@pred_id, @pred_name, @args = pred_id, pred_name, args
|
61
104
|
end
|
62
105
|
|
63
106
|
def inspect
|
64
|
-
return @
|
107
|
+
return @pred_name.to_s + @args.inspect.to_s
|
65
108
|
end
|
66
109
|
|
110
|
+
def to_prolog
|
111
|
+
args_out = @args.map do |arg|
|
112
|
+
case arg
|
113
|
+
when Symbol
|
114
|
+
if arg == :_
|
115
|
+
"_"
|
116
|
+
elsif /[[:upper:]]/.match(arg.to_s[0])
|
117
|
+
arg.to_s
|
118
|
+
else
|
119
|
+
"_#{arg.to_s}"
|
120
|
+
end
|
121
|
+
when String
|
122
|
+
"'#{arg}'"
|
123
|
+
when Cons, Goal
|
124
|
+
arg.to_prolog
|
125
|
+
when Numeric
|
126
|
+
arg.to_s
|
127
|
+
else
|
128
|
+
raise "Unknown argument: #{arg.inspect}"
|
129
|
+
end
|
130
|
+
end.join(', ')
|
131
|
+
|
132
|
+
if @pred_name == :not_
|
133
|
+
"\\+ #{args_out}"
|
134
|
+
else
|
135
|
+
"#{@pred_name}(#{args_out})"
|
136
|
+
end
|
137
|
+
end
|
67
138
|
end
|
68
139
|
|
69
140
|
|
@@ -86,6 +157,19 @@ module RubyProlog
|
|
86
157
|
return '(' + repr[self].join(' ') + ')'
|
87
158
|
end
|
88
159
|
|
160
|
+
def to_prolog
|
161
|
+
current = self
|
162
|
+
array = []
|
163
|
+
while current
|
164
|
+
array << case current[0]
|
165
|
+
when :CUT then '!'
|
166
|
+
when :_ then '_'
|
167
|
+
else current[0].to_prolog
|
168
|
+
end
|
169
|
+
current = current[1]
|
170
|
+
end
|
171
|
+
return array.join(', ')
|
172
|
+
end
|
89
173
|
end
|
90
174
|
|
91
175
|
|
@@ -111,6 +195,24 @@ module RubyProlog
|
|
111
195
|
@table.clear
|
112
196
|
end
|
113
197
|
|
198
|
+
def solution
|
199
|
+
@table.map do |var, env|
|
200
|
+
xp = env
|
201
|
+
loop {
|
202
|
+
x, x_env = xp
|
203
|
+
y, y_env = x_env.dereference(x)
|
204
|
+
next_xp = y_env.get(x)
|
205
|
+
if next_xp.nil?
|
206
|
+
xp = [y, y_env]
|
207
|
+
break
|
208
|
+
else
|
209
|
+
xp = next_xp
|
210
|
+
end
|
211
|
+
}
|
212
|
+
[var, xp[0]]
|
213
|
+
end.to_h
|
214
|
+
end
|
215
|
+
|
114
216
|
def dereference(t)
|
115
217
|
env = self
|
116
218
|
while Symbol === t
|
@@ -124,32 +226,79 @@ module RubyProlog
|
|
124
226
|
def [](t)
|
125
227
|
t, env = dereference(t)
|
126
228
|
return case t
|
127
|
-
when Goal then Goal.new(t.
|
229
|
+
when Goal then Goal.new(t.pred_id, t.pred_name, env[t.args])
|
128
230
|
when Cons then Cons.new(env[t[0]], env[t[1]])
|
129
231
|
when Array then t.collect {|e| env[e]}
|
130
232
|
else t
|
131
233
|
end
|
132
234
|
end
|
133
|
-
|
134
|
-
|
235
|
+
|
236
|
+
|
135
237
|
end
|
136
238
|
|
137
239
|
|
138
240
|
class CallbackEnvironment
|
139
|
-
|
241
|
+
|
140
242
|
def initialize(env, trail, core)
|
141
243
|
@env, @trail, @core = env, trail, core
|
142
244
|
end
|
143
|
-
|
245
|
+
|
144
246
|
def [](t)
|
145
247
|
return @env[t]
|
146
248
|
end
|
147
|
-
|
249
|
+
|
148
250
|
def unify(t, u)
|
149
251
|
# pp "CORE " + @core
|
150
252
|
return @core._unify(t, @env, u, @env, @trail, @env)
|
151
253
|
end
|
152
|
-
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
class Database
|
259
|
+
attr_reader :by_name, :by_id
|
260
|
+
|
261
|
+
def initialize
|
262
|
+
@by_name = {}
|
263
|
+
@by_id = {}
|
264
|
+
@listing_enabled = false
|
265
|
+
@listing = {}
|
266
|
+
end
|
267
|
+
|
268
|
+
def register(pred_name, skip_listing: false)
|
269
|
+
pred = @by_name[pred_name] = Predicate.new(self, pred_name)
|
270
|
+
@by_id[pred.id] = pred
|
271
|
+
@listing[pred.id] = false if skip_listing
|
272
|
+
pred
|
273
|
+
end
|
274
|
+
|
275
|
+
def enable_listing(flag=true)
|
276
|
+
@listing_enabled = true
|
277
|
+
end
|
278
|
+
|
279
|
+
def append(head, body)
|
280
|
+
pred = @by_id[head.pred_id]
|
281
|
+
if pred.nil?
|
282
|
+
raise "No such predicate for head: #{head.inspect}"
|
283
|
+
end
|
284
|
+
pred.clauses << [head, body]
|
285
|
+
if @listing_enabled && @listing[pred.id] != false
|
286
|
+
# Ruby hashes maintain insertion order
|
287
|
+
@listing[pred.id] = true
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def initialize_copy(orig)
|
292
|
+
super
|
293
|
+
@by_id = @by_id.transform_values do |pred|
|
294
|
+
pred.fork(self)
|
295
|
+
end
|
296
|
+
@by_name = @by_name.transform_values {|pred| @by_id[pred.id]}
|
297
|
+
end
|
298
|
+
|
299
|
+
def listing
|
300
|
+
@listing.select{|_,v| v}.map{|k,v| @by_id[k]}
|
301
|
+
end
|
153
302
|
end
|
154
303
|
|
155
304
|
|
@@ -158,7 +307,9 @@ module RubyProlog
|
|
158
307
|
def _unify(x, x_env, y, y_env, trail, tmp_env)
|
159
308
|
|
160
309
|
loop {
|
161
|
-
if
|
310
|
+
if x == :_
|
311
|
+
return true
|
312
|
+
elsif Symbol === x
|
162
313
|
xp = x_env.get(x)
|
163
314
|
if xp.nil?
|
164
315
|
y, y_env = y_env.dereference(y)
|
@@ -179,10 +330,10 @@ module RubyProlog
|
|
179
330
|
}
|
180
331
|
|
181
332
|
if Goal === x and Goal === y
|
182
|
-
return false unless x.
|
333
|
+
return false unless x.pred_id == y.pred_id
|
183
334
|
x, y = x.args, y.args
|
184
335
|
end
|
185
|
-
|
336
|
+
|
186
337
|
if Array === x and Array === y
|
187
338
|
return false unless x.length == y.length
|
188
339
|
for i in 0 ... x.length # x.each_index do |i| も可
|
@@ -201,8 +352,8 @@ module RubyProlog
|
|
201
352
|
x.reverse_each {|e| y = Cons.new(e, y)}
|
202
353
|
return y
|
203
354
|
end
|
204
|
-
|
205
|
-
|
355
|
+
|
356
|
+
|
206
357
|
def resolve(*goals)
|
207
358
|
env = Environment.new
|
208
359
|
_resolve_body(list(*goals), env, [false]) {
|
@@ -224,11 +375,7 @@ module RubyProlog
|
|
224
375
|
else
|
225
376
|
d_env = Environment.new
|
226
377
|
d_cut = [false]
|
227
|
-
|
228
|
-
# pp 'G ' + goal.class.to_s
|
229
|
-
# pp goal.pred
|
230
|
-
for d_head, d_body in goal.pred.defs
|
231
|
-
# for d_head, d_body in goal.defs
|
378
|
+
for d_head, d_body in @db.by_id[goal.pred_id].clauses
|
232
379
|
break if d_cut[0] or cut[0]
|
233
380
|
trail = []
|
234
381
|
if _unify_(goal, env, d_head, d_env, trail, d_env)
|
@@ -271,26 +418,21 @@ module RubyProlog
|
|
271
418
|
end
|
272
419
|
|
273
420
|
|
274
|
-
def query(
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
resolve(*goals) {|env|
|
282
|
-
count += 1
|
283
|
-
results << env[goals]
|
284
|
-
# printout[env[goals]]
|
421
|
+
def query(&block)
|
422
|
+
goals = instance_eval(&block)
|
423
|
+
goals = [goals] unless goals.is_a?(Array)
|
424
|
+
results = []
|
425
|
+
|
426
|
+
resolve(*goals.map(&:to_goal)) {|env|
|
427
|
+
results << env.solution
|
285
428
|
}
|
286
|
-
# printout[goals] if count == 0
|
287
429
|
return results
|
288
430
|
end
|
289
|
-
|
290
|
-
|
431
|
+
|
432
|
+
|
291
433
|
def is(*syms,&block)
|
292
434
|
$is_cnt ||= 0
|
293
|
-
is =
|
435
|
+
is = @db.register("IS_#{$is_cnt += 1}", skip_listing: true)
|
294
436
|
raise "At least one symbol needed" unless syms.size > 0
|
295
437
|
is[*syms].calls do |env|
|
296
438
|
value = block.call(*syms[1..-1].map{|x| env[x]})
|
@@ -300,46 +442,22 @@ module RubyProlog
|
|
300
442
|
end
|
301
443
|
|
302
444
|
def method_missing(meth, *args)
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
# You can't do this..
|
311
|
-
# class << self
|
312
|
-
# module_eval do
|
313
|
-
# send(:define_method, m, proc)
|
314
|
-
# end
|
315
|
-
# end
|
316
|
-
|
317
|
-
# Nor this..
|
318
|
-
# define_method(meth) {pred}
|
319
|
-
|
320
|
-
# Nor this..
|
321
|
-
# self.send(:define_method, meth, proc)
|
322
|
-
|
323
|
-
# And you don't want to pollute the global namespace like this...
|
324
|
-
# Object.class_eval{ define_method(meth){pr} }
|
325
|
-
|
326
|
-
|
327
|
-
# Sooooo... I know this doesn't really make intuitive sense,
|
328
|
-
# but you need to get the eigenclass and then define
|
329
|
-
# the method within that context in such a way that we
|
330
|
-
# have access to local variables, like this...
|
331
|
-
class << self; self; end.module_eval do
|
332
|
-
define_method meth, Proc.new{pred}
|
333
|
-
end
|
334
|
-
# ...which is major fuglytown, but I don't know how to do it any other way.
|
445
|
+
pred = @db.register(meth)
|
446
|
+
|
447
|
+
# We only want to define the method on this specific object instance to avoid polluting global namespaces.
|
448
|
+
define_singleton_method(meth){ @db.by_name[meth] }
|
449
|
+
|
450
|
+
pred
|
451
|
+
end
|
335
452
|
|
336
|
-
|
453
|
+
def to_prolog
|
454
|
+
@db.listing.map(&:to_prolog).join("\n\n")
|
337
455
|
end
|
338
456
|
|
339
|
-
|
457
|
+
|
340
458
|
def initialize
|
341
|
-
|
342
|
-
#
|
459
|
+
@db = Database.new
|
460
|
+
# These predicates are made available in all environments
|
343
461
|
write[:X].calls{|env| print env[:X]; true}
|
344
462
|
writenl[:X].calls{|env| puts env[:X]; true}
|
345
463
|
nl[:X].calls{|e| puts; true}
|
@@ -358,8 +476,21 @@ module RubyProlog
|
|
358
476
|
end
|
359
477
|
end
|
360
478
|
numeric[:X].calls{|env| Numeric === env[:X] }
|
479
|
+
|
480
|
+
not_[:X].calls do |env|
|
481
|
+
found_solution = false
|
482
|
+
resolve(env[:X], :CUT) { found_solution = true }
|
483
|
+
found_solution == false
|
484
|
+
end
|
485
|
+
|
486
|
+
# Enable here so the predicates above don't make it in to_prolog output
|
487
|
+
@db.enable_listing
|
361
488
|
end
|
362
489
|
|
490
|
+
def initialize_copy(orig)
|
491
|
+
super
|
492
|
+
@db = @db.clone
|
493
|
+
end
|
363
494
|
end
|
364
495
|
|
365
|
-
end
|
496
|
+
end
|
data/lib/ruby-prolog/version.rb
CHANGED
data/ruby-prolog.gemspec
CHANGED
@@ -8,17 +8,18 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = RubyProlog::VERSION
|
9
9
|
spec.authors = ["Preston Lee"]
|
10
10
|
spec.email = ["preston.lee@prestonlee.com"]
|
11
|
-
spec.description = "A
|
11
|
+
spec.description = "A pure Ruby implementation of a useful subset of Prolog."
|
12
12
|
spec.summary = "A Prolog-ish Ruby DSL."
|
13
13
|
spec.homepage = "http://github.com/preston/ruby-prolog"
|
14
|
-
spec.license = "Apache
|
14
|
+
spec.license = "Apache-2.0"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler"
|
22
|
-
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "minitest"
|
21
|
+
spec.add_development_dependency "bundler", "~> 2"
|
22
|
+
spec.add_development_dependency "rake", "~> 13"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.14.0"
|
24
|
+
spec.add_development_dependency "minitest-focus", "~> 1.1.2"
|
24
25
|
end
|
@@ -3,51 +3,114 @@ require_relative '../../test_helper'
|
|
3
3
|
|
4
4
|
|
5
5
|
|
6
|
-
describe RubyProlog do
|
7
|
-
|
6
|
+
describe RubyProlog do
|
7
|
+
|
8
8
|
it 'should not pollute the global namespace with predicates.' do
|
9
|
-
|
9
|
+
|
10
10
|
# We'll create numerous instances of the engine and assert they do not interfere with each other.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
query(male[:X]).length.must_equal 1
|
20
|
-
end
|
21
|
-
|
22
|
-
three = RubyProlog::Core.new
|
23
|
-
three.instance_eval do
|
24
|
-
query(male[:X]).length.must_equal 0
|
25
|
-
end
|
26
|
-
|
27
|
-
one.instance_eval do
|
28
|
-
query(male[:X]).length.must_equal 0
|
29
|
-
end
|
11
|
+
one = RubyProlog::Core.new
|
12
|
+
_( one.query{ male[:X] }.length ).must_equal 0
|
13
|
+
|
14
|
+
two = RubyProlog::Core.new
|
15
|
+
two.instance_eval do
|
16
|
+
male[:preston].fact
|
17
|
+
end
|
18
|
+
_( two.query{ male[:X] }.length ).must_equal 1
|
30
19
|
|
20
|
+
three = RubyProlog::Core.new
|
21
|
+
_( three.query{ male[:X] }.length ).must_equal 0
|
22
|
+
|
23
|
+
_( one.query{ male[:X] }.length ).must_equal 0
|
31
24
|
end
|
32
|
-
|
33
25
|
|
34
26
|
|
27
|
+
it 'returns hashes of solutions' do
|
28
|
+
one = RubyProlog.new do
|
29
|
+
foo['a', 'b'].fact
|
30
|
+
foo['a', 'b'].fact
|
31
|
+
foo['a', 'c'].fact
|
32
|
+
foo['d', 'e'].fact
|
33
|
+
foo['d', 'c'].fact
|
34
|
+
end
|
35
|
+
_( one.query {_= foo['a', :X] } ).must_equal [{ X: 'b' }, { X: 'b' }, { X: 'c' }]
|
36
|
+
_( one.query {_= foo['a', :X], foo['d', :X] } ).must_equal [{ X: 'c' }]
|
37
|
+
_(one.to_prolog.class).must_equal String
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'works with numbers' do
|
41
|
+
one = RubyProlog.new do
|
42
|
+
foo[10, 20].fact
|
43
|
+
foo[10, 30].fact
|
44
|
+
end
|
45
|
+
_( one.query {_= foo[10, :X] } ).must_equal [{ X: 20 }, { X: 30 }]
|
46
|
+
|
47
|
+
_(one.to_prolog.class).must_equal String
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'considers all predicates dynamic' do
|
51
|
+
one = RubyProlog::Core.new
|
52
|
+
one.instance_eval do
|
53
|
+
foo[10] << [bar[20]]
|
54
|
+
end
|
55
|
+
_( one.query {_= foo[:X] } ).must_equal []
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'supports underscore' do
|
59
|
+
one = RubyProlog::Core.new
|
60
|
+
one.instance_eval do
|
61
|
+
foo[10, 200].fact
|
62
|
+
foo[10, 300].fact
|
63
|
+
foo[20, 400].fact
|
64
|
+
|
65
|
+
bar[50, :_].fact
|
66
|
+
end
|
67
|
+
_( one.query { foo[:X, :_] } ).must_equal [{X: 10}, {X: 10}, {X: 20}]
|
68
|
+
_( one.query { bar[50, 99] } ).must_equal [{}]
|
69
|
+
one.to_prolog
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'supports clone' do
|
73
|
+
one = RubyProlog::Core.new
|
74
|
+
one.instance_eval do
|
75
|
+
foo[10].fact
|
76
|
+
end
|
77
|
+
_( one.query {_= foo[:X] } ).must_equal [{X: 10}]
|
78
|
+
|
79
|
+
two = one.clone
|
80
|
+
_( one.query {_= foo[:X] } ).must_equal [{X: 10}]
|
81
|
+
_( two.query {_= foo[:X] } ).must_equal [{X: 10}]
|
82
|
+
|
83
|
+
one.instance_eval{ foo[20].fact }
|
84
|
+
|
85
|
+
_( one.query {_= foo[:X] } ).must_equal [{X: 10}, {X: 20}]
|
86
|
+
_( two.query {_= foo[:X] } ).must_equal [{X: 10}]
|
87
|
+
|
88
|
+
two.instance_eval{ foo[30].fact }
|
89
|
+
_( one.query {_= foo[:X] } ).must_equal [{X: 10}, {X: 20}]
|
90
|
+
_( two.query {_= foo[:X] } ).must_equal [{X: 10}, {X: 30}]
|
91
|
+
end
|
92
|
+
|
35
93
|
it 'should be able to query simple family trees.' do
|
36
94
|
|
37
|
-
c = RubyProlog
|
38
|
-
c.instance_eval do
|
95
|
+
c = RubyProlog.new do
|
39
96
|
# Basic family tree relationships..
|
40
|
-
sibling[:X,:Y]
|
41
|
-
mother[:X,:Y]
|
42
|
-
father[:X,:Y]
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
97
|
+
sibling[:X,:Y] << [ parent[:Z,:X], parent[:Z,:Y], noteq[:X,:Y] ]
|
98
|
+
mother[:X,:Y] << [parent[:X, :Y], female[:X]]
|
99
|
+
father[:X,:Y] << [parent[:X, :Y], male[:X]]
|
100
|
+
|
101
|
+
grandparent[:G,:C] << [ parent[:G,:P], parent[:P,:C]]
|
102
|
+
|
103
|
+
ancestor[:A, :C] << [parent[:A, :C]]
|
104
|
+
ancestor[:A, :C] << [parent[:A, :X], parent[:X, :C]]
|
105
|
+
|
106
|
+
mothers[:M, :C] << mother[:M, :C]
|
107
|
+
mothers[:M, :C] << [mother[:M, :X], mothers[:X, :C]]
|
108
|
+
|
109
|
+
fathers[:F, :C] << father[:F, :C]
|
110
|
+
fathers[:F, :C] << [father[:F, :X], fathers[:X, :C]]
|
111
|
+
|
112
|
+
widower[:W] << [married[:W, :X], deceased[:X], nl[deceased[:W]]]
|
113
|
+
widower[:W] << [married[:X, :W], deceased[:X], nl[deceased[:W]]]
|
51
114
|
|
52
115
|
# Basic parents relationships as could be stored in a typical relational database.
|
53
116
|
parent['Ms. Old', 'Marge'].fact
|
@@ -108,108 +171,137 @@ describe RubyProlog do
|
|
108
171
|
interest['Karen', 'Walks'].fact
|
109
172
|
interest['Ron', 'Walks'].fact
|
110
173
|
interest['Marcia', 'Walks'].fact
|
111
|
-
|
112
|
-
# Runs some queries..
|
113
|
-
|
114
|
-
# p "Who are Silas's parents?"
|
115
|
-
# Silas should have two parents: Matt and Julie.
|
116
|
-
r = query(parent[:P, 'Silas'])
|
117
|
-
r.length.must_equal 2
|
118
|
-
r[0][0].args[0].must_equal 'Matt'
|
119
|
-
r[1][0].args[0].must_equal 'Julie'
|
120
|
-
|
121
|
-
# p "Who is married?"
|
122
|
-
# We defined 5 married facts.
|
123
|
-
query(married[:A, :B]).length.must_equal 5
|
124
|
-
|
125
|
-
# p 'Are Karen and Julie siblings?'
|
126
|
-
# Yes, through two parents.
|
127
|
-
query(sibling['Karen', 'Julie']).length.must_equal 2
|
128
|
-
|
129
|
-
|
130
|
-
# p "Who likes to play games?"
|
131
|
-
# Four people.
|
132
|
-
query(interest[:X, 'Games']).length.must_equal 4
|
133
|
-
|
134
|
-
|
135
|
-
# p "Who likes to play checkers?"
|
136
|
-
# Nobody.
|
137
|
-
query(interest[:X, 'Checkers']).length.must_equal 0
|
138
|
-
|
139
|
-
# p "Who are Karen's ancestors?"
|
140
|
-
# query(ancestor[:A, 'Karen'])
|
141
|
-
|
142
|
-
# p "What grandparents are also widowers?"
|
143
|
-
# Marge, twice, because of two grandchildren.
|
144
|
-
query(widower[:X], grandparent[:X, :G]).length.must_equal 2
|
145
174
|
end
|
146
175
|
|
176
|
+
# Runs some queries..
|
177
|
+
|
178
|
+
# p "Who are Silas's parents?"
|
179
|
+
# Silas should have two parents: Matt and Julie.
|
180
|
+
_( c.query{ parent[:P, 'Silas'] } ).must_equal [{P: 'Matt'}, {P: 'Julie'}]
|
181
|
+
|
182
|
+
# p "Who is married?"
|
183
|
+
# We defined 5 married facts.
|
184
|
+
_( c.query{ married[:A, :B] }.length ).must_equal 5
|
185
|
+
|
186
|
+
# p 'Are Karen and Julie siblings?'
|
187
|
+
# Yes, through two parents.
|
188
|
+
_( c.query{ sibling['Karen', 'Julie'] }.length ).must_equal 2
|
189
|
+
|
190
|
+
|
191
|
+
# p "Who likes to play games?"
|
192
|
+
# Four people.
|
193
|
+
_( c.query{ interest[:X, 'Games'] }.length ).must_equal 4
|
194
|
+
|
195
|
+
|
196
|
+
# p "Who likes to play checkers?"
|
197
|
+
# Nobody.
|
198
|
+
_( c.query{ interest[:X, 'Checkers'] }.length ).must_equal 0
|
199
|
+
|
200
|
+
# p "Who are Karen's ancestors?"
|
201
|
+
_( c.query{ ancestor[:A, 'Karen'] } ).must_equal [
|
202
|
+
{A: 'Marcia'},
|
203
|
+
{A: 'Ron'},
|
204
|
+
{A: 'Carol'},
|
205
|
+
{A: 'Kent'},
|
206
|
+
{A: 'Marge'},
|
207
|
+
{A: 'Pappy'},
|
208
|
+
]
|
209
|
+
|
210
|
+
# p "What grandparents are also widowers?"
|
211
|
+
# Marge, twice, because of two grandchildren.
|
212
|
+
_( c.query{_= widower[:X], grandparent[:X, :G] }.length ).must_equal 2
|
213
|
+
|
214
|
+
_(c.to_prolog.class).must_equal String
|
147
215
|
end
|
148
216
|
|
149
217
|
|
150
218
|
it 'should be able to query simple family trees.' do
|
151
219
|
|
152
|
-
c = RubyProlog
|
153
|
-
c.instance_eval do
|
220
|
+
c = RubyProlog.new do
|
154
221
|
|
155
222
|
vendor['dell'].fact
|
156
223
|
vendor['apple'].fact
|
157
|
-
|
224
|
+
|
158
225
|
model['ultrasharp'].fact
|
159
226
|
model['xps'].fact
|
160
227
|
model['macbook'].fact
|
161
228
|
model['iphone'].fact
|
162
|
-
|
229
|
+
|
163
230
|
manufactures['dell', 'ultrasharp'].fact
|
164
231
|
manufactures['dell', 'xps'].fact
|
165
232
|
manufactures['apple', 'macbook'].fact
|
166
233
|
manufactures['apple', 'iphone'].fact
|
167
|
-
|
234
|
+
|
168
235
|
is_a['xps', 'laptop'].fact
|
169
236
|
is_a['macbook', 'laptop'].fact
|
170
237
|
is_a['ultrasharp', 'monitor'].fact
|
171
238
|
is_a['iphone', 'phone'].fact
|
172
|
-
|
173
|
-
kind['laptop']
|
174
|
-
kind['monitor']
|
175
|
-
kind['phone']
|
176
|
-
|
177
|
-
model[:M]
|
178
|
-
|
179
|
-
vendor_of[:V, :K]
|
180
|
-
|
181
|
-
|
182
|
-
query(is_a[:K, 'laptop']).length == 2
|
183
|
-
query(vendor_of[:V, 'phone']) == 1
|
184
|
-
# pp query(not_vendor_of[:V, 'phone'])
|
239
|
+
|
240
|
+
kind['laptop'].fact
|
241
|
+
kind['monitor'].fact
|
242
|
+
kind['phone'].fact
|
243
|
+
|
244
|
+
model[:M] << [manfactures[:V, :M]]
|
245
|
+
|
246
|
+
vendor_of[:V, :K] << [vendor[:V], manufactures[:V, :M], is_a[:M, :K]]
|
247
|
+
not_vendor_of[:V, :K] << [vendor[:V], not_[vendor_of[:V, :K]]]
|
185
248
|
end
|
186
|
-
|
249
|
+
|
250
|
+
_( c.query{ is_a[:K, 'laptop'] }.length ).must_equal 2
|
251
|
+
_( c.query{ vendor_of[:V, 'phone'] } ).must_equal [{V: 'apple'}]
|
252
|
+
_( c.query{ not_vendor_of[:V, 'phone'] } ).must_equal [{V: 'dell'}]
|
253
|
+
_(c.to_prolog.class).must_equal String
|
187
254
|
end
|
188
|
-
|
189
|
-
|
255
|
+
|
256
|
+
|
190
257
|
it 'should solve the Towers of Hanoi problem.' do
|
191
|
-
c = RubyProlog
|
192
|
-
c.instance_eval do
|
258
|
+
c = RubyProlog.new do
|
193
259
|
|
194
|
-
move[0,:X,:Y,:Z]
|
195
|
-
move[:N,:A,:B,:C]
|
260
|
+
move[0,:X,:Y,:Z] << :CUT # There are no more moves left
|
261
|
+
move[:N,:A,:B,:C] << [
|
196
262
|
is(:M,:N){|n| n - 1}, # reads as "M IS N - 1"
|
197
263
|
move[:M,:A,:C,:B],
|
198
264
|
# write_info[:A,:B],
|
199
265
|
move[:M,:C,:B,:A]
|
200
266
|
]
|
201
|
-
write_info[:X,:Y]
|
267
|
+
write_info[:X,:Y] << [
|
202
268
|
# write["move a disc from the "],
|
203
269
|
# write[:X], write[" pole to the "],
|
204
270
|
# write[:Y], writenl[" pole "]
|
205
271
|
]
|
206
272
|
|
207
|
-
hanoi[:N]
|
208
|
-
|
273
|
+
hanoi[:N] << move[:N,"left","right","center"]
|
274
|
+
end
|
275
|
+
|
276
|
+
_( c.query{ hanoi[5] } ).must_equal [{}]
|
277
|
+
|
278
|
+
_(c.to_prolog.class).must_equal String
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'works on the other examples in the readme' do
|
282
|
+
db = RubyProlog.new do
|
283
|
+
implication['a', 'b'].fact
|
284
|
+
implication['b', 'c'].fact
|
285
|
+
implication['c', 'd'].fact
|
286
|
+
implication['c', 'x'].fact
|
287
|
+
|
288
|
+
implies[:A, :B] << implication[:A, :B]
|
289
|
+
implies[:A, :B] << [
|
290
|
+
implication[:A, :Something],
|
291
|
+
implies[:Something, :B]
|
292
|
+
]
|
293
|
+
end
|
294
|
+
|
295
|
+
_( db.query{ implication['c', :X] } ).must_equal [{ X: 'd' }, { X: 'x' }]
|
296
|
+
_( db.query{ implication[:X, :_] } ).must_equal [{ X: 'a' }, { X: 'b' }, { X: 'c' }, { X: 'c' }]
|
297
|
+
_( db.query{_= implies['a', :X] } ).must_equal [{ X: 'b' }, { X: 'c' }, { X: 'd' }, { X: 'x' }]
|
209
298
|
|
210
|
-
|
299
|
+
_( db.query{[ implication['b', :S], implies[:S, :B] ]} ).must_equal [{:S=>"c", :B=>"d"}, {:S=>"c", :B=>"x"}]
|
300
|
+
_( db.query{_= implication['b', :S], implies[:S, :B] } ).must_equal [{:S=>"c", :B=>"d"}, {:S=>"c", :B=>"x"}]
|
211
301
|
|
212
|
-
|
213
|
-
|
302
|
+
# For good measure
|
303
|
+
_( db.query{_= implies['a', 'b'] } ).must_equal [{}]
|
304
|
+
_( db.query{_= implies['a', 'd'] } ).must_equal [{}]
|
305
|
+
_( db.query{_= implies['a', 'idontexist'] } ).must_equal []
|
214
306
|
end
|
215
307
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,58 +1,72 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-prolog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Preston Lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 5.14.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
55
|
-
|
54
|
+
version: 5.14.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-focus
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.2
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.2
|
69
|
+
description: A pure Ruby implementation of a useful subset of Prolog.
|
56
70
|
email:
|
57
71
|
- preston.lee@prestonlee.com
|
58
72
|
executables:
|
@@ -61,7 +75,7 @@ executables:
|
|
61
75
|
extensions: []
|
62
76
|
extra_rdoc_files: []
|
63
77
|
files:
|
64
|
-
- .gitignore
|
78
|
+
- ".gitignore"
|
65
79
|
- Gemfile
|
66
80
|
- NOTICE
|
67
81
|
- README.md
|
@@ -76,7 +90,7 @@ files:
|
|
76
90
|
- test/test_helper.rb
|
77
91
|
homepage: http://github.com/preston/ruby-prolog
|
78
92
|
licenses:
|
79
|
-
- Apache
|
93
|
+
- Apache-2.0
|
80
94
|
metadata: {}
|
81
95
|
post_install_message:
|
82
96
|
rdoc_options: []
|
@@ -84,17 +98,16 @@ require_paths:
|
|
84
98
|
- lib
|
85
99
|
required_ruby_version: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
|
-
- -
|
101
|
+
- - ">="
|
88
102
|
- !ruby/object:Gem::Version
|
89
103
|
version: '0'
|
90
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
105
|
requirements:
|
92
|
-
- -
|
106
|
+
- - ">="
|
93
107
|
- !ruby/object:Gem::Version
|
94
108
|
version: '0'
|
95
109
|
requirements: []
|
96
|
-
|
97
|
-
rubygems_version: 2.0.3
|
110
|
+
rubygems_version: 3.1.2
|
98
111
|
signing_key:
|
99
112
|
specification_version: 4
|
100
113
|
summary: A Prolog-ish Ruby DSL.
|