oakproof 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/eprover/eprover-2.3-linux +0 -0
- data/eprover/eprover-2.3-mac +0 -0
- data/eprover/eprover-2.3-windows.exe +0 -0
- data/eprover/eprover.txt +5 -0
- data/oak +3 -0
- data/src/bindings.rb +464 -0
- data/src/commands.rb +349 -0
- data/src/external prover.rb +189 -0
- data/src/grammar rules.rb +439 -0
- data/src/grammar.rb +218 -0
- data/src/oak.rb +56 -0
- data/src/parser.rb +1089 -0
- data/src/proof.rb +471 -0
- data/src/schema.rb +170 -0
- data/src/utilities.rb +361 -0
- metadata +58 -0
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
|
data/eprover/eprover.txt
ADDED
@@ -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/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
|