oakproof 0.7.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f15b0ead7b7f0d5f1dade59eb2a777769b8bc50f006e976fe99befea8aeae4b9
4
+ data.tar.gz: f49f7228719fa47a580bfeef65c3b1139f721afbd24f059d7ede21e6e2a6447b
5
+ SHA512:
6
+ metadata.gz: 9a57aedc97b65aef33feae93b2afd8e3bba209fd8f7ab6a012dd316fec287c086279caa676fd293432a4d57e7df819a4d223543a763eae14fefaf8ab5a8abbd0
7
+ data.tar.gz: c0091be99a7d8f7370b4f61c2d5b965a8076a4bb1d09f1661fab8cef4e7a3db818c4e5aa63f522c32d25e769d419747ee041bdbbb2f4cde50299ebd84720e6a9
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ E is an equational theorem prover created and maintained by Stephan Schulz (<schulz@eprover.org>), with the help of several contributors.
2
+
3
+ E is free software, developed and distributed under the GNU General Public License.
4
+
5
+ The E homepage, including source code for E and further licensing information, can be found at <http://www.eprover.org>.
data/oak ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'src/oak.rb'
data/src/bindings.rb ADDED
@@ -0,0 +1,464 @@
1
+ =begin
2
+
3
+ This class supports "scopes" and "blocks".
4
+
5
+ Scopes are tracked automatically, while blocks are set manually.
6
+
7
+ When a variable is bound, its scope begins. Whenever it is used, its scope is
8
+ extended through that point. Its scope can end anywhere after its last use, as
9
+ long as it is (1) before the block it was bound in ends, and (2) before it is
10
+ rebound.
11
+
12
+ Blocks are set with begin_block and end_block.
13
+
14
+ Scopes cannot interleave blocks. That is, a scope cannot begin outside a block
15
+ and end inside it, or begin inside a block and end outside it.
16
+
17
+ This guarantees that if you bind a variable outside a block, and extend its
18
+ scope into the block (by using it in the block), it will "mean" the same thing
19
+ for the whole block.
20
+
21
+ Definitions are handled automatically with internal blocks.
22
+
23
+ =end
24
+
25
+ class Proof
26
+
27
+ class Bindings
28
+ class Node
29
+ attr_reader :line, :branches, :depth
30
+ attr_accessor :parent, :active
31
+
32
+ def initialize line, parent
33
+ @line, @parent = line, parent
34
+ @branches = []
35
+ @active = true
36
+ @depth = parent ? (parent.depth + 1) : 0
37
+ end
38
+
39
+ def to_s prefix = ''
40
+ type = case
41
+ when not(@line) then 'block'
42
+ when @line.definition? then "define block (line #{@line.fileline})"
43
+ else "line #{@line.fileline}"
44
+ end
45
+ variables = @line ? @line.binds : []
46
+ me = "#{prefix}#{type}, variables #{variables}, active #{@active}"
47
+ them = branches.collect {|node| node.to_s prefix + ' '}
48
+ [me, *them].join "\n"
49
+ end
50
+ end
51
+ end
52
+
53
+ class Bindings
54
+ def initialize
55
+ @constants = Set[]
56
+ @bound = {}
57
+ @unbound = Set[]
58
+ @tie_ins = {}
59
+ @root = Node.new nil, nil
60
+ @blocks = [@root]
61
+ @node_for_line = Hash.new {raise}
62
+ end
63
+
64
+ def admit line # is nil when called by begin_block
65
+ # assumes check_admission has already been called!
66
+
67
+ binds = line ? line.binds : []
68
+ uses = line ? line.uses : []
69
+
70
+ # unbind any of the variables to be bound which were already bound
71
+ binds.each {|variable| deactivate_node @bound[variable] if @bound[variable]}
72
+
73
+ # add the new bindings
74
+ node = Node.new line, @blocks.last
75
+ @blocks.last.branches << node
76
+ binds.each {|variable| @bound[variable] = node}
77
+ @unbound.subtract binds
78
+
79
+ uses.each {|variable|
80
+ if @bound[variable]
81
+ # shift so that this appearance of variable will be in the binding scope
82
+ shift_under_node @bound[variable]
83
+ else
84
+ # variable was never bound, so register it as a constant
85
+ @constants << variable
86
+ end
87
+ }
88
+
89
+ # set tie-ins to start retroactively, as far back as possible
90
+ if line and not line.tie_ins.empty?
91
+ starter = most_recent_binding uses + binds
92
+ @tie_ins[starter] = [] if not @tie_ins[starter]
93
+ @tie_ins[starter] << node
94
+ end
95
+
96
+ # Tie-ins that are part of bindings only apply to the variables being bound,
97
+ # so their position in the tree is handled by the "uses" logic above. For
98
+ # other tie-ins, we cannot easily tell (due to their mutually recursive
99
+ # nature) whether they apply to the line being added or not. If they do, we
100
+ # need to call shift_under_node to put the line in their scope. If they
101
+ # don't, we should leave the line out of their scope, so that it will not be
102
+ # deactivated along with the tie-in. We use the following heuristic to make
103
+ # this decision:
104
+ # * If the line is a schema, it doesn't need tie-ins, and also it can't be
105
+ # cited if it is inactive, so we had better not deactivate it too soon.
106
+ # So we leave it out of the tie-in scope.
107
+ # * Otherwise, if the line doesn't bind anything, then hopefully it won't
108
+ # matter if it gets deactivated early, so we shift it under the tie-in.
109
+ # * Otherwise, we leave it out of the tie-in scope.
110
+ if line and line.binds.empty? and not line.schema
111
+ @tie_ins.each {|starter, tie_ins|
112
+ tie_ins.each {|tie_in| shift_under_node tie_in}
113
+ }
114
+ end
115
+
116
+ @node_for_line[line] = node if line
117
+ @blocks << node if not line or line.definition?
118
+ end
119
+
120
+ def begin_block
121
+ admit nil
122
+ end
123
+
124
+ def check_admission content
125
+ binds = content.binds
126
+ uses = content.uses
127
+
128
+ unless (@constants & binds).empty?
129
+ variable = (@constants & binds).first
130
+ msg = "cannot bind #{variable}: already occurred as a constant"
131
+ raise ProofException, msg
132
+ end
133
+
134
+ unless (@unbound & uses).empty?
135
+ variable = (@unbound & uses).first
136
+ raise ProofException, "variable #{variable} is no longer in scope"
137
+ end
138
+
139
+ binds.each {|variable|
140
+ next unless @bound[variable]
141
+ begin
142
+ to_unbind = variables_for_unbinding @bound[variable]
143
+ rescue ProofException
144
+ msg = "binding of variable #{variable} causes scope interleaving"
145
+ raise ProofException, msg
146
+ end
147
+ unless (uses & to_unbind).empty?
148
+ culprit = (uses & to_unbind).first
149
+ msg = "placement of variable #{culprit} causes scope interleaving"
150
+ raise ProofException, msg
151
+ end
152
+ }
153
+ end
154
+
155
+ def end_block
156
+ @blocks.pop while @blocks.last.line # pop define blocks
157
+ node = @blocks.pop
158
+ deactivate_node node
159
+ end
160
+
161
+ def to_check content, cited_lines
162
+ # adjust if schema is being cited
163
+ schema_lines = cited_lines.select {|line| line.schema}
164
+ if schema_lines.size > 1
165
+ raise ProofException, 'cannot cite multiple schemas'
166
+ elsif not schema_lines.empty?
167
+ if not @node_for_line[schema_lines[0]].active
168
+ raise ProofException, 'cannot cite inactive schema'
169
+ end
170
+ cited_lines -= [schema_lines[0]]
171
+ schema = schema_lines[0].schema
172
+ end
173
+
174
+ # handle cases that don't need tie-ins
175
+ cited = cited_tree cited_lines, false
176
+ tree = implication_tree [cited], content.sentence
177
+ if schema
178
+ return {:schema => schema, :tree => tree}
179
+ elsif self_implication? tree
180
+ tree = tree_for_true # make things easy for external prover
181
+ return {:tree => tree}
182
+ end
183
+
184
+ # finally, handle tie-ins
185
+ cited = cited_tree cited_lines, true
186
+ tree = nil
187
+ if content.body
188
+ # apply active tie-ins to the body, as long as they do not use or bind
189
+ # vars which content is rebinding
190
+ tie_ins = @tie_ins.values.flatten.select {|tie_in|
191
+ uses_or_binds = tie_in.line.uses + tie_in.line.binds
192
+ (uses_or_binds & content.binds).empty?
193
+ }
194
+ tied_in = tied_in_for content.body, tie_ins
195
+ tied_in = tied_in.collect {|match, used| match}
196
+ tree = implication_tree tied_in, content.body
197
+ end
198
+ tree = bind_variables content.binds, tree, :for_some
199
+ tree = implication_tree [cited], tree
200
+ tree = deduplicate tree
201
+ {:tree => tree}
202
+ end
203
+
204
+ def to_s
205
+ @root.to_s
206
+ end
207
+
208
+ private ###################################################################
209
+
210
+ # tie-ins are a bit tricky. here are two of the main ideas.
211
+ #
212
+ # (1) for any line, there are two classes of tie-ins that can apply to it:
213
+ # those that were in effect at the time the line was added, and those which
214
+ # are in effect now. to handle the second class, we place them
215
+ # "retroactively" at the earliest nodes in the tree they can apply to. these
216
+ # placements are kept in @tie_ins. they stay there for as long as they are
217
+ # active, then fall back to their placement in the line that created them.
218
+ #
219
+ # (2) matches for a tie-in cannot be computed "high" in the tree, because
220
+ # there may be lower tie-ins that could apply to them. but they cannot be
221
+ # added to the tree "low", because they may then be encompassed in a binding
222
+ # or supposition context which need not apply to them. so we have to compute
223
+ # them "low" and add them "high". to do this, we pass the tie-ins down
224
+ # through the tree, compute their matches on the way back up, but store the
225
+ # results as pending and only add them when we cannot retract them any further
226
+ # up the tree.
227
+
228
+ def cited_tree cited_lines, add_tied_in
229
+ cited_nodes = cited_lines.collect {|line| @node_for_line[line]}
230
+ ancestors = get_ancestors cited_nodes
231
+ tie_ins = (add_tied_in ? [] : nil)
232
+ tree, pending = cited_tree_internal @root, cited_nodes, ancestors, tie_ins
233
+ raise if not pending.empty? # sanity check
234
+ tree
235
+ end
236
+
237
+ def cited_tree_internal scope_tree, cited_nodes, ancestors, tie_ins
238
+ # if nothing under it was cited, there will be nothing to return
239
+ return [nil, []] unless ancestors.include? scope_tree
240
+
241
+ active = scope_tree.active
242
+ line = scope_tree.line
243
+ delay = (line and line.supposition? and not active) # handle next level up
244
+
245
+ if tie_ins and not delay
246
+ tie_in_precount = tie_ins.size
247
+ # add tie-ins defined here, unless they are active, in which case they
248
+ # are handled by the retroactive placement
249
+ tie_ins += [scope_tree] if line and not line.tie_ins.empty? and not active
250
+ # add any tie-ins that have been retroactively placed here
251
+ tie_ins += @tie_ins[scope_tree] if @tie_ins[scope_tree]
252
+ end
253
+
254
+ # make trees recursively
255
+ trees, pending = [], []
256
+ scope_tree.branches.each {|branch|
257
+ result = cited_tree_internal branch, cited_nodes, ancestors, tie_ins
258
+ trees << result[0] if result[0]
259
+ pending.concat result[1]
260
+ }
261
+
262
+ return [conjunction_tree(trees), pending] if delay
263
+
264
+ # supposition start block (first branch is the supposition itself)
265
+ # handled "one level up" because supposition may be wider than its binding
266
+ branch = scope_tree.branches[0]
267
+ if branch and branch.line and branch.line.supposition? and not active
268
+ raise if line # should be a supposition block
269
+ line = branch.line
270
+ tree = conjunction_tree trees
271
+ tree = Tree.new :implies, [line.body, tree] if line.body and tree
272
+ pending.concat tied_in_for line.body, tie_ins if line.body and tie_ins
273
+ readies = release_pending pending, line, tie_in_precount if tie_ins
274
+ tree = conjunction_tree [*readies, tree].compact
275
+ # generalize the binding
276
+ tree = bind_variables line.binds, tree, :for_all if tree
277
+ return [tree, pending]
278
+ end
279
+
280
+ if line and line.body and cited_nodes.include? scope_tree
281
+ trees.unshift line.body
282
+ pending.concat tied_in_for line.body, tie_ins if tie_ins
283
+ end
284
+ trees.unshift *release_pending(pending, line, tie_in_precount) if tie_ins
285
+ tree = conjunction_tree trees
286
+ tree = bind_variables line.binds, tree, :for_some if line and not active
287
+ [tree, pending]
288
+ end
289
+
290
+ def deactivate_node n
291
+ return if not n.active
292
+ n.branches.each {|branch| deactivate_node branch}
293
+ if n.line
294
+ n.line.binds.each {|v| @bound.delete v}
295
+ @unbound.merge n.line.binds
296
+ @tie_ins.each {|starter, tie_ins| tie_ins.delete n}
297
+ end
298
+ n.active = false
299
+ end
300
+
301
+ def deduplicate tree, facts = [], top = true
302
+ # does some simplifying to reduce duplication in the tree
303
+ tree = case tree.operator
304
+ when :for_all, :for_some
305
+ vars, body = tree.subtrees
306
+ return tree if not body
307
+ facts = facts.select {|fact|
308
+ (fact.free_variables & vars.operator).empty?
309
+ }
310
+ body = deduplicate body, facts, false
311
+ Tree.new tree.operator, [vars, body] if body
312
+ when :and
313
+ subtrees = tree.subtrees.uniq - facts.to_a
314
+ subtrees = subtrees.collect.with_index {|subtree, i|
315
+ new_facts = facts + subtrees[0...i] + subtrees[i+1..-1]
316
+ deduplicate subtree, new_facts, false
317
+ }.compact
318
+ if subtrees.empty?
319
+ nil
320
+ elsif subtrees.size == 1
321
+ subtrees[0]
322
+ else
323
+ Tree.new :and, subtrees
324
+ end
325
+ when :implies
326
+ antecedent = deduplicate tree.subtrees[0], facts, false
327
+ if tree.subtrees[0].operator == :and
328
+ new_facts = facts + tree.subtrees[0].subtrees
329
+ else
330
+ new_facts = facts + [tree.subtrees[0]]
331
+ end
332
+ consequent = deduplicate tree.subtrees[1], new_facts, false
333
+ consequent ? implication_tree([antecedent], consequent) : nil
334
+ else
335
+ tree
336
+ end
337
+ (top and not tree) ? tree_for_true : tree
338
+ end
339
+
340
+ def get_ancestors nodes
341
+ result = Set[]
342
+ nodes.each {|node|
343
+ while node
344
+ result << node
345
+ node = node.parent
346
+ end
347
+ }
348
+ result
349
+ end
350
+
351
+ def matches_in tie_in, tree
352
+ # find all matches in the tree and its subtrees for the tie-in
353
+ matches = matches_in_internal tie_in, tree, Set[]
354
+ matches.uniq
355
+ end
356
+
357
+ def matches_in_internal tie_in, tree, quantified
358
+ if Tree::Quantifiers.include? tree.operator
359
+ return [] if tree.subtrees.size == 1
360
+ # start = Time.now
361
+ vars = tree.subtrees[0].operator
362
+ # if pattern contains quantified variables, it cannot match
363
+ return [] if not (vars & tie_in.pattern.free_variables).empty?
364
+ quantified += Set[*vars]
365
+ return matches_in_internal tie_in, tree.subtrees[1], quantified
366
+ end
367
+ matches = []
368
+ tree.subtrees.each {|subtree|
369
+ matches.concat matches_in_internal tie_in, subtree, quantified
370
+ }
371
+ begin # check tree as a whole
372
+ params = [tie_in.pattern, tree, tie_in.metas]
373
+ constraints = SchemaModule.find_constraints *params
374
+ next if not constraints
375
+
376
+ resolved = SchemaModule.resolve_constraints constraints
377
+ next if not resolved
378
+ next unless (tie_in.metas - resolved.keys).empty?
379
+
380
+ # tie-ins must be terms
381
+ next unless resolved.values.none? {|v| v.boolean?}
382
+
383
+ # tie-ins cannot instantiate quantified variables
384
+ used_metas = tie_in.metas & tie_in.body.free_variables
385
+ found = used_metas.find {|meta|
386
+ not (quantified & resolved[meta].free_variables).empty?
387
+ }
388
+ next if found
389
+
390
+ matches << substitute(tie_in.body, resolved)
391
+ rescue ProofException # tree as a whole didn't match
392
+ end while false # for next to work!
393
+ matches
394
+ end
395
+
396
+ def most_recent_binding vars
397
+ # assumes bound vars are on same root-to-leaf path
398
+ nodes = vars.map {|v| @bound[v]}.compact
399
+ return @root if nodes.empty?
400
+ nodes.max_by {|node| node.depth}
401
+ end
402
+
403
+ def shift_under_node n
404
+ # shift everything after n to be under n
405
+ holder = n
406
+ while holder != @root
407
+ parent = holder.parent
408
+ i = parent.branches.index holder
409
+ nodes = parent.branches[i+1..-1]
410
+ parent.branches.slice! i+1..-1
411
+ n.branches.concat nodes
412
+ nodes.each {|node| node.parent = n}
413
+ holder = parent
414
+ end
415
+ end
416
+
417
+ def tied_in_for tree, tie_in_nodes, used = [], calls = {}
418
+ # find all tie-ins for the tree
419
+ calls[tree] = [] if not calls[tree]
420
+ # skip if we already made this call with fewer restrictions
421
+ return [] if calls[tree].find {|call| (call - used).empty?}
422
+ calls[tree] << used
423
+ results = []
424
+ tie_in_nodes.each_with_index {|node, i|
425
+ node.line.tie_ins.each_with_index {|tie_in, j|
426
+ # skip tie-ins used in making the tree
427
+ next if used.include? [i,j]
428
+ matches_in(tie_in, tree).each {|match|
429
+ new_used = used + [[i,j]]
430
+ results << [match, new_used.transpose[0]]
431
+ results.concat tied_in_for match, tie_in_nodes, new_used, calls
432
+ }
433
+ }
434
+ }
435
+ results
436
+ end
437
+
438
+ def release_pending pending, line, threshold
439
+ # find any pending tie-in matches that cannot be retracted further upward,
440
+ # remove them from pending, and return them
441
+ readies = []
442
+ vars = line ? line.binds : []
443
+ pending.delete_if {|match, used|
444
+ ready = false
445
+ # match uses a variable that this line binds
446
+ ready = true if not (match.free_variables & vars).empty?
447
+ # match uses a tie-in added by this line
448
+ ready = true if used.max >= threshold
449
+ readies << match if ready
450
+ ready
451
+ }
452
+ readies
453
+ end
454
+
455
+ def variables_for_unbinding n
456
+ return [] if not n.active
457
+ raise ProofException if @blocks.include? n # can't unbind a block
458
+ result = n.line.binds.dup
459
+ n.branches.each {|branch| result.concat variables_for_unbinding(branch)}
460
+ result
461
+ end
462
+ end
463
+
464
+ end