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 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 additions.
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.0pre1'
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
- === Data types
43
+ == Data types
29
44
 
30
- Rubylog is similar to Prolog, but there are quite a few differences. In Rubylog,
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 variables whose name starts with +ANY...+ (case-insensitive) is a don't-care
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, however, in Rubylog, splats are not limited
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
- === Predicates
52
- As in prolog, predicates are the buinding blocks of your program. However, the arguments are in a different order than they are in prolog:
65
+ [1, *X, 5]
66
+ [*A, *B]
53
67
 
54
- predicate_for String, ".likes()"
55
- 'John'.likes!('beer')
68
+ Currently you cannot use hashes as terms.
56
69
 
57
- which would be <tt>likes('John','beer').</tt> in prolog. In Rubylog, predicates must be declared. The string indicating the predicate syntax is <tt>".likes()"</tt>. The format is <tt>:asdf .asdf .asdf() .asdf(,) .asdf(,,)</tt> for predicates with 0,1,2,3,4 arguments.
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
- You can assert a rule with the +if+ method:
73
+ 'John'.likes('beer')
60
74
 
61
- predicate_for String, ".drinks() .has()"
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
- This would be +drinks(X,Y) :- has(X,Y), likes(X,Y)+ in Prolog.
77
+ In Rubylog, predicates must be declared with +predicate_for+:
65
78
 
66
- You can assert facts with <tt>if(:true)</tt>, or, as a shorthand you can use the bang
67
- syntax:
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
- 'John'.likes! 'milk'
83
+ Declaring a predicate with arguments gives you three methods on the subject class:
70
84
 
71
- Bang assertions return their first argument (which is <tt>'John'</tt> in this case), so they can be chained:
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
- 'John'.likes!('beer').has!('beer')
90
+ === Nullary predicates
74
91
 
75
- You can also use +unless+:
92
+ Nullary predicates are symbols, and they have to be declared with +predicate+:
76
93
 
77
- predicate_for String, ".good .bad"
78
- A.good.unless A.bad
94
+ predicate ":asdf"
95
+ :asdf
79
96
 
80
- Nullary predicates are symbols, similar to Prolog:
81
97
 
82
- 'John'.drinks('beer').if :false.and(:cut!).or(:true)
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
- === Built-in predicates
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
- Some built-in predicates and their Prolog equivalents:
108
+ 'John'.likes!('beer').has!('beer')
89
109
 
90
- Rubylog Prolog
91
- ------- ------
92
- :true true
93
- :fail fail
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
- There are some new ones:
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 see reference of these in <tt>lib/rubylog/builtins/logic.rb</tt> and <tt>lib/rubylog/builtins/term.rb</tt>
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
- === Unification
127
+ == Unification
112
128
 
113
- In Rubylog, unification works quite the same in Prolog, with the +is+ functor.
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 } # outputs "asdf"
129
- A[/x/].in(["asdf","xyz"]).each { p A } # outputs "xyz"
130
- A[thats < 5].in([4,5,6]).each { p A } # outputs 4
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
- === Moving between Ruby and Rubylog
133
- ==== Running a query
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
- If you want to run a query, you have many different syntaxes:
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
- ==== Enumerations
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
- ==== Procs as predicates
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
- ==== Procs as functions
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
- ==== The two modes of Rubylog
200
+ === Variables in blocks
172
201
 
173
- Rubylog has two modes, DSL and native. DSL code is executed only once
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
- In dsl mode, variables are +Rubylog::Variable+ objects. In native mode,
182
- variables are substituted with their respecitve value (or +nil+ if they are not
183
- bound).
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
- === Rubylog as a test suite
214
+ == Rspec integration
189
215
 
190
- You can write simple tests using the +check+ method:
216
+ Rubylog can integrate with RSpec. This enables you to use variables and predicates in specs.
191
217
 
192
- theory do
193
- check :true.or :false
194
- check A.is(3).and A.in [1,2,3]
195
- check { 5+5 == 10 }
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
- You sould put this file in <tt>"./logic/something\_logic.rb"</tt>. Then you can run it
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
- rubylog logic/something_logic.rb
228
+ check 5.is(5)
202
229
 
203
- Or you can run all files in <tt>logic/**/*\_logic.rb</tt> with
230
+ == Built-in predicates
204
231
 
205
- rubylog
232
+ Some built-in predicates and their Prolog equivalents:
206
233
 
207
- === Other built-in libraries
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
- ==== File system
246
+ There are some new ones which do not exist in prolog.
210
247
 
211
- You can make some queries on the file system:
248
+ * <tt>.not\_in()</tt> is the negation of <tt>.in()</tt>.
212
249
 
213
- require "rubylog/builtins/file_system"
214
250
 
215
- theory do
216
- check "README".filename_in "."
251
+ === Quantifiers
217
252
 
218
- X.dirname_in(".").each { puts X }
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
- ==== Reflection
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
- You can make some metaprogramming with Rubylog
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
- require "rubylog/builtins/reflection"
280
+ check "README".filename_in "."
281
+ check "./README".file_in "."
228
282
 
229
- theory do
230
- functor_for String, :likes
283
+ X.dirname_in(".").each { puts X }
231
284
 
232
- check "John".likes("Jane").structure(:likes, ["John", "Jane"])
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> is removed. Use <tt>Kernel#Rubylog</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>. If you use
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::Clause</tt>
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.0pre1
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::Clause, %w(.definitely_solves())
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::Clause, %w(.trial_for())
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!(false)
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
@@ -1,6 +1,6 @@
1
1
  rubylog do
2
2
 
3
- class << primitives_for ::Rubylog::Clause
3
+ class << primitives_for [::Rubylog::Structure, ::Rubylog::Goal]
4
4
  # succeeds if A succeeds. At backtracking, solves B
5
5
  def ensure a, b
6
6
  begin
@@ -12,14 +12,14 @@ rubylog do
12
12
  # !
13
13
  def cut!
14
14
  yield
15
- throw :cut
15
+ throw :rubylog_cut
16
16
  end
17
17
  end
18
18
 
19
19
 
20
- primitives_for_clause = primitives_for [::Rubylog::Clause, ::Rubylog::Structure]
20
+ primitives_for_goal = primitives_for [::Rubylog::Goal, ::Rubylog::Structure]
21
21
 
22
- class << primitives_for_clause
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 :cut do
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 _, a, b
116
- c = []
117
- a.prove do
118
- if b.is_a? Proc
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
- b_resolved = proc do
127
- b.call_with_rubylog_variables vars
119
+ proc do
120
+ subterm.call_with_rubylog_variables vars
128
121
  end
129
- else
122
+ when Rubylog::Variable
130
123
  # dereference actual variables
131
- b_resolved = b.rubylog_deep_dereference
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
- c << b_resolved
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
- c.inject(:true){|a,b|a.and b}.solve { yield }
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
- subterm.instance_variable_set :"@rubylog_variables", vars
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
- attr_reader :rubylog_variables
47
+ attr_accessor :rubylog_variables
48
+ protected :rubylog_variables=
39
49
 
40
50
 
41
51
  # This is a general deep-copy generating method