oakproof 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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