rubylog 2.0pre1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +197 -98
- data/RELEASE_NOTES.rdoc +3 -4
- data/VERSION +1 -1
- data/examples/mice.rb +2 -2
- data/examples/tracing.rb +2 -2
- data/lib/rubylog/builtins/ensure.rb +1 -1
- data/lib/rubylog/builtins/logic.rb +47 -25
- data/lib/rubylog/compound_term.rb +12 -2
- data/lib/rubylog/context_modules/predicates.rb +2 -36
- data/lib/rubylog/dsl/indicators.rb +36 -0
- data/lib/rubylog/dsl/variables.rb +1 -1
- data/lib/rubylog/errors.rb +2 -2
- data/lib/rubylog/{clause.rb → goal.rb} +2 -2
- data/lib/rubylog/mixins/proc.rb +2 -2
- data/lib/rubylog/mixins/symbol.rb +2 -2
- data/lib/rubylog/procedure.rb +5 -1
- data/lib/rubylog/rule.rb +4 -0
- data/lib/rubylog/structure.rb +2 -2
- data/lib/rubylog/variable.rb +29 -9
- data/lib/rubylog.rb +1 -1
- data/rubylog.gemspec +8 -4
- data/spec/inriasuite_spec.rb +1 -3
- data/spec/integration/dsl_spec.rb +2 -2
- data/spec/rubylog/builtins/logic_spec.rb +42 -0
- data/spec/rubylog/builtins/term_spec.rb +0 -4
- data/spec/rubylog/context_modules/predicates_spec.rb +0 -22
- data/spec/rubylog/context_modules/thats_spec.rb +1 -1
- data/spec/rubylog/dsl/indicators_spec.rb +26 -0
- data/spec/rubylog/interfaces/term_spec.rb +9 -9
- data/spec/rubylog/mixins/array_spec.rb +1 -1
- data/spec/rubylog/procedure_spec.rb +11 -0
- data/spec/rubylog/rule_spec.rb +13 -0
- data/spec/rubylog/variable_spec.rb +21 -4
- metadata +21 -17
data/README.rdoc
CHANGED
@@ -3,38 +3,52 @@
|
|
3
3
|
Rubylog is a Prolog-like DSL for Ruby. The language is inspired by {Jamis
|
4
4
|
Buck}[http://weblog.jamisbuck.org/2006/10/28/prolog-in-ruby], and the
|
5
5
|
implementation is based on {Yield Prolog}[http://yieldprolog.sourceforge.net/],
|
6
|
-
with lots of sintactic and semantic
|
6
|
+
with lots of sintactic and semantic candy.
|
7
7
|
|
8
8
|
See the {wiki}[https://github.com/cie/rubylog/wiki] for online documentation.
|
9
9
|
|
10
10
|
== Getting started
|
11
11
|
|
12
|
+
=== Installing
|
13
|
+
|
14
|
+
Currently, Rubylog only works with Ruby 1.9.2.
|
15
|
+
|
12
16
|
First, install the gem
|
13
17
|
|
14
18
|
$ gem install rubylog
|
15
19
|
|
16
20
|
or, if you use +bundler+, add this line to your +Gemfile+:
|
17
21
|
|
18
|
-
gem 'rubylog', '~>2.
|
22
|
+
gem 'rubylog', '~>2.0'
|
23
|
+
|
19
24
|
|
25
|
+
=== The context
|
26
|
+
|
27
|
+
Secondly, you need a Rubylog context. The simplest you can do is to extend Rubylog::Context into the main object.
|
28
|
+
|
29
|
+
require 'rubylog'
|
30
|
+
extend Rubylog::Context
|
20
31
|
|
32
|
+
Another option is to use Kernel#rubylog:
|
21
33
|
|
22
|
-
First, you need a Rubylog context. The simplest you can do is to extend Rubylog::Context into the main object.
|
23
34
|
|
24
35
|
require 'rubylog'
|
25
36
|
extend Rubylog::Context
|
26
37
|
|
38
|
+
rubylog do
|
39
|
+
# your code here
|
40
|
+
end
|
41
|
+
|
27
42
|
|
28
|
-
|
43
|
+
== Data types
|
29
44
|
|
30
|
-
Rubylog is similar to Prolog, but there are quite a few differences.
|
31
|
-
you can use any Ruby object as data.
|
45
|
+
Rubylog is similar to Prolog, but there are quite a few differences.
|
32
46
|
|
33
47
|
Rubylog variables are (undefined) constant names:
|
34
48
|
|
35
49
|
A, B, ANYTHING
|
36
50
|
|
37
|
-
A
|
51
|
+
A variable can be bound or unbound, and it contains a Ruby object when bound. A variable whose name starts with +ANY...+ (case-insensitive) is a don't-care
|
38
52
|
variable (like +_+ in Prolog).
|
39
53
|
|
40
54
|
Lists are just Ruby arrays:
|
@@ -45,72 +59,74 @@ They can have splats:
|
|
45
59
|
|
46
60
|
[1, 2, *T]
|
47
61
|
|
48
|
-
Which would be <tt>[1,2|T]</tt> in Prolog
|
49
|
-
to the end
|
62
|
+
Which would be <tt>[1,2|T]</tt> in Prolog. However, in Rubylog, splats are not limited
|
63
|
+
to the end:
|
50
64
|
|
51
|
-
|
52
|
-
|
65
|
+
[1, *X, 5]
|
66
|
+
[*A, *B]
|
53
67
|
|
54
|
-
|
55
|
-
'John'.likes!('beer')
|
68
|
+
Currently you cannot use hashes as terms.
|
56
69
|
|
57
|
-
|
70
|
+
== Predicates
|
71
|
+
As in prolog, predicates are the building blocks of your program. However, the arguments are in a different order than they are in prolog:
|
58
72
|
|
59
|
-
|
73
|
+
'John'.likes('beer')
|
60
74
|
|
61
|
-
|
62
|
-
X.drinks(Y).if X.has(Y).and X.likes(Y)
|
75
|
+
which would be <tt>likes('John','beer')</tt> in prolog. As you can see, the first argument comes first, then the functor, and then the further arguments.
|
63
76
|
|
64
|
-
|
77
|
+
In Rubylog, predicates must be declared with +predicate_for+:
|
65
78
|
|
66
|
-
|
67
|
-
|
79
|
+
predicate_for String, ".likes()"
|
80
|
+
|
81
|
+
You have to specify the class of the possible first arguments (+String+ in this case), this is called the subject class. This could also be an array of classes. The string indicating the predicate syntax is <tt>".likes()"</tt>. The format is <tt>.asdf .asdf() .asdf(,) .asdf(,,)</tt> for predicates with 1,2,3 and 4 arguments. You can add descriptions in the indicator string e.g. <tt>"Person.likes(Drink)"</tt> means the same as <tt>".likes()"</tt>.
|
68
82
|
|
69
|
-
|
83
|
+
Declaring a predicate with arguments gives you three methods on the subject class:
|
70
84
|
|
71
|
-
|
85
|
+
predicate_for String, ".likes()"
|
86
|
+
'John'.likes('beer') # returns a structure object representing this logical statement
|
87
|
+
'John'.likes!('beer') # asserts that this statement as a fact
|
88
|
+
'John'.likes?('beer') # tells if this statement is true (in this case, returns true)
|
72
89
|
|
73
|
-
|
90
|
+
=== Nullary predicates
|
74
91
|
|
75
|
-
|
92
|
+
Nullary predicates are symbols, and they have to be declared with +predicate+:
|
76
93
|
|
77
|
-
|
78
|
-
|
94
|
+
predicate ":asdf"
|
95
|
+
:asdf
|
79
96
|
|
80
|
-
Nullary predicates are symbols, similar to Prolog:
|
81
97
|
|
82
|
-
|
98
|
+
== Asserting clauses
|
83
99
|
|
100
|
+
As in Prolog, there are two types of program clauses: facts and rules.
|
101
|
+
You can assert facts with the bang syntax:
|
84
102
|
|
103
|
+
predicate_for String, ".likes()"
|
104
|
+
'John'.likes! 'milk'
|
85
105
|
|
86
|
-
|
106
|
+
This would be <tt>likes('John','beer').</tt> in Prolog. Bang assertions return their first argument (which is <tt>'John'</tt> in this case), so they can be chained:
|
87
107
|
|
88
|
-
|
108
|
+
'John'.likes!('beer').has!('beer')
|
89
109
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
.and()
|
95
|
-
.or() ;
|
96
|
-
:false \+
|
97
|
-
.is() =
|
98
|
-
.is_not() =/=
|
99
|
-
.in() member
|
100
|
-
:cut! !
|
110
|
+
You can assert rules with the +if+ method:
|
111
|
+
|
112
|
+
predicate_for String, ".likes() .drinks() .has()"
|
113
|
+
|
114
|
+
X.drinks(Y).if X.has(Y).and X.likes(Y)
|
101
115
|
|
102
|
-
|
116
|
+
This would be <tt>drinks(X,Y) :- has(X,Y), likes(X,Y).</tt> in Prolog.
|
103
117
|
|
104
|
-
not_in, all, any, one, none, iff
|
105
118
|
|
106
|
-
You can
|
119
|
+
You can also use +unless+:
|
120
|
+
|
121
|
+
predicate_for String, ".good .bad"
|
122
|
+
A.good.unless A.bad
|
123
|
+
|
107
124
|
|
108
125
|
|
109
|
-
See the documentation in <tt>lib/rubylog/builtins/</tt>
|
110
126
|
|
111
|
-
|
127
|
+
== Unification
|
112
128
|
|
113
|
-
In Rubylog, unification works
|
129
|
+
In Rubylog, unification works like in Prolog, but with the +is+ functor.
|
114
130
|
|
115
131
|
A.is(B)
|
116
132
|
|
@@ -121,25 +137,36 @@ Using arrays, you can benefit the splats:
|
|
121
137
|
|
122
138
|
The +in+ predicate unifies the first argument with any member of the collection:
|
123
139
|
|
124
|
-
4.in([1,2,3,4])
|
140
|
+
4.in([1,2,3,4]) # member(4,[1,2,3,4]) in prolog
|
141
|
+
|
142
|
+
== Guards
|
125
143
|
|
126
|
-
You can use guards:
|
144
|
+
You can use guards. These are constant expressions that restrict the unification of variables. There are several types of guards:
|
127
145
|
|
128
|
-
A[String].in(["asdf",5,nil]).each { p A }
|
129
|
-
A[/x/].in(["asdf","xyz"]).each { p A }
|
130
|
-
A[
|
146
|
+
A[String].in(["asdf",5,nil]).each { p A } # outputs "asdf"
|
147
|
+
A[/x/].in(["asdf","xyz"]).each { p A } # outputs "xyz"
|
148
|
+
A[length: 3].in(["abc","abcd"]).each { p A } # outputs "abc"
|
149
|
+
A[thats < 5].in([4,5,6]).each { p A } # outputs 4
|
150
|
+
A[thats.length + 1 == 5].in(["abc","abcd"]).each { p A } # outputs "abcd"
|
131
151
|
|
132
|
-
|
133
|
-
|
152
|
+
+thats+ can receive any number of messages chained, and this will be applied to the value that would be bound to the variable.
|
153
|
+
You can add various guards to a variable. +thats\_not+ is the negation of +thats+.
|
134
154
|
|
135
|
-
|
155
|
+
A[Integer, thats%2 == 0].even!
|
156
|
+
|
157
|
+
This is an experimental feature. Currently, you cannot use variables in guards, only constant values.
|
158
|
+
|
159
|
+
|
160
|
+
== Moving between Ruby and Rubylog
|
161
|
+
=== Running a query
|
162
|
+
|
163
|
+
If you want to run a query, you have three different syntaxes:
|
136
164
|
|
137
|
-
prove ('John'.drinks 'beer') # => true
|
138
165
|
true? ('John'.drinks 'beer') # => true
|
139
166
|
('John'.drinks 'beer').true? # => true
|
140
167
|
'John'.drinks? 'beer' # => true
|
141
168
|
|
142
|
-
|
169
|
+
=== Finding solutions
|
143
170
|
|
144
171
|
+Structure+ implements +Enumerable+, and yields the solutions. Within the
|
145
172
|
enumeration block, you can access the values of your variables.
|
@@ -149,7 +176,9 @@ enumeration block, you can access the values of your variables.
|
|
149
176
|
('John'.drinks X).map{X} # => ['beer']
|
150
177
|
('John'.drinks X).count # => 1
|
151
178
|
|
152
|
-
|
179
|
+
You can also use +solve+, which is equivalent with +each+.
|
180
|
+
|
181
|
+
=== Procs as predicates
|
153
182
|
|
154
183
|
You can invoke Ruby codes in Rubylog rules with a proc:
|
155
184
|
|
@@ -161,75 +190,107 @@ or in most cases you can use just a block:
|
|
161
190
|
|
162
191
|
The predicate succeeds if the block returns a true value.
|
163
192
|
|
164
|
-
|
193
|
+
=== Procs as functions
|
165
194
|
|
166
195
|
+is+ and +in+ can take a proc or block argument, which they execute and take its return value:
|
167
196
|
|
168
197
|
X.good.if X.is { 'BEER'.downcase }
|
169
198
|
X.good.if X.in { get_good_drinks() }
|
170
199
|
|
171
|
-
|
200
|
+
=== Variables in blocks
|
172
201
|
|
173
|
-
|
174
|
-
at compile time, and is used for describing the Rubylog program. Native code is
|
175
|
-
executed runtime. Any block passed to Rubylog structures native code.
|
202
|
+
When you use blocks or procs as predicates or functions, or when you use enumeration methods with blocks, you can access values of variables by their name in the blocks (if they are bound).
|
176
203
|
|
177
|
-
('John'.drinks X).and { X != 'beer'}.each { p X }
|
178
|
-
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ dsl mode
|
179
|
-
^^^^^^^^^^^^ ^^^^^ native mode
|
180
204
|
|
181
|
-
|
182
|
-
|
183
|
-
|
205
|
+
'John'.likes(Y).if { Y =~ /ale/ }
|
206
|
+
'John'.likes(Y).if Y.is { Y =~ /ale/ }
|
207
|
+
'John'.likes(Y).each { p Y }
|
208
|
+
|
209
|
+
If your variable is unbound, you will get the variable object.
|
210
|
+
|
211
|
+
X.is(Y).each { p X.class } # outputs 'Rubylog::Variable'
|
184
212
|
|
185
|
-
All built-in rubylog predicates are clean logical programming predicates witout
|
186
|
-
a side-effect. If you want some side-effects, you always go into native mode.
|
187
213
|
|
188
|
-
|
214
|
+
== Rspec integration
|
189
215
|
|
190
|
-
|
216
|
+
Rubylog can integrate with RSpec. This enables you to use variables and predicates in specs.
|
191
217
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
218
|
+
require "rspec/rubylog"
|
219
|
+
|
220
|
+
describe "numbers", rubylog: true do
|
221
|
+
specify do
|
222
|
+
A.is(5).map{A}.should == [5]
|
223
|
+
end
|
196
224
|
end
|
197
225
|
|
198
|
-
|
199
|
-
with
|
226
|
+
There is an assertion method called +check+ that receives a predicate as an argument and raises an exception if it fails.
|
200
227
|
|
201
|
-
|
228
|
+
check 5.is(5)
|
202
229
|
|
203
|
-
|
230
|
+
== Built-in predicates
|
204
231
|
|
205
|
-
|
232
|
+
Some built-in predicates and their Prolog equivalents:
|
206
233
|
|
207
|
-
|
234
|
+
Rubylog Prolog
|
235
|
+
------- ------
|
236
|
+
:true true
|
237
|
+
:fail fail
|
238
|
+
.and() ,
|
239
|
+
.or() ;
|
240
|
+
.false \+
|
241
|
+
.is() =
|
242
|
+
.is_not() =/=
|
243
|
+
.in() member
|
244
|
+
:cut! !
|
208
245
|
|
209
|
-
|
246
|
+
There are some new ones which do not exist in prolog.
|
210
247
|
|
211
|
-
|
248
|
+
* <tt>.not\_in()</tt> is the negation of <tt>.in()</tt>.
|
212
249
|
|
213
|
-
require "rubylog/builtins/file_system"
|
214
250
|
|
215
|
-
|
216
|
-
check "README".filename_in "."
|
251
|
+
=== Quantifiers
|
217
252
|
|
218
|
-
|
219
|
-
end
|
253
|
+
<tt>.all(), .any(), .one(), .none()</tt> are prediates that are analogous to their equivalents in <tt>Enumerable</tt>. They prove their first argument, and for each solution try to prove their second argument. If the second argument succeeds for all / any / exactly one / none of the solutions of the first argument, they succeed. Some examples:
|
220
254
|
|
255
|
+
predicate_for Integer, ".even"
|
256
|
+
X.even.if { X%2 == 0 }
|
257
|
+
check X.in([2,4]).all(X.even)
|
258
|
+
check X.in([1,2,4]).any(X.even)
|
259
|
+
check X.in([1,2,3]).one(X.even)
|
260
|
+
check X.in([1,3]).none(X.even)
|
221
261
|
|
222
|
-
|
262
|
+
There is another similar predicate <tt>A.iff(B)</tt>, that succeeds if for all solutions of A, B is true, and vice versa. For example,
|
223
263
|
|
224
|
-
|
264
|
+
check X.in([2,4]).iff(X.in(1..4).and X.even)
|
225
265
|
|
266
|
+
These predicates also have a prefix form, which can be used to create more naturally sounding program lines:
|
267
|
+
|
268
|
+
check all X.in([2,4]), X.even
|
269
|
+
check any X.in([1,2,4]), X.even
|
270
|
+
check one X.in([1,2,3]), X.even
|
271
|
+
check none X.in([1,3]), X.even
|
272
|
+
check iff X.in([2,4]), X.in(1..4).and(X.even)
|
273
|
+
|
274
|
+
There is another quantifier, which only has a prefix form <tt>every(A, B)</tt>. This works similarly to <tt>.all()</tt>, but for each solution of A, creates a copy of B and chains them together with <tt>.and()</tt>. It can be useful for work with assumptions, see below. This is an experimental feature, and still contains bugs.
|
275
|
+
|
276
|
+
=== File system
|
277
|
+
|
278
|
+
You can make some queries on the file system:
|
226
279
|
|
227
|
-
|
280
|
+
check "README".filename_in "."
|
281
|
+
check "./README".file_in "."
|
228
282
|
|
229
|
-
|
230
|
-
functor_for String, :likes
|
283
|
+
X.dirname_in(".").each { puts X }
|
231
284
|
|
232
|
-
|
285
|
+
|
286
|
+
=== Reflection
|
287
|
+
|
288
|
+
You can make some metaprogramming with Rubylog
|
289
|
+
|
290
|
+
|
291
|
+
predicate_for String, ".likes()"
|
292
|
+
|
293
|
+
check "John".likes("Jane").structure(Pred, :likes, ["John", "Jane"])
|
233
294
|
|
234
295
|
"John".likes(X).if X.likes("John")
|
235
296
|
"Jane".likes!("John")
|
@@ -241,12 +302,50 @@ You can make some metaprogramming with Rubylog
|
|
241
302
|
|
242
303
|
end
|
243
304
|
|
305
|
+
=== Arithmetics
|
306
|
+
|
307
|
+
check 5.sum_of(2,3)
|
308
|
+
check 5.product_of(1,5)
|
309
|
+
|
310
|
+
These work as expected if you provide any two of the three paramters. For example,
|
311
|
+
|
312
|
+
10.sum_of(6,A).solve { p A } # outputs 4
|
313
|
+
|
314
|
+
A.in(1..21).and(21.product_of(A,B)).each do
|
315
|
+
p [A,B]
|
316
|
+
end # outputs pairs of divisors
|
317
|
+
|
318
|
+
=== Assumptions
|
319
|
+
|
320
|
+
An assumption is an assertion that gets erased at backtracking. There are several possibilites for assuming clauses.
|
321
|
+
|
322
|
+
A.assumed # assumes A as a fact
|
323
|
+
A.assumed_if(B) # assumes A.if(B)
|
324
|
+
A.assumed_unless(B) # assumes A.unless(B)
|
325
|
+
A.rejected # assumes A.if(:cut!.and :fail) to the beginning of the rule list
|
326
|
+
A.rejected_if(B) # assumes A.if(B.and :cut!.and :fail) to the beginning of the rule list
|
327
|
+
A.rejected_unless(B) # assumes A.if(B.false.and :cut!.and :fail) to the beginning of the rule list
|
328
|
+
A.revoked # temporarily removes a rule which holds for A
|
329
|
+
|
330
|
+
These are experimental features.
|
331
|
+
|
332
|
+
== Troubleshooting
|
333
|
+
|
334
|
+
You can turn on tracing with
|
335
|
+
|
336
|
+
Rubylog.trace
|
337
|
+
|
338
|
+
Or, you can trace a specific code block with
|
339
|
+
|
340
|
+
Rubylog.trace do
|
341
|
+
...
|
342
|
+
end
|
343
|
+
|
344
|
+
|
244
345
|
== Contributing
|
245
346
|
|
246
347
|
=== To the language
|
247
348
|
|
248
|
-
* Post your own examples to the {wiki}[https://github.com/cie/rubylog/wiki/Examples].
|
249
|
-
* Improve others' examples.
|
250
349
|
* If you have a suggestion for the language, submit an issue.
|
251
350
|
|
252
351
|
=== Reporting bugs or requesting features
|
data/RELEASE_NOTES.rdoc
CHANGED
@@ -6,11 +6,10 @@
|
|
6
6
|
|
7
7
|
== Version 2.0.0 (to be released)
|
8
8
|
* Backwards incompatibilities
|
9
|
-
* <tt>Rubylog.theory</tt>
|
9
|
+
* <tt>Rubylog.theory</tt> renamed to <tt>Kernel#rubylog</tt>.
|
10
10
|
* <tt>file\_in</tt> includes directories and yields relative paths.
|
11
11
|
<tt>dir\_in</tt> also yields relative paths.
|
12
|
-
* <tt>trace</tt> has became <tt>Rubylog.trace</tt>.
|
13
|
-
<tt>Kernel#Rubylog</tt>, it does not matter.
|
12
|
+
* <tt>trace</tt> has became <tt>Rubylog.trace</tt>.
|
14
13
|
* <tt>Theory</tt> has changed to <tt>Context</tt> and it became a module.
|
15
14
|
* No more inclusion of theories (contexts).
|
16
15
|
* Predicates do not have reference in the theory (context), they are only referenced from the
|
@@ -26,7 +25,7 @@
|
|
26
25
|
* No more real prefix functors, n-ary prefix functors are n\+1-ary functors with the context object as the first argument. Use <tt>predicate\_for Rubylog::Context, "..."</tt>.
|
27
26
|
* <tt>Rubylog::DefaultBuiltins</tt> is replaced by <tt>Rubylog</tt>
|
28
27
|
* <tt>Theory#prove</tt> was removed. Use <tt>Context#true?</tt>
|
29
|
-
* <tt>Rubylog::Callable<tt> renamed to <tt>Rubylog::
|
28
|
+
* <tt>Rubylog::Callable<tt> renamed to <tt>Rubylog::Goal</tt>
|
30
29
|
* <tt>discontiguous</tt> is removed. No more discontiguity checks.
|
31
30
|
* <tt>implicit</tt> is removed. No implicit mode anymore.
|
32
31
|
* <tt>Theory#clear</tt> is replaced by <tt>Context#initialize\_context</tt>.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.0.0
|
data/examples/mice.rb
CHANGED
@@ -53,11 +53,11 @@ class CupSet
|
|
53
53
|
CS.has_neighbors.if [C,D].in{CS[0..-2].zip(CS[1..-1] || [])}.and C.has_mouse.and D.has_mouse
|
54
54
|
|
55
55
|
# A predicate definitely solves a set if there is no ambiguity
|
56
|
-
predicate_for Rubylog::
|
56
|
+
predicate_for Rubylog::Goal, %w(.definitely_solves())
|
57
57
|
T.definitely_solves(CS).if T.any(CS.has_neighbors).and(T.any(CS.has_neighbors.false)).false
|
58
58
|
|
59
59
|
# A trial consist of peeking some cups
|
60
|
-
predicate_for Rubylog::
|
60
|
+
predicate_for Rubylog::Goal, %w(.trial_for())
|
61
61
|
T.trial_for(CS).if T.is{C.in(CS).map{C.peeked.or :true}.inject(:true,&:and)}.and T
|
62
62
|
|
63
63
|
# A set is easy if can be definitely solved by a trial that has not seen all
|
data/examples/tracing.rb
CHANGED
@@ -6,9 +6,9 @@ extend Rubylog::Context
|
|
6
6
|
solve S.is("Hello #{X}!").and X.is("no Trace") do puts S end
|
7
7
|
|
8
8
|
# tracing with on/off
|
9
|
-
Rubylog.trace
|
9
|
+
Rubylog.trace
|
10
10
|
solve S.is("Hello #{X}!").and X.is("Trace") do puts S end
|
11
|
-
Rubylog.trace
|
11
|
+
Rubylog.trace(false)
|
12
12
|
|
13
13
|
# no trace again
|
14
14
|
solve S.is("Hello #{X}!").and X.is("no Trace again") do puts S end
|
@@ -12,14 +12,14 @@ rubylog do
|
|
12
12
|
# !
|
13
13
|
def cut!
|
14
14
|
yield
|
15
|
-
throw :
|
15
|
+
throw :rubylog_cut
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
|
20
|
-
|
20
|
+
primitives_for_goal = primitives_for [::Rubylog::Goal, ::Rubylog::Structure]
|
21
21
|
|
22
|
-
class <<
|
22
|
+
class << primitives_for_goal
|
23
23
|
# Succeeds if both +a+ and +b+ succeeds.
|
24
24
|
def and a, b
|
25
25
|
a.prove { b.prove { yield } }
|
@@ -34,7 +34,7 @@ rubylog do
|
|
34
34
|
# Succeeds if +a+ does not succeed.
|
35
35
|
def false a
|
36
36
|
# catch cuts
|
37
|
-
catch :
|
37
|
+
catch :rubylog_cut do
|
38
38
|
a.prove { return }
|
39
39
|
end
|
40
40
|
yield
|
@@ -97,44 +97,66 @@ rubylog do
|
|
97
97
|
yield
|
98
98
|
end
|
99
99
|
|
100
|
-
end
|
101
100
|
|
102
|
-
# We also implement some of these methods in a prefix style
|
103
|
-
%w(false all iff any one none).each do |fct|
|
104
|
-
# we discard the first argument, which is the context,
|
105
|
-
# because they are the same in any context
|
106
|
-
primitives_for_context.define_singleton_method fct do |_,*args,&block|
|
107
|
-
primitives_for_clause.send(fct, *args, &block)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
class << primitives_for_context
|
112
101
|
# finds every solution of a, and for each solution dereferences all
|
113
102
|
# variables in b if possible and collects the results. Then joins all b's
|
114
103
|
# with .and() and solves the resulted expression.
|
115
|
-
def every
|
116
|
-
|
117
|
-
a
|
118
|
-
|
104
|
+
def every a, b
|
105
|
+
|
106
|
+
# a block that makes a snapshot of b
|
107
|
+
snapshot = nil
|
108
|
+
snapshot = proc do |subterm|
|
109
|
+
case subterm
|
110
|
+
when Proc
|
119
111
|
# save copies of actual variables
|
120
112
|
vars = a.rubylog_variables.map do |v|
|
121
113
|
new_v = Rubylog::Variable.new(v.name)
|
122
114
|
new_v.send :bind_to!, v.value if v.bound?
|
115
|
+
new_v
|
123
116
|
end
|
124
117
|
|
125
118
|
# store them in a closure
|
126
|
-
|
127
|
-
|
119
|
+
proc do
|
120
|
+
subterm.call_with_rubylog_variables vars
|
128
121
|
end
|
129
|
-
|
122
|
+
when Rubylog::Variable
|
130
123
|
# dereference actual variables
|
131
|
-
|
124
|
+
if subterm.bound?
|
125
|
+
subterm.rubylog_dereference.rubylog_clone(&snapshot)
|
126
|
+
else
|
127
|
+
subterm
|
128
|
+
end
|
129
|
+
else
|
130
|
+
subterm
|
132
131
|
end
|
133
|
-
|
132
|
+
end
|
133
|
+
|
134
|
+
# collect resolved b's in an array
|
135
|
+
resolved_bs = []
|
136
|
+
a.prove do
|
137
|
+
resolved_bs << b.rubylog_clone(&snapshot)
|
134
138
|
end
|
135
|
-
|
139
|
+
|
140
|
+
# chain resolved b's together
|
141
|
+
goal = resolved_bs.empty? ? :true : resolved_bs.inject{|a,b|a.and b}
|
142
|
+
|
143
|
+
# match variables in the resolved b's together with variables in a
|
144
|
+
goal = [a.rubylog_variables, goal].rubylog_match_variables[1]
|
145
|
+
|
146
|
+
# prove b's
|
147
|
+
goal.prove { yield }
|
136
148
|
end
|
137
149
|
end
|
138
150
|
|
151
|
+
# We also implement some of these methods in a prefix style
|
152
|
+
%w(false all iff any one none every).each do |fct|
|
153
|
+
# we discard the first argument, which is the context,
|
154
|
+
# because they are the same in any context
|
155
|
+
primitives_for_context.define_singleton_method fct do |_,*args,&block|
|
156
|
+
primitives_for_goal.send(fct, *args, &block)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
139
161
|
end
|
140
162
|
|
@@ -1,19 +1,27 @@
|
|
1
1
|
module Rubylog::CompoundTerm
|
2
|
+
|
3
|
+
# returns a copy of the term, with variables with the same name meade same
|
4
|
+
# objects. Don't care variables are not matched.
|
2
5
|
def rubylog_match_variables
|
3
6
|
vars = []; vars_by_name = {}
|
7
|
+
|
4
8
|
rubylog_clone do |subterm|
|
5
9
|
case subterm
|
6
10
|
when Rubylog::Variable
|
7
11
|
var = subterm
|
8
12
|
|
9
13
|
if var.dont_care?
|
14
|
+
# duplicate don't care variables
|
10
15
|
var.dup
|
11
16
|
else
|
17
|
+
# see if a var with that name already exists
|
12
18
|
new_var = vars_by_name[var.name]
|
13
19
|
if new_var
|
20
|
+
# append guards
|
14
21
|
new_var.guards = new_var.guards + var.guards
|
15
22
|
new_var
|
16
23
|
else
|
24
|
+
# create and add new var
|
17
25
|
new_var = var.dup
|
18
26
|
vars << new_var
|
19
27
|
vars_by_name[var.name] = new_var
|
@@ -22,7 +30,8 @@ module Rubylog::CompoundTerm
|
|
22
30
|
end
|
23
31
|
|
24
32
|
when Rubylog::CompoundTerm
|
25
|
-
|
33
|
+
# save rubylog variables
|
34
|
+
subterm.rubylog_variables=vars
|
26
35
|
subterm
|
27
36
|
else
|
28
37
|
subterm
|
@@ -35,7 +44,8 @@ module Rubylog::CompoundTerm
|
|
35
44
|
#self.class.new attr1.rubylog_deep_dereference
|
36
45
|
#end
|
37
46
|
|
38
|
-
|
47
|
+
attr_accessor :rubylog_variables
|
48
|
+
protected :rubylog_variables=
|
39
49
|
|
40
50
|
|
41
51
|
# This is a general deep-copy generating method
|