porolog 0.0.4 → 1.0.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -5
  3. data/Rakefile +7 -2
  4. data/bin/porolog +58 -1
  5. data/coverage/badge.svg +1 -1
  6. data/coverage/index.html +76733 -2638
  7. data/doc/Array.html +1066 -0
  8. data/doc/Object.html +674 -0
  9. data/doc/Porolog.html +4153 -74
  10. data/doc/Symbol.html +501 -0
  11. data/doc/_index.html +280 -6
  12. data/doc/class_list.html +1 -1
  13. data/doc/file.README.html +34 -39
  14. data/doc/index.html +34 -39
  15. data/doc/method_list.html +1337 -57
  16. data/doc/top-level-namespace.html +4 -2
  17. data/lib/porolog.rb +1144 -4
  18. data/lib/porolog/arguments.rb +28 -24
  19. data/lib/porolog/core_ext.rb +188 -0
  20. data/lib/porolog/error.rb +9 -0
  21. data/lib/porolog/goal.rb +357 -0
  22. data/lib/porolog/instantiation.rb +346 -0
  23. data/lib/porolog/predicate.rb +74 -31
  24. data/lib/porolog/predicate/builtin.rb +825 -0
  25. data/lib/porolog/rule.rb +162 -0
  26. data/lib/porolog/scope.rb +4 -4
  27. data/lib/porolog/tail.rb +57 -0
  28. data/lib/porolog/value.rb +105 -0
  29. data/lib/porolog/variable.rb +325 -0
  30. data/test/porolog/arguments_test.rb +244 -195
  31. data/test/porolog/core_ext_test.rb +290 -0
  32. data/test/porolog/goal_test.rb +891 -0
  33. data/test/porolog/instantiation_test.rb +910 -0
  34. data/test/porolog/porolog_test.rb +2376 -13
  35. data/test/porolog/predicate/builtin_test.rb +1340 -0
  36. data/test/porolog/predicate_test.rb +84 -30
  37. data/test/porolog/rule_test.rb +527 -0
  38. data/test/porolog/scope_test.rb +0 -2
  39. data/test/porolog/tail_test.rb +127 -0
  40. data/test/porolog/value_test.rb +315 -0
  41. data/test/porolog/variable_test.rb +1614 -0
  42. data/test/samples_test.rb +277 -0
  43. data/test/test_helper.rb +115 -0
  44. metadata +34 -7
@@ -0,0 +1,825 @@
1
+ #
2
+ # lib/porolog/predicate/builtin.rb - Plain Old Ruby Objects Prolog Engine -- Builtin Predicates
3
+ #
4
+ # Luis Esteban 29 July 2020
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
10
+ class Predicate
11
+
12
+ # The Porolog::Predicate::Builtin module is a collection of the implementations of the builtin predicates.
13
+ # It is possible to define custom builtin predicates. Each builtin requires a goal and a block,
14
+ # and should return the result of
15
+ # block.call(goal) || false
16
+ # if the predicate is deemed successful; otherwise, it should return false.
17
+ #
18
+ # @author Luis Esteban
19
+ #
20
+ module Builtin
21
+
22
+ # Corresponds to the standard Prolog print predicate.
23
+ # `print` could not be used because of the clash with the Ruby method.
24
+ # It outputs all arguments. If an argument is a variable,
25
+ # then if it is instantiated, its value is output; otherwise its name is output.
26
+ # If the value is an Array, its inspect is output instead.
27
+ # Use:
28
+ # write('X = ', :X, ', Y = ', :Y, "\n")
29
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
30
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
31
+ # @param args [Array<Object>] the arguments to be passed to the provided block.
32
+ # @return [Boolean] whether the goal was satisfied.
33
+ def write(goal, block, *args)
34
+ args = args.map(&:value).map(&:value)
35
+ args = args.map{|arg|
36
+ arg.is_a?(Array) ? arg.inspect : arg
37
+ }
38
+ args = args.map{|arg|
39
+ arg.type == :variable ? arg.to_sym.inspect : arg
40
+ }
41
+ $stdout.print args.join
42
+ block.call(goal) || false
43
+ end
44
+
45
+ # Corresponds to the standard Prolog print and nl predicate.
46
+ # `print` could not be used because of the clash with the Ruby method.
47
+ # It outputs all arguments and a new line. If an argument is a variable,
48
+ # then if it is instantiated, its value is output; otherwise its name is output.
49
+ # If the value is an Array, its inspect is output instead.
50
+ # Use:
51
+ # writenl('X = ', :X, ', Y = ', :Y)
52
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
53
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
54
+ # @param args [Array<Object>] the arguments to be passed to the provided block.
55
+ # @return [Boolean] whether the goal was satisfied.
56
+ def writenl(goal, block, *args)
57
+ args = args.map(&:value).map(&:value)
58
+ args = args.map{|arg|
59
+ arg.is_a?(Array) ? arg.inspect : arg
60
+ }
61
+ args = args.map{|arg|
62
+ arg.type == :variable ? arg.to_sym.inspect : arg
63
+ }
64
+ $stdout.puts args.join
65
+ block.call(goal) || false
66
+ end
67
+
68
+ # Corresponds to the standard Prolog nl predicate.
69
+ # It outputs a newline.
70
+ # Use:
71
+ # nl
72
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
73
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
74
+ # @return [Boolean] whether the goal was satisfied.
75
+ def nl(goal, block)
76
+ $stdout.puts
77
+ block.call(goal) || false
78
+ end
79
+
80
+ # Corresponds to the standard Prolog is predicate.
81
+ # It instantiates a Variable with the result of the provided block.
82
+ # Use:
83
+ # is(:Y, :X) {|x| x + 1 }
84
+ # is(:name, :first, :last) {|first, last| [first, last] }
85
+ # is(:name, :first, :last) {|first, last| "#{first} #{last}" }
86
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
87
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
88
+ # @param variable [Porolog::Variable] the Variable to be instantiated.
89
+ # @param args [Array<Object>] the arguments to be passed to the provided block.
90
+ # @param is_block [Proc] the block provided in the Goal's Arguments.
91
+ # @return [Boolean] whether the goal was satisfied.
92
+ def is(goal, block, variable, *args, &is_block)
93
+ raise NonVariableError, "#{variable.inspect} is not a variable" unless variable.type == :variable
94
+
95
+ result = is_block.call(*args.map(&:value).map(&:value))
96
+
97
+ result && !!variable.instantiate(result) && block.call(goal) || false
98
+ end
99
+
100
+ # Corresponds to the standard Prolog var predicate.
101
+ # It is satisfied if the argument is an uninstantiated variable.
102
+ # Use:
103
+ # var(:X)
104
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
105
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
106
+ # @param variable [Porolog::Variable,Object] the argument to be tested.
107
+ # @return [Boolean] whether the goal was satisfied.
108
+ def var(goal, block, variable)
109
+ variable.value.value.type == :variable && block.call(goal) || false
110
+ end
111
+
112
+ # Corresponds to the standard Prolog nonvar predicate.
113
+ # It is satisfied if the argument is not an uninstantiated variable.
114
+ # Use:
115
+ # nonvar(:X)
116
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
117
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
118
+ # @param variable [Porolog::Variable,Object] the argument to be tested.
119
+ # @return [Boolean] whether the goal was satisfied.
120
+ def nonvar(goal, block, variable)
121
+ variable.value.value.type != :variable && block.call(goal) || false
122
+ end
123
+
124
+ # Corresponds to the standard Prolog atom predicate.
125
+ # It is satisfied if the argument is a String.
126
+ # Use:
127
+ # atom(:X)
128
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
129
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
130
+ # @param variable [Porolog::Variable,Object] the argument to be tested.
131
+ # @return [Boolean] whether the goal was satisfied.
132
+ def atom(goal, block, variable)
133
+ variable.value.value.is_a?(String) && block.call(goal) || false
134
+ end
135
+
136
+ # Corresponds to the standard Prolog atomic predicate.
137
+ # It is satisfied if the argument is a String or an Integer.
138
+ # Use:
139
+ # atomic(:X)
140
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
141
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
142
+ # @param variable [Porolog::Variable,Object] the argument to be tested.
143
+ # @return [Boolean] whether the goal was satisfied.
144
+ def atomic(goal, block, variable)
145
+ variable.value.value.type == :atomic && block.call(goal) || false
146
+ end
147
+
148
+ # Corresponds to the standard Prolog integer predicate.
149
+ # It is satisfied if the argument is an Integer.
150
+ # Use:
151
+ # integer(:X)
152
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
153
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
154
+ # @param variable [Porolog::Variable,Object] the argument to be tested.
155
+ # @return [Boolean] whether the goal was satisfied.
156
+ def integer(goal, block, variable)
157
+ variable.value.value.is_a?(Integer) && block.call(goal) || false
158
+ end
159
+
160
+ # Corresponds to the standard Prolog == predicate.
161
+ # It is satisfied if:
162
+ # - it is provided with two values (or instantiated Variables) that are equal, or
163
+ # - it is provided with two uninstantiaed Variables that are bound to each other.
164
+ # Variables are not instantiated; however, if they are uninstantiated, they are
165
+ # temporarily instantiated to a unique value to see if they are bound in some way.
166
+ # Use:
167
+ # eq(:X, :Y)
168
+ # eq(:name, ['Sam', 'Smith'])
169
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
170
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
171
+ # @param x [Object] the left hand side of the equality.
172
+ # @param y [Object] the right hand side of the equality.
173
+ # @return [Boolean] whether the goal was satisfied.
174
+ def eq(goal, block, x, y)
175
+ x = x.value.value
176
+ y = y.value.value
177
+
178
+ case [x.type, y.type]
179
+ when [:variable, :variable]
180
+ equal = false
181
+ temporary_instantiation = x.instantiate UNIQUE_VALUE
182
+ if temporary_instantiation
183
+ equal = y.value.value == UNIQUE_VALUE
184
+ temporary_instantiation.remove
185
+ end
186
+ equal
187
+ else
188
+ x == y
189
+ end && block.call(goal) || false
190
+ end
191
+
192
+ # Corresponds to a synthesis of the standard Prolog == and is predicates.
193
+ # The left hand side (i.e. the first parameter) must be a variable.
194
+ # It compares equality if the left hand side is instantiated;
195
+ # otherwise, it instantiates the left hand side to the right hand side.
196
+ # It is satisfied if:
197
+ # - the values are equal, or
198
+ # - the variable can successfully be instantiated to the right hand side.
199
+ # Use:
200
+ # is_eq(:X, :Y)
201
+ # is_eq(:name, ['Sam', 'Smith'])
202
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
203
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
204
+ # @param x [Porolog::Variable] the left hand side of the equality / assignment.
205
+ # @param y [Object] the right hand side of the equality / assignment.
206
+ # @return [Boolean] whether the goal was satisfied.
207
+ def is_eq(goal, block, x, y)
208
+ return false unless x.type == :variable
209
+ return block.call(goal) if x.value.value == y.value.value
210
+
211
+ !!x.instantiate(y) && block.call(goal) || false
212
+ end
213
+
214
+ # Allows a plain Ruby block to be executed as a goal.
215
+ # It is assumed to be successful unless evaluates to :fail .
216
+ # Use:
217
+ # ruby(:X, :Y, :Z) {|x, y, z| csv << [x, y, z] }
218
+ # ruby { $stdout.print '.' }
219
+ # ruby {
220
+ # $stdout.puts 'Forcing backtracking ...'
221
+ # :fail
222
+ # }
223
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
224
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
225
+ # @param args [Array<Object>] the arguments to be passed to the provided block.
226
+ # @param ruby_block [Proc] the block provided in the Goal's Arguments.
227
+ # @return [Boolean] whether the goal was satisfied.
228
+ def ruby(goal, block, *args, &ruby_block)
229
+ (ruby_block.call(goal, *args.map(&:value).map(&:value)) != :fail) && block.call(goal) || false
230
+ end
231
+
232
+ # Corresponds to the standard Prolog != predicate.
233
+ # It is satisfied if:
234
+ # - it is provided with two values (or instantiated Variables) that are unequal, or
235
+ # - it is provided with two uninstantiaed Variables that are not bound to each other.
236
+ # Variables are not instantiated; however, if they are uninstantiated, they are
237
+ # temporarily instantiated to a unique value to see if they are bound in some way.
238
+ # Use:
239
+ # noteq(:X, :Y)
240
+ # noteq(:name, ['Sam', 'Smith'])
241
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
242
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
243
+ # @param x [Object] the left hand side of the inequality.
244
+ # @param y [Object] the right hand side of the inequality.
245
+ # @return [Boolean] whether the goal was satisfied.
246
+ def noteq(goal, block, x, y)
247
+ x = x.value.value
248
+ y = y.value.value
249
+
250
+ case [x.type, y.type]
251
+ when [:variable, :variable]
252
+ equal = false
253
+ temporary_instantiation = x.instantiate UNIQUE_VALUE
254
+ if temporary_instantiation
255
+ equal = y.value.value == x.value.value
256
+ temporary_instantiation.remove
257
+ end
258
+ !equal
259
+ else
260
+ x != y
261
+ end && block.call(goal) || false
262
+ end
263
+
264
+ # This does not really correspond to a standard Prolog predicate.
265
+ # It implements a basic constraint mechanism.
266
+ # The Variable is instantiated (if possible) to all possible values provide
267
+ # except for all exclusions.
268
+ # Further, the exclusions are checked for collective uniqueness.
269
+ # Use:
270
+ # is_noteq(:digit, (0..9).to_a, :second_digit, :fifth_digit)
271
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
272
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
273
+ # @param variable [Porolog::Variable] the variable being instantiated.
274
+ # @param all_values [Array<Object>] all possible values (i.e. the domain) of the variable.
275
+ # @param exclusions [Array<Object>] mutually exclusive values (or variables), which the variable cannot be.
276
+ # @return [Boolean] whether the goal was satisfied.
277
+ def is_noteq(goal, block, variable, all_values, *exclusions)
278
+ return false unless variable.type == :variable
279
+
280
+ all_values = all_values.map(&:value).map(&:value)
281
+ exclusions = exclusions.map(&:value).map(&:value)
282
+
283
+ possible_values = goal[anonymous]
284
+
285
+ if exclusions.uniq.size == exclusions.size
286
+ !!possible_values.instantiate(all_values - exclusions) && Predicate.call_builtin(:member, goal, block, variable, possible_values) || false
287
+ else
288
+ false
289
+ end
290
+ end
291
+
292
+ # Corresponds to the standard Prolog < predicate.
293
+ # It is satisfied if:
294
+ # - it is provided with two values (or instantiated Variables) where the first is less than the second.
295
+ # Variables are not instantiated.
296
+ # Use:
297
+ # less(:X, :Y)
298
+ # less(:X, 3)
299
+ # less(9, :Y)
300
+ # less(:name, 'max')
301
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
302
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
303
+ # @param x [Object] the left hand side of the inequality.
304
+ # @param y [Object] the right hand side of the inequality.
305
+ # @return [Boolean] whether the goal was satisfied.
306
+ def less(goal, block, x, y)
307
+ x = x.value.value
308
+ y = y.value.value
309
+
310
+ if [x.type, y.type].include?(:variable)
311
+ false
312
+ else
313
+ x < y
314
+ end && block.call(goal) || false
315
+ end
316
+
317
+ # Corresponds to the standard Prolog > predicate.
318
+ # It is satisfied if:
319
+ # - it is provided with two values (or instantiated Variables) where the first is greater than the second.
320
+ # Variables are not instantiated.
321
+ # Use:
322
+ # gtr(:X, :Y)
323
+ # gtr(:X, 3)
324
+ # gtr(9, :Y)
325
+ # gtr(:name, 'max')
326
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
327
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
328
+ # @param x [Object] the left hand side of the inequality.
329
+ # @param y [Object] the right hand side of the inequality.
330
+ # @return [Boolean] whether the goal was satisfied.
331
+ def gtr(goal, block, x, y)
332
+ x = x.value.value
333
+ y = y.value.value
334
+
335
+ if [x.type, y.type].include?(:variable)
336
+ false
337
+ else
338
+ x > y
339
+ end && block.call(goal) || false
340
+ end
341
+
342
+ # Corresponds to the standard Prolog <= predicate.
343
+ # It is satisfied if:
344
+ # - it is provided with two values (or instantiated Variables) where the first is less than or equal to the second, or
345
+ # - it is provided with two uninstantiaed Variables that are bound to each other.
346
+ # Variables are not instantiated (except temporarily to test if they are bound).
347
+ # Use:
348
+ # lesseq(:X, :Y)
349
+ # lesseq(:X, 3)
350
+ # lesseq(9, :Y)
351
+ # lesseq(:name, 'max')
352
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
353
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
354
+ # @param x [Object] the left hand side of the inequality.
355
+ # @param y [Object] the right hand side of the inequality.
356
+ # @return [Boolean] whether the goal was satisfied.
357
+ def lesseq(goal, block, x, y)
358
+ x = x.value.value
359
+ y = y.value.value
360
+
361
+ case [x.type, y.type]
362
+ when [:variable, :variable]
363
+ equal = false
364
+ temporary_instantiation = x.instantiate UNIQUE_VALUE
365
+ if temporary_instantiation
366
+ equal = y.value.value == UNIQUE_VALUE
367
+ temporary_instantiation.remove
368
+ end
369
+ equal
370
+ else
371
+ if [x.type, y.type].include?(:variable)
372
+ false
373
+ else
374
+ x <= y
375
+ end
376
+ end && block.call(goal) || false
377
+ end
378
+
379
+ # Corresponds to the standard Prolog >= predicate.
380
+ # It is satisfied if:
381
+ # - it is provided with two values (or instantiated Variables) where the first is greater than or equal to the second, or
382
+ # - it is provided with two uninstantiaed Variables that are bound to each other.
383
+ # Variables are not instantiated (except temporarily to test if they are bound).
384
+ # Use:
385
+ # gtreq(:X, :Y)
386
+ # gtreq(:X, 3)
387
+ # gtreq(9, :Y)
388
+ # gtreq(:name, 'max')
389
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
390
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
391
+ # @param x [Object] the left hand side of the inequality.
392
+ # @param y [Object] the right hand side of the inequality.
393
+ # @return [Boolean] whether the goal was satisfied.
394
+ def gtreq(goal, block, x, y)
395
+ x = x.value.value
396
+ y = y.value.value
397
+
398
+ case [x.type, y.type]
399
+ when [:variable, :variable]
400
+ equal = false
401
+ temporary_instantiation = x.instantiate UNIQUE_VALUE
402
+ if temporary_instantiation
403
+ equal = y.value.value == UNIQUE_VALUE
404
+ temporary_instantiation.remove
405
+ end
406
+ equal
407
+ else
408
+ if [x.type, y.type].include?(:variable)
409
+ false
410
+ else
411
+ x >= y
412
+ end
413
+ end && block.call(goal) || false
414
+ end
415
+
416
+ # Corresponds to the standard Prolog length predicate.
417
+ # It is satisfied if:
418
+ # - it is provided with an Array and an Integer where the Integer corresponds to the length of the Array,
419
+ # - it is provided with an Array and a Variable and the Variable is successfully instantiated to the length of the Array, or
420
+ # - it is provided with a Variable and an Integer where the Variable is successfully instantiated to an of anonymous variables.
421
+ # Use:
422
+ # length([1,2,3,4], 4)
423
+ # length([1,2,3,4], :Length)
424
+ # length(:L, 4)
425
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
426
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
427
+ # @param list [Array,Porolog::Variable] the list.
428
+ # @param length [Integer,Porolog::Variable] the length of the list.
429
+ # @return [Boolean] whether the goal was satisfied.
430
+ def length(goal, block, list, length)
431
+ list = list.value.value
432
+ length = length.value.value
433
+
434
+ case [list.type, length.type]
435
+ when [:array, :atomic]
436
+ list.length == length
437
+ when [:variable, :atomic]
438
+ list.instantiate(Array.new(length){goal[_]})
439
+ when [:array, :variable]
440
+ length.instantiate(list.length)
441
+ else
442
+ false
443
+ end && block.call(goal) || false
444
+ end
445
+
446
+ # Does not correspond to a standard Prolog predicate.
447
+ # This is a convenience Predicate to allow efficient control of
448
+ # iteration as well as range comparison.
449
+ # Use:
450
+ # between(5, 0, 9)
451
+ # between(:N, 0, 9)
452
+ # between(:N, :Upper, :Lower)
453
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
454
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
455
+ # @param variable [String,Integer,Porolog::Variable] the intermediate value or variable.
456
+ # @param lower [String,Integer] the lower bound of the iteration or range.
457
+ # @param upper [String,Integer] the upper bound of the iteration or range.
458
+ # @return [Boolean] whether the goal was satisfied.
459
+ def between(goal, block, variable, lower, upper)
460
+ variable = variable.value.value
461
+ lower = lower.value.value
462
+ upper = upper.value.value
463
+
464
+ case [variable.type, lower.type, upper.type]
465
+ when [:atomic, :atomic, :atomic]
466
+ (lower..upper) === variable && block.call(goal) || false
467
+
468
+ when [:atomic, :variable, :variable]
469
+ satisfied = false
470
+ lower_instantiation = lower.instantiate(variable)
471
+ upper_instantiation = lower_instantiation && upper.instantiate(variable)
472
+ upper_instantiation && block.call(goal) && (satisfied = true)
473
+ upper_instantiation&.remove
474
+ lower_instantiation&.remove
475
+ satisfied
476
+
477
+ when [:atomic, :atomic, :variable]
478
+ satisfied = false
479
+ if variable >= lower
480
+ upper_instantiation = upper.instantiate(variable)
481
+ upper_instantiation && block.call(goal) && (satisfied = true)
482
+ upper_instantiation&.remove
483
+ end
484
+ satisfied
485
+
486
+ when [:atomic, :variable, :atomic]
487
+ satisfied = false
488
+ if variable <= upper
489
+ lower_instantiation = lower.instantiate(variable)
490
+ lower_instantiation && block.call(goal) && (satisfied = true)
491
+ lower_instantiation&.remove
492
+ end
493
+ satisfied
494
+
495
+ when [:variable, :atomic, :atomic]
496
+ satisfied = false
497
+ (lower..upper).each do |i|
498
+ instantiation = variable.instantiate(i)
499
+ instantiation && block.call(goal) && (satisfied = true) || false
500
+ instantiation&.remove
501
+ return satisfied if goal.terminated?
502
+ end
503
+ satisfied
504
+
505
+ else
506
+ false
507
+ end
508
+ end
509
+
510
+ # Corresponds to the standard Prolog member predicate.
511
+ # This implements the usual operation of member but also
512
+ # provides the ability to generate lists that contain the
513
+ # provided element, even if the element is an uninstantiated variable.
514
+ # Use:
515
+ # member(3, [1,2,3,4,5])
516
+ # member(['Chris','Smith'], [['Foo','Bar'],['Boo','Far'],['Chris','Smith']])
517
+ # member(:X, [1,2,3,4,5])
518
+ # member(:X, :Y)
519
+ # member(:X, :Y, 5)
520
+ # member(3, :Y, 10)
521
+ # member(['Chris','Smith'], :Names, 16)
522
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
523
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
524
+ # @param element [Object] the element to be found in the provided or generated list.
525
+ # @param list [Array,Porolog::Variable] the provided or generated list that is to contain the element.
526
+ # @param limit [Integer] the number of lists to generate.
527
+ # @return [Boolean] whether the goal was satisfied.
528
+ def member(goal, block, element, list, limit = 100)
529
+ element_value = element.value.value
530
+ list = list.value.value
531
+
532
+ case [element_value.type, list.type]
533
+ when [:atomic, :array], [:array, :array]
534
+ satisfied = false
535
+ list.each do |i|
536
+ unifications = unify(element_value, i, goal)
537
+ if unifications
538
+ instantiations = instantiate_unifications(unifications)
539
+ if instantiations
540
+ block.call(goal) && (satisfied = true)
541
+ instantiations.each(&:remove)
542
+ end
543
+ end
544
+
545
+ return satisfied if goal.terminated?
546
+ end
547
+ satisfied
548
+
549
+ when [:variable, :array]
550
+ satisfied = false
551
+ list.each do |i|
552
+ instantiation = element_value.instantiate(i)
553
+ instantiation && block.call(goal) && (satisfied = true)
554
+ instantiation&.remove
555
+ return satisfied if goal.terminated?
556
+ satisfied = true
557
+ end
558
+ satisfied
559
+
560
+ when [:variable, :variable], [:atomic, :variable], [:array, :variable]
561
+ satisfied = false
562
+ limit.times do |i|
563
+ instantiation = list.instantiate([*Array.new(i){goal[_]}, element, UNKNOWN_TAIL])
564
+ instantiation && block.call(goal) && (satisfied = true)
565
+ instantiation&.remove
566
+ return satisfied if goal.terminated?
567
+ end
568
+ satisfied
569
+
570
+ else
571
+ false
572
+ end
573
+ end
574
+
575
+ # Corresponds to the standard Prolog append predicate.
576
+ # This implements the usual operation of member but also
577
+ # provides the ability instantiate uninstantiated arguments
578
+ # as an instnatiation of the concatenation of the first two arguments.
579
+ # Use:
580
+ # append([1,2,3], [4,5,6], [1,2,3,4,5,6])
581
+ # append([1,2,3], [4,5,6], :C)
582
+ # append([1,2,3], :B, [1,2,3,4,5,6])
583
+ # append(:A, [4,5,6], [1,2,3,4,5,6])
584
+ # append(:A, :B, [1,2,3,4,5,6])
585
+ # append([1,2,3], :B, :C)
586
+ # append(:A, [4,5,6], :C)
587
+ # append(:A, :B, :C)
588
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
589
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
590
+ # @param front [Array,Porolog::Variable] the front portion of the combined front_back argument.
591
+ # @param back [Array,Porolog::Variable] the back portion of the combined front_back argument.
592
+ # @param front_back [Array,Porolog::Variable] the combined argument of the front and back arguments.
593
+ # @return [Boolean] whether the goal was satisfied.
594
+ def append(goal, block, front, back, front_back)
595
+ front = front.value.value
596
+ back = back.value.value
597
+ front_back = front_back.value.value
598
+
599
+ case [front.type, back.type, front_back.type]
600
+ when [:array, :array, :array]
601
+ satisfied = false
602
+ if front.length + back.length == front_back.length
603
+ unifications = unify(front + back, front_back, goal)
604
+ instantiations = instantiate_unifications(unifications) if unifications
605
+ instantiations && block.call(goal) && (satisfied = true)
606
+ instantiations&.each(&:remove)
607
+ end
608
+ satisfied
609
+
610
+ when [:array, :array, :variable]
611
+ satisfied = false
612
+ unifications = unify(front + back, front_back, goal)
613
+ instantiations = instantiate_unifications(unifications) if unifications
614
+ instantiations && block.call(goal) && (satisfied = true)
615
+ instantiations&.each(&:remove)
616
+ satisfied
617
+
618
+ when [:array, :variable, :array]
619
+ satisfied = false
620
+ if front.length <= front_back.length
621
+ expected_front = front_back[0...front.length]
622
+ expected_back = front_back[front.length..-1]
623
+
624
+ unifications = unify(front, expected_front, goal)
625
+ unifications += unify(back, expected_back, goal) if unifications
626
+ instantiations = instantiate_unifications(unifications) if unifications
627
+ instantiations && block.call(goal) && (satisfied = true)
628
+ instantiations&.each(&:remove)
629
+ end
630
+ satisfied
631
+
632
+ when [:variable, :array, :array]
633
+ satisfied = false
634
+ if back.length <= front_back.length
635
+ expected_front = front_back[0...-back.length]
636
+ expected_back = front_back[-back.length..-1]
637
+
638
+ unifications = unify(front, expected_front, goal)
639
+ unifications += unify(back, expected_back, goal) if unifications
640
+ instantiations = instantiate_unifications(unifications) if unifications
641
+ instantiations && block.call(goal) && (satisfied = true)
642
+ instantiations&.each(&:remove)
643
+ end
644
+ satisfied
645
+
646
+ when [:variable, :variable, :array]
647
+ satisfied = false
648
+ (front_back.length + 1).times do |i|
649
+ expected_front = front_back[0...i]
650
+ expected_back = front_back[i..-1]
651
+
652
+ unifications = unify(front, expected_front, goal)
653
+ unifications += unify(back, expected_back, goal) if unifications
654
+ instantiations = instantiate_unifications(unifications) if unifications
655
+ instantiations && block.call(goal) && (satisfied = true)
656
+ instantiations&.each(&:remove)
657
+ return satisfied if goal.terminated?
658
+ end
659
+ satisfied
660
+
661
+ when [:array, :variable, :variable]
662
+ satisfied = false
663
+ unifications = unify(front / back, front_back, goal)
664
+ instantiations = instantiate_unifications(unifications) if unifications
665
+ instantiations && block.call(goal) && (satisfied = true)
666
+ instantiations&.each(&:remove)
667
+ satisfied
668
+
669
+ when [:variable, :array, :variable]
670
+ satisfied = false
671
+ instantiation_head = front_back.instantiate(front, nil, :flathead)
672
+ instantiation_tail = front_back.instantiate(back, nil, :flattail)
673
+ instantiations = [instantiation_head, instantiation_tail].compact
674
+ instantiations = nil if instantiations.empty?
675
+ instantiations && block.call(goal) && (satisfied = true)
676
+ instantiations&.each(&:remove)
677
+ satisfied
678
+
679
+ when [:variable, :variable, :variable]
680
+ satisfied = false
681
+ instantiation_head = front_back.instantiate(front, nil, :flathead)
682
+ instantiation_tail = front_back.instantiate(back, nil, :flattail)
683
+ instantiations = [instantiation_head, instantiation_tail].compact
684
+ instantiations = nil if instantiations.empty?
685
+ instantiations && block.call(goal) && (satisfied = true)
686
+ instantiations&.each(&:remove)
687
+ satisfied
688
+
689
+ else
690
+ false
691
+ end
692
+ end
693
+
694
+ # Corresponds to the standard Prolog permutation predicate.
695
+ # It not only returns whether one list is a permutation of the other
696
+ # but also can generate permutations.
697
+ # Use:
698
+ # permutation([3,1,2,4], [1,2,3,4])
699
+ # permutation([3,:A,2,4], [1,2,3,4])
700
+ # permutation([3,1,2,4], [1,2,:C,4])
701
+ # permutation([3,1,:B,4], [1,2,:C,4])
702
+ # permutation([3,1,2,4], :Q)
703
+ # permutation(:P, [1,2,3,4])
704
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
705
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
706
+ # @param list1 [Array,Porolog::Variable] the first list.
707
+ # @param list2 [Array,Porolog::Variable] the second list.
708
+ # @return [Boolean] whether the goal was satisfied.
709
+ def permutation(goal, block, list1, list2)
710
+ # TODO: Detect and deal with tails
711
+ # E.g. permutation([:H]/:T, [1,...])
712
+ list1 = list1.value.value
713
+ list2 = list2.value.value
714
+
715
+ case [list1.type, list2.type]
716
+ when [:array, :array]
717
+ satisfied = false
718
+ case [list1.variables.empty?, list2.variables.empty?]
719
+ when [true, true]
720
+ list1 = list1.sort_by(&:inspect)
721
+ list2 = list2.sort_by(&:inspect)
722
+
723
+ unifications = unify(list1, list2, goal)
724
+ instantiations = instantiate_unifications(unifications) if unifications
725
+ instantiations && block.call(goal) && (satisfied = true)
726
+ instantiations&.each(&:remove)
727
+
728
+ when [false, true], [false, false]
729
+ list2.permutation do |p|
730
+ unifications = unify(list1, p, goal)
731
+ instantiations = nil
732
+ instantiations = instantiate_unifications(unifications) if unifications
733
+ instantiations && block.call(goal) && (satisfied = true)
734
+ instantiations&.each(&:remove)
735
+ return satisfied if goal.terminated?
736
+ end
737
+
738
+ when [true, false]
739
+ list1.permutation do |p|
740
+ unifications = unify(list2, p, goal)
741
+ instantiations = nil
742
+ instantiations = instantiate_unifications(unifications) if unifications
743
+ instantiations && block.call(goal) && (satisfied = true)
744
+ instantiations&.each(&:remove)
745
+ return satisfied if goal.terminated?
746
+ end
747
+ end
748
+ satisfied
749
+
750
+ when [:array, :variable]
751
+ satisfied = false
752
+ list1.permutation do |p|
753
+ unifications = unify(p, list2, goal)
754
+ instantiations = instantiate_unifications(unifications) if unifications
755
+ instantiations && block.call(goal) && (satisfied = true)
756
+ instantiations&.each(&:remove)
757
+ return satisfied if goal.terminated?
758
+ end
759
+ satisfied
760
+
761
+ when [:variable, :array]
762
+ satisfied = false
763
+ list2.permutation do |p|
764
+ unifications = unify(list1, p, goal)
765
+ instantiations = nil
766
+ instantiations = instantiate_unifications(unifications) if unifications
767
+ instantiations && block.call(goal) && (satisfied = true)
768
+ instantiations&.each(&:remove)
769
+ return satisfied if goal.terminated?
770
+ end
771
+ satisfied
772
+
773
+ else
774
+ false
775
+ end
776
+ end
777
+
778
+ # Corresponds to the standard Prolog reverse predicate.
779
+ # It returns whether the lists are a reversal of each other,
780
+ # or otherwise generates a reversed list.
781
+ # Use:
782
+ # reverse([1,2,3,4], [4,3,2,1])
783
+ # reverse([1,:A,3,4], [4,3,2,1])
784
+ # reverse([1,2,3,4], [4,:B,2,1])
785
+ # reverse([1,:A,3,4], [4,:B,2,1])
786
+ # reverse(:L, [4,3,2,1])
787
+ # reverse([1,2,3,4], :L)
788
+ # @param goal [Porolog::Goal] the Goal to satisfy the Predicate.
789
+ # @param block [Proc] the continuation of solving to call if this Predicate is satisfied.
790
+ # @param list1 [Array,Porolog::Variable] the first list.
791
+ # @param list2 [Array,Porolog::Variable] the second list.
792
+ # @return [Boolean] whether the goal was satisfied.
793
+ def reverse(goal, block, list1, list2)
794
+ # TODO: Detect and deal with tails
795
+ # E.g. reverse([:H]/:T, [1,...])
796
+ list1 = list1.value.value
797
+ list2 = list2.value.value
798
+
799
+ case [list1.type, list2.type]
800
+ when [:array, :array], [:variable, :array]
801
+ satisfied = false
802
+ unifications = unify(list1, list2.reverse, goal)
803
+ instantiations = instantiate_unifications(unifications) if unifications
804
+ instantiations && block.call(goal) && (satisfied = true)
805
+ instantiations&.each(&:remove)
806
+ satisfied
807
+
808
+ when [:array, :variable]
809
+ satisfied = false
810
+ unifications = unify(list1.reverse, list2, goal)
811
+ instantiations = instantiate_unifications(unifications) if unifications
812
+ instantiations && block.call(goal) && (satisfied = true)
813
+ instantiations&.each(&:remove)
814
+ satisfied
815
+
816
+ else
817
+ false
818
+ end
819
+ end
820
+
821
+ end
822
+
823
+ end
824
+
825
+ end