porolog 0.0.8 → 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.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/Rakefile +2 -2
- data/bin/porolog +34 -13
- data/coverage/badge.svg +1 -1
- data/coverage/index.html +43822 -25140
- data/doc/Array.html +158 -51
- data/doc/Object.html +2 -2
- data/doc/Porolog.html +1584 -1113
- data/doc/Symbol.html +2 -2
- data/doc/_index.html +40 -56
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +8 -4
- data/doc/index.html +8 -4
- data/doc/method_list.html +424 -248
- data/doc/top-level-namespace.html +1 -1
- data/lib/porolog.rb +184 -61
- data/lib/porolog/arguments.rb +12 -11
- data/lib/porolog/core_ext.rb +27 -9
- data/lib/porolog/error.rb +3 -0
- data/lib/porolog/goal.rb +57 -15
- data/lib/porolog/instantiation.rb +55 -9
- data/lib/porolog/predicate.rb +52 -26
- data/lib/porolog/predicate/builtin.rb +825 -0
- data/lib/porolog/rule.rb +8 -24
- data/lib/porolog/scope.rb +1 -1
- data/lib/porolog/tail.rb +5 -0
- data/lib/porolog/value.rb +3 -3
- data/lib/porolog/variable.rb +29 -11
- data/test/porolog/arguments_test.rb +45 -66
- data/test/porolog/core_ext_test.rb +25 -0
- data/test/porolog/goal_test.rb +86 -9
- data/test/porolog/instantiation_test.rb +36 -0
- data/test/porolog/porolog_test.rb +285 -30
- data/test/porolog/predicate/builtin_test.rb +1340 -0
- data/test/porolog/predicate_test.rb +78 -16
- data/test/porolog/rule_test.rb +19 -0
- data/test/porolog/variable_test.rb +44 -55
- data/test/samples_test.rb +277 -0
- data/test/test_helper.rb +9 -0
- metadata +9 -5
@@ -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
|