oakproof 0.7.1 → 0.8
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/src/bindings.rb +46 -25
- data/src/commands.rb +29 -22
- data/src/grammar rules.rb +32 -12
- data/src/oak.rb +7 -2
- data/src/parser.rb +28 -25
- data/src/proof.rb +18 -11
- data/src/utilities.rb +75 -6
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41f24b1c7255008c0d60800dfd0b24dd4e886edbd283eaa91232a2b15bcf389e
|
|
4
|
+
data.tar.gz: ac82cddd55a371da32407fa2a580a2290f784f1c780d60569b6a2601877a65aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65fe47bde399dcb72ebd4f79f5af85b4820cb1a6e0b0b7bce35d847aeb55166344d27b18e5d7898aed65dd07555954078f7ef42eda63d4c3f6c7cc9a4e58d362
|
|
7
|
+
data.tar.gz: 8576b1f0c310fb511f0886af61ac3b850c6f195aae6bcad32a393d2f40ed120e74387b39021c33f53295cd6b5a593f1cccd6071b9c27cb3b23e876e19f913fbc
|
data/src/bindings.rb
CHANGED
|
@@ -93,25 +93,36 @@ class Bindings
|
|
|
93
93
|
@tie_ins[starter] << node
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
# * Otherwise,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
96
|
+
# For tie-ins that apply to the line being added, we need to call
|
|
97
|
+
# shift_under_node to put the line in their scope. For those that don't,
|
|
98
|
+
# we should leave the line out of their scope, so that when they are
|
|
99
|
+
# deactivated, they do not prematurely deactivate the line (which can cause
|
|
100
|
+
# errors if blocks are in play, or otherwise inconvenience the user).
|
|
101
|
+
# Unfortunately, due to the mutually recursive nature of tie-ins, checking
|
|
102
|
+
# this is complicated and slow (caused a 10% performance decrease). As a
|
|
103
|
+
# substitute, we use the following heuristic:
|
|
104
|
+
|
|
105
|
+
# * If the line is a schema, it doesn't need tie-ins, and also it can't
|
|
106
|
+
# be cited if it is inactive, so we had better not deactivate it too soon.
|
|
107
|
+
# So we leave it out of the tie-in scope.
|
|
108
|
+
|
|
109
|
+
# * Otherwise, for tie-ins that are part of bindings, if the line uses
|
|
110
|
+
# the bound variable, it would have been shifted by the "uses" logic
|
|
111
|
+
# above. If it does not, maybe/hopefully it doesn't need the tie-in, so we
|
|
112
|
+
# leave it alone.
|
|
113
|
+
|
|
114
|
+
# * Otherwise, for tie-ins that are not part of bindings, we shift the
|
|
115
|
+
# line under them, since these tie-ins are less likely to be suddenly
|
|
116
|
+
# deactivated.
|
|
117
|
+
|
|
118
|
+
if line and not line.schema
|
|
119
|
+
@tie_ins.each {|starter, tie_in_nodes|
|
|
120
|
+
tie_in_nodes.each {|tie_in_node|
|
|
121
|
+
next unless tie_in_node.line.binds.empty?
|
|
122
|
+
shift_under_node tie_in_node
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
end
|
|
115
126
|
|
|
116
127
|
@node_for_line[line] = node if line
|
|
117
128
|
@blocks << node if not line or line.definition?
|
|
@@ -179,23 +190,33 @@ class Bindings
|
|
|
179
190
|
elsif self_implication? tree
|
|
180
191
|
tree = tree_for_true # make things easy for external prover
|
|
181
192
|
return {:tree => tree}
|
|
182
|
-
|
|
193
|
+
elsif instantiation_implication? tree
|
|
194
|
+
tree = tree_for_true # make things easy for external prover
|
|
195
|
+
return {:tree => tree}
|
|
196
|
+
end
|
|
183
197
|
|
|
184
198
|
# finally, handle tie-ins
|
|
185
199
|
cited = cited_tree cited_lines, true
|
|
186
200
|
tree = nil
|
|
187
|
-
|
|
201
|
+
binds, body = content.binds, content.body
|
|
202
|
+
if body
|
|
203
|
+
# thesis does not create a binding within the proof, but we need to check
|
|
204
|
+
# it as if it does, so its body will get tie-ins, just as with end block
|
|
205
|
+
if body.operator == :for_some and binds.empty?
|
|
206
|
+
binds = body.subtrees[0].operator
|
|
207
|
+
body = body.subtrees[1]
|
|
208
|
+
end
|
|
188
209
|
# apply active tie-ins to the body, as long as they do not use or bind
|
|
189
210
|
# vars which content is rebinding
|
|
190
211
|
tie_ins = @tie_ins.values.flatten.select {|tie_in|
|
|
191
212
|
uses_or_binds = tie_in.line.uses + tie_in.line.binds
|
|
192
|
-
(uses_or_binds &
|
|
213
|
+
(uses_or_binds & binds).empty?
|
|
193
214
|
}
|
|
194
|
-
tied_in = tied_in_for
|
|
215
|
+
tied_in = tied_in_for body, tie_ins
|
|
195
216
|
tied_in = tied_in.collect {|match, used| match}
|
|
196
|
-
tree = implication_tree tied_in,
|
|
217
|
+
tree = implication_tree tied_in, body
|
|
197
218
|
end
|
|
198
|
-
tree = bind_variables
|
|
219
|
+
tree = bind_variables binds, tree, :for_some
|
|
199
220
|
tree = implication_tree [cited], tree
|
|
200
221
|
tree = deduplicate tree
|
|
201
222
|
{:tree => tree}
|
data/src/commands.rb
CHANGED
|
@@ -28,7 +28,7 @@ class Proof
|
|
|
28
28
|
def initialize manager, options = {}
|
|
29
29
|
@lines = []
|
|
30
30
|
@active_suppositions = []
|
|
31
|
-
@
|
|
31
|
+
@active_buffers = [[]]
|
|
32
32
|
@scopes = []
|
|
33
33
|
@theses = []
|
|
34
34
|
@label_stack = [[]]
|
|
@@ -85,7 +85,9 @@ class Proof
|
|
|
85
85
|
def derive content, reasons = [], id = nil
|
|
86
86
|
return assume content, id if assuming?
|
|
87
87
|
line_numbers, question_mark = process_reasons reasons
|
|
88
|
-
|
|
88
|
+
check_admission content
|
|
89
|
+
derive_internal content, line_numbers, question_mark
|
|
90
|
+
add content, :derivation, id
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def end_assume
|
|
@@ -109,7 +111,7 @@ class Proof
|
|
|
109
111
|
raise ProofException, '"begin assume" must end with "end assume"'
|
|
110
112
|
end
|
|
111
113
|
last_scope = @scopes.pop
|
|
112
|
-
|
|
114
|
+
last_buffer = @active_buffers.pop
|
|
113
115
|
last_thesis = @theses.pop
|
|
114
116
|
if last_scope.is_a? Array # end of proof block
|
|
115
117
|
@label_stack[-1].each {|label|
|
|
@@ -118,20 +120,26 @@ class Proof
|
|
|
118
120
|
}
|
|
119
121
|
@label_stack.pop
|
|
120
122
|
|
|
121
|
-
@bindings.end_block
|
|
122
|
-
|
|
123
123
|
content, id = last_scope
|
|
124
124
|
if assuming? then
|
|
125
|
+
@bindings.end_block
|
|
125
126
|
assume content, id
|
|
126
127
|
else
|
|
127
|
-
|
|
128
|
+
# check content without bindings while in proof block
|
|
129
|
+
check_admission Content.new content.sentence
|
|
130
|
+
# derive before ending block, to use active variables
|
|
131
|
+
derive_internal content, last_buffer, false
|
|
132
|
+
# now end the block and re-check content with bindings
|
|
133
|
+
@bindings.end_block
|
|
134
|
+
check_admission content
|
|
135
|
+
add content, :derivation, id
|
|
128
136
|
end
|
|
129
137
|
else
|
|
130
138
|
if last_scope == :suppose
|
|
131
139
|
@active_suppositions.pop
|
|
132
140
|
@bindings.end_block
|
|
133
141
|
end
|
|
134
|
-
@
|
|
142
|
+
@active_buffers.last.concat last_buffer
|
|
135
143
|
end
|
|
136
144
|
end
|
|
137
145
|
|
|
@@ -146,13 +154,13 @@ class Proof
|
|
|
146
154
|
|
|
147
155
|
def now
|
|
148
156
|
@scopes << :now
|
|
149
|
-
@
|
|
157
|
+
@active_buffers << []
|
|
150
158
|
@theses << @theses[-1] # inherit thesis if it exists
|
|
151
159
|
end
|
|
152
160
|
|
|
153
161
|
def proof content, id = nil
|
|
154
162
|
@scopes << [content, id]
|
|
155
|
-
@
|
|
163
|
+
@active_buffers << []
|
|
156
164
|
@theses << content
|
|
157
165
|
@label_stack << []
|
|
158
166
|
|
|
@@ -161,26 +169,28 @@ class Proof
|
|
|
161
169
|
|
|
162
170
|
def so content, reasons = [], id = nil
|
|
163
171
|
return so_assume content, id if assuming?
|
|
164
|
-
if @
|
|
172
|
+
if @active_buffers[-1].empty?
|
|
165
173
|
raise ProofException, 'nothing for "so" to use'
|
|
166
174
|
end
|
|
167
175
|
line_numbers, question_mark = process_reasons reasons
|
|
168
|
-
line_numbers.concat @
|
|
169
|
-
@
|
|
170
|
-
|
|
176
|
+
line_numbers.concat @active_buffers[-1]
|
|
177
|
+
@active_buffers[-1] = []
|
|
178
|
+
check_admission content
|
|
179
|
+
derive_internal content, line_numbers, question_mark
|
|
180
|
+
add content, :derivation, id
|
|
171
181
|
end
|
|
172
182
|
|
|
173
183
|
def so_assume content, id = nil
|
|
174
|
-
if @
|
|
184
|
+
if @active_buffers[-1].empty?
|
|
175
185
|
raise ProofException, 'nothing for "so" to use'
|
|
176
186
|
end
|
|
177
|
-
@
|
|
187
|
+
@active_buffers[-1] = []
|
|
178
188
|
assume content, id
|
|
179
189
|
end
|
|
180
190
|
|
|
181
191
|
def suppose content, id = nil
|
|
182
192
|
@scopes << :suppose
|
|
183
|
-
@
|
|
193
|
+
@active_buffers << []
|
|
184
194
|
@theses << @theses[-1] # inherit thesis if it exists
|
|
185
195
|
check_admission content
|
|
186
196
|
@active_suppositions << @lines.size
|
|
@@ -200,8 +210,7 @@ class Proof
|
|
|
200
210
|
end
|
|
201
211
|
end
|
|
202
212
|
|
|
203
|
-
# puts
|
|
204
|
-
# tree.pretty_print
|
|
213
|
+
# puts "\nchecking tree:\n#{tree.pretty_to_s}"
|
|
205
214
|
|
|
206
215
|
result = ExternalProver.valid_e? tree
|
|
207
216
|
|
|
@@ -291,15 +300,13 @@ class Proof
|
|
|
291
300
|
@label_stack[-1] << label
|
|
292
301
|
@line_numbers_by_label[label] = n
|
|
293
302
|
elsif content.tie_ins.empty? or not content.binds.empty?
|
|
294
|
-
@
|
|
303
|
+
@active_buffers[-1] << n
|
|
295
304
|
end
|
|
296
305
|
n
|
|
297
306
|
end
|
|
298
307
|
|
|
299
|
-
def derive_internal content, line_numbers, question_mark
|
|
300
|
-
check_admission content
|
|
308
|
+
def derive_internal content, line_numbers, question_mark
|
|
301
309
|
@manager.derive self, content, line_numbers, question_mark
|
|
302
|
-
add content, :derivation, id
|
|
303
310
|
end
|
|
304
311
|
|
|
305
312
|
def process_reasons reasons
|
data/src/grammar rules.rb
CHANGED
|
@@ -134,9 +134,7 @@ def grammar_rules
|
|
|
134
134
|
[:exp0b, :prefix0, :end],
|
|
135
135
|
[:exp0b, :prefix_, :end],
|
|
136
136
|
|
|
137
|
-
[:
|
|
138
|
-
|
|
139
|
-
[:exp1, :prefix1, :end],
|
|
137
|
+
# [:exp1, :prefix1, :end],
|
|
140
138
|
[:exp1, :exp2, :exp1a],
|
|
141
139
|
[:exp1a, :and, :and1],
|
|
142
140
|
[:and, /\s*and\b/i, :end],
|
|
@@ -153,10 +151,11 @@ def grammar_rules
|
|
|
153
151
|
[:exp1a, :else, :end],
|
|
154
152
|
[:exp1b, :exp2, :end],
|
|
155
153
|
[:exp1b, :else, :exp1c],
|
|
156
|
-
[:exp1c, :prefix1, :end],
|
|
154
|
+
# [:exp1c, :prefix1, :end],
|
|
157
155
|
[:exp1c, :prefix0, :end],
|
|
158
156
|
[:exp1c, :prefix_, :end],
|
|
159
157
|
|
|
158
|
+
[:prefix2, :not, :end],
|
|
160
159
|
[:prefix2, :every, :end],
|
|
161
160
|
[:prefix2, :no, :end],
|
|
162
161
|
[:prefix2, :some, :end],
|
|
@@ -226,16 +225,37 @@ def grammar_rules
|
|
|
226
225
|
[:atom_list2, [/\s*and\b/i, :atom_block], :atom_list2],
|
|
227
226
|
[:atom_list2, :else, :end],
|
|
228
227
|
|
|
228
|
+
# this is a bit tricky:
|
|
229
|
+
# there is a prefix of zero or more words (space-separated)
|
|
230
|
+
# followed by one or more definables (comma-separated)
|
|
231
|
+
# then a preposition is allowed if the prefix was non-empty
|
|
232
|
+
# then an optional condition
|
|
233
|
+
|
|
229
234
|
[:atom_block, :word, :atom_block2],
|
|
230
|
-
[:atom_block, :
|
|
231
|
-
|
|
235
|
+
[:atom_block, :definable, :atom_block2a],
|
|
236
|
+
|
|
237
|
+
[:atom_block2, :word_same_line, :atom_block3],
|
|
232
238
|
[:atom_block2, :condition, :end, :catch],
|
|
233
|
-
[:atom_block2, :definable_same_line, :
|
|
234
|
-
[:atom_block2, :else, :
|
|
239
|
+
[:atom_block2, :definable_same_line, :atom_block3a],
|
|
240
|
+
[:atom_block2, :else, :atom_block2a],
|
|
241
|
+
|
|
242
|
+
[:atom_block2a, [/,/, :definable_raw,], :atom_block2a, :catch],
|
|
243
|
+
[:atom_block2a, :else, :atom_block4],
|
|
244
|
+
|
|
245
|
+
[:atom_block3, :word_same_line, :atom_block3],
|
|
246
|
+
[:atom_block3, :condition, :end, :catch],
|
|
247
|
+
[:atom_block3, :definable_same_line, :atom_block3a],
|
|
248
|
+
[:atom_block3, :else, :atom_block3a],
|
|
249
|
+
|
|
250
|
+
[:atom_block3a, [/,/, :definable_raw,], :atom_block3a, :catch],
|
|
251
|
+
[:atom_block3a, :preposition, :atom_block4],
|
|
252
|
+
[:atom_block3a, :else, :atom_block4],
|
|
253
|
+
|
|
254
|
+
[:atom_block4, :condition, :end],
|
|
255
|
+
[:atom_block4, :else, :end],
|
|
235
256
|
|
|
236
257
|
[:atom_list_adjacent, :definable, :atom_list_adjacent2],
|
|
237
258
|
[:atom_list_adjacent2, [/,/, :definable_raw,], :atom_list_adjacent2, :catch],
|
|
238
|
-
[:atom_list_adjacent2, :condition, :end],
|
|
239
259
|
[:atom_list_adjacent2, :else, :end],
|
|
240
260
|
|
|
241
261
|
[:universal, /\s*for (all|any|each|every) meta\b/i, :null],
|
|
@@ -304,10 +324,10 @@ def grammar_rules
|
|
|
304
324
|
[:is_in, /\s*is in\b/i, :end],
|
|
305
325
|
[:is_not_in, /\s*is not in\b/i, :end],
|
|
306
326
|
|
|
307
|
-
[:preposition, /\s*(of|on)\b/i, :end],
|
|
327
|
+
[:preposition, [/\s*(of|on)\b/i, :exp3], :end],
|
|
308
328
|
|
|
309
329
|
[:category, :word, :category2],
|
|
310
|
-
[:category2,
|
|
330
|
+
[:category2, :preposition, :end],
|
|
311
331
|
[:category2, :word_same_line, :category2],
|
|
312
332
|
[:category2, :else, :end],
|
|
313
333
|
|
|
@@ -419,7 +439,7 @@ def grammar_rules
|
|
|
419
439
|
|
|
420
440
|
[:definable_same_line, [/[ \t]*/, :definable_raw], :end, :catch],
|
|
421
441
|
|
|
422
|
-
[:definable_raw,
|
|
442
|
+
[:definable_raw, /in|\+|\-|\*|\÷|\/|\^|⊆|⊊|⊂|\|\||{}|\[\]|∪|∩|</, :end],
|
|
423
443
|
[:definable_raw, :atom, :end],
|
|
424
444
|
|
|
425
445
|
[:atom, /for (all|any|at least one|at most one)\b/i, :null],
|
data/src/oak.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require_relative 'proof.rb'
|
|
2
2
|
|
|
3
|
-
name_version = 'Oak version 0.
|
|
3
|
+
name_version = 'Oak version 0.8'
|
|
4
4
|
issues_url = 'https://github.com/timlabs/oak/issues'
|
|
5
5
|
|
|
6
6
|
options = {}
|
|
@@ -22,6 +22,10 @@ if ARGV.delete '-m'
|
|
|
22
22
|
options[:marker] = true
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
if ARGV.delete '-p'
|
|
26
|
+
options[:print_failed_tree] = true
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
if ARGV.delete '-w'
|
|
26
30
|
options[:wait] = true
|
|
27
31
|
end
|
|
@@ -32,11 +36,12 @@ if options[:fix] and options[:wait]
|
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
if ARGV.size != 1 or ARGV[0].start_with? '-'
|
|
35
|
-
puts 'usage: oak [-v] [-c] [-f] [-m] [-w] <filename>'
|
|
39
|
+
puts 'usage: oak [-v] [-c] [-f] [-m] [-p] [-w] <filename>'
|
|
36
40
|
puts ' -v print the version number of Oak'
|
|
37
41
|
puts ' -c check for unneeded citations'
|
|
38
42
|
puts ' -f look for a fix'
|
|
39
43
|
puts ' -m assume until marker'
|
|
44
|
+
puts ' -p if a derivation fails, print its full tree'
|
|
40
45
|
puts ' -w wait for validity (does not change proof outcome)'
|
|
41
46
|
exit
|
|
42
47
|
end
|
data/src/parser.rb
CHANGED
|
@@ -196,20 +196,25 @@ class Parser
|
|
|
196
196
|
switched = true
|
|
197
197
|
end
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
preposition = node.branches.find {|branch| branch.value == :preposition}
|
|
200
|
+
condition = node.branches.find {|branch| branch.value == :condition}
|
|
201
|
+
process_atom_block_parts properties, variables, preposition, condition
|
|
201
202
|
end
|
|
202
203
|
|
|
203
|
-
def
|
|
204
|
+
def process_atom_block_parts properties, variables, preposition, condition
|
|
204
205
|
variable_trees = variables.collect {|node| tree_from_grammar node}
|
|
205
206
|
property_trees = properties.collect {|node| tree_from_grammar node}
|
|
206
|
-
|
|
207
|
+
preposition_right_side = tree_from_grammar preposition.branches[1] if preposition
|
|
208
|
+
condition_right_side = tree_from_grammar condition.branches[1] if condition
|
|
207
209
|
conditions = variable_trees.collect {|variable_tree|
|
|
208
210
|
trees = property_trees.collect {|property_tree|
|
|
209
211
|
Tree.new :predicate, [property_tree, variable_tree]
|
|
210
212
|
}
|
|
213
|
+
if preposition
|
|
214
|
+
trees[-1] = Tree.new :predicate, [*trees[-1].subtrees, preposition_right_side]
|
|
215
|
+
end
|
|
211
216
|
if condition
|
|
212
|
-
trees << apply_condition(condition,
|
|
217
|
+
trees << apply_condition(condition, condition_right_side, variable_tree)
|
|
213
218
|
end
|
|
214
219
|
conjunction_tree trees
|
|
215
220
|
}
|
|
@@ -382,13 +387,11 @@ class Parser
|
|
|
382
387
|
when :quantified then node.branches[1].branches
|
|
383
388
|
when :word then [node]
|
|
384
389
|
end
|
|
385
|
-
if subtrees
|
|
386
|
-
predicate = tree_from_grammar subtrees[-
|
|
387
|
-
other = tree_from_grammar subtrees[-1]
|
|
388
|
-
preposition_tree = Tree.new :predicate, [
|
|
389
|
-
|
|
390
|
-
]
|
|
391
|
-
subtrees = subtrees[0...-3]
|
|
390
|
+
if subtrees[-1].value == :preposition
|
|
391
|
+
predicate = tree_from_grammar subtrees[-2]
|
|
392
|
+
other = tree_from_grammar subtrees[-1].branches[1]
|
|
393
|
+
preposition_tree = Tree.new :predicate, [predicate, subject, other]
|
|
394
|
+
subtrees = subtrees[0...-2]
|
|
392
395
|
end
|
|
393
396
|
subtrees = subtrees.collect {|subtree|
|
|
394
397
|
predicate = tree_from_grammar subtree
|
|
@@ -420,7 +423,10 @@ class Parser
|
|
|
420
423
|
next unless subtree.subtrees.empty? and i > 0 # atom
|
|
421
424
|
prev = node.branches[(i-1)*2]
|
|
422
425
|
is = false
|
|
423
|
-
|
|
426
|
+
tags = [:every, :no, :some, :at_most_one]
|
|
427
|
+
if tags.include? prev.branches[0].branches[0].value
|
|
428
|
+
is = true
|
|
429
|
+
end
|
|
424
430
|
tags = [:is, :is_not, :is_in, :is_not_in]
|
|
425
431
|
if prev.branches.size >= 2 and tags.include? prev.branches[1].value
|
|
426
432
|
is = true
|
|
@@ -603,7 +609,8 @@ class Parser
|
|
|
603
609
|
subtrees.collect! {|subtree| tree_from_grammar subtree}
|
|
604
610
|
end
|
|
605
611
|
=end
|
|
606
|
-
|
|
612
|
+
when :word, :word_same_line, :definable, :definable_same_line,
|
|
613
|
+
:definable_raw, :atom
|
|
607
614
|
operator = standardize_operator node.text
|
|
608
615
|
subtrees = []
|
|
609
616
|
when :boolean
|
|
@@ -668,7 +675,7 @@ class Parser
|
|
|
668
675
|
raise ParseException, '"with" not allowed in negative quantifier' if with
|
|
669
676
|
tree = conjunction_tree [conditions_tree, such_that, body].compact
|
|
670
677
|
tree = Tree.new :for_some, [variables_tree, tree].compact
|
|
671
|
-
|
|
678
|
+
Tree.new :not, [tree]
|
|
672
679
|
when :existential, :define, :take
|
|
673
680
|
tie_ins = []
|
|
674
681
|
[variables, conditions].transpose.each {|variable, condition|
|
|
@@ -708,9 +715,7 @@ class Parser
|
|
|
708
715
|
metas = []
|
|
709
716
|
if node.branches[5]
|
|
710
717
|
metas, conditions = process_atom_block node.branches[5]
|
|
711
|
-
|
|
712
|
-
raise ParseException, 'tie-in variables cannot have conditions'
|
|
713
|
-
end
|
|
718
|
+
raise if not conditions.compact.empty?
|
|
714
719
|
end
|
|
715
720
|
return TieIn.new metas, pattern, body
|
|
716
721
|
end
|
|
@@ -827,14 +832,12 @@ class Tree
|
|
|
827
832
|
result
|
|
828
833
|
end
|
|
829
834
|
|
|
830
|
-
def
|
|
835
|
+
def pretty_to_s level = 0
|
|
831
836
|
s = ' ' * level + to_s
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
@subtrees.each {|subtree| subtree.pretty_print level+1}
|
|
837
|
-
end
|
|
837
|
+
return s if s.length < ConsoleWidth
|
|
838
|
+
s = ' ' * level + @operator.to_s
|
|
839
|
+
parts = @subtrees.map {|subtree| subtree.pretty_to_s level+1}
|
|
840
|
+
[s, *parts].join "\n"
|
|
838
841
|
end
|
|
839
842
|
|
|
840
843
|
Quantifiers = [:for_all, :for_some, :for_at_most_one]
|
data/src/proof.rb
CHANGED
|
@@ -53,9 +53,7 @@ class Proof
|
|
|
53
53
|
status = :exited
|
|
54
54
|
break
|
|
55
55
|
end
|
|
56
|
-
if content.is_a? Content
|
|
57
|
-
content = process_content content, proof.theses[-1]
|
|
58
|
-
end
|
|
56
|
+
content = process_content content, proof.theses if content.is_a? Content
|
|
59
57
|
# puts "content for line #{fileline} is: #{content.inspect}"
|
|
60
58
|
id = {label: label, filename: filename, fileline: fileline}
|
|
61
59
|
case action
|
|
@@ -99,15 +97,15 @@ class Proof
|
|
|
99
97
|
def self.finalize proof, status, addons, options
|
|
100
98
|
printer, tracker = addons.values_at :printer, :tracker
|
|
101
99
|
|
|
102
|
-
|
|
100
|
+
if options[:marker] and proof.assuming?
|
|
101
|
+
message = '-m option used without marker'
|
|
102
|
+
elsif not proof.scopes.empty?
|
|
103
103
|
message = case proof.scopes.last
|
|
104
104
|
when :suppose then 'active supposition'
|
|
105
105
|
when :now then 'active "now" block'
|
|
106
106
|
when Array then 'active "proof" block'
|
|
107
107
|
when :assume then 'active assume block'
|
|
108
108
|
end
|
|
109
|
-
elsif options[:marker] and proof.assuming?
|
|
110
|
-
message = '-m option used without marker'
|
|
111
109
|
end
|
|
112
110
|
raise EndException, message if message
|
|
113
111
|
|
|
@@ -136,8 +134,8 @@ class Proof
|
|
|
136
134
|
end
|
|
137
135
|
end
|
|
138
136
|
|
|
139
|
-
def self.process_content content,
|
|
140
|
-
if not
|
|
137
|
+
def self.process_content content, theses
|
|
138
|
+
if not theses[-1]
|
|
141
139
|
return content unless content.uses.include? 'thesis'
|
|
142
140
|
raise ProofException, 'thesis used outside of proof block'
|
|
143
141
|
elsif content.uses.include? 'thesis'
|
|
@@ -149,14 +147,18 @@ class Proof
|
|
|
149
147
|
raise ProofException, "cannot use thesis in tie-in"
|
|
150
148
|
end
|
|
151
149
|
begin # content was just a Tree
|
|
152
|
-
tree = substitute content.sentence, {'thesis' =>
|
|
150
|
+
tree = substitute content.sentence, {'thesis' => theses[-1].sentence}
|
|
153
151
|
rescue SubstituteException => e
|
|
154
152
|
message = "cannot quantify variable #{e}: conflicts with thesis"
|
|
155
153
|
raise ProofException, message
|
|
156
154
|
end
|
|
157
155
|
return Content.new tree
|
|
158
156
|
else
|
|
159
|
-
|
|
157
|
+
conflict = nil
|
|
158
|
+
theses.reverse_each.find {|thesis|
|
|
159
|
+
next if not thesis # may be nil due to placeholders at beginning
|
|
160
|
+
conflict = (thesis.uses & content.binds).first
|
|
161
|
+
}
|
|
160
162
|
return content unless conflict
|
|
161
163
|
raise ProofException, "cannot bind variable #{conflict}: part of thesis"
|
|
162
164
|
end
|
|
@@ -169,7 +171,8 @@ end
|
|
|
169
171
|
class DerivationManager
|
|
170
172
|
def initialize printer, options
|
|
171
173
|
@printer = printer
|
|
172
|
-
@fix, @reduce, @wait_on_unknown
|
|
174
|
+
@fix, @reduce, @wait_on_unknown, @print_failed_tree =
|
|
175
|
+
options.values_at :fix, :reduce, :wait, :print_failed_tree
|
|
173
176
|
end
|
|
174
177
|
|
|
175
178
|
def derive proof, content, line_numbers, question_mark
|
|
@@ -202,6 +205,10 @@ class DerivationManager
|
|
|
202
205
|
end
|
|
203
206
|
end
|
|
204
207
|
else
|
|
208
|
+
if @print_failed_tree
|
|
209
|
+
message = "full tree below\n\n#{checked.pretty_to_s}\n"
|
|
210
|
+
@printer.option_message '-p', message
|
|
211
|
+
end
|
|
205
212
|
message = case result
|
|
206
213
|
when :invalid then 'invalid derivation'
|
|
207
214
|
when :unknown then 'could not determine validity of derivation'
|
data/src/utilities.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
require 'io/console'
|
|
2
2
|
|
|
3
|
+
ConsoleWidth = IO.console.winsize[1] - 1 # subtract 1 for portability
|
|
4
|
+
|
|
3
5
|
class WordWrapper
|
|
4
6
|
def initialize
|
|
5
7
|
@indent = 2
|
|
6
|
-
@width =
|
|
8
|
+
@width = ConsoleWidth
|
|
7
9
|
@position = 0
|
|
8
10
|
end
|
|
9
11
|
|
|
@@ -94,15 +96,16 @@ def equal_up_to_variable_names? tree1, tree2, refs1 = {}, refs2 = {}, level = 0
|
|
|
94
96
|
case tree1.operator
|
|
95
97
|
when *Tree::Quantifiers
|
|
96
98
|
last1, last2 = {}, {}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
vars1, vars2 = tree1.subtrees[0].operator, tree2.subtrees[0].operator
|
|
100
|
+
return false unless vars1.size == vars2.size
|
|
101
|
+
vars1.zip(vars2).each_with_index {|(var1, var2), i|
|
|
99
102
|
refs1[var1], last1[var1] = [level,i], refs1[var1]
|
|
100
103
|
refs2[var2], last2[var2] = [level,i], refs2[var2]
|
|
101
104
|
}
|
|
102
105
|
result = pairs[1..-1].all? {|subtree1, subtree2|
|
|
103
106
|
equal_up_to_variable_names? subtree1, subtree2, refs1, refs2, level+1
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
vars1.zip(vars2).each_with_index {|(var1, var2), i|
|
|
106
109
|
last1[var1] ? (refs1[var1] = last1[var1]) : (refs1.delete var1)
|
|
107
110
|
last2[var2] ? (refs2[var2] = last2[var2]) : (refs2.delete var2)
|
|
108
111
|
}
|
|
@@ -126,6 +129,70 @@ def equal_up_to_variable_names? tree1, tree2, refs1 = {}, refs2 = {}, level = 0
|
|
|
126
129
|
end
|
|
127
130
|
end
|
|
128
131
|
|
|
132
|
+
def instantiated_by? tree1, tree2, refs1 = {}, refs2 = {}, level = 0
|
|
133
|
+
if tree1.operator.is_a? Symbol
|
|
134
|
+
return false unless tree1.operator == tree2.operator
|
|
135
|
+
return false unless tree1.subtrees.size == tree2.subtrees.size
|
|
136
|
+
pairs = [tree1.subtrees, tree2.subtrees].transpose
|
|
137
|
+
case tree1.operator
|
|
138
|
+
when *Tree::Quantifiers
|
|
139
|
+
last1, last2 = {}, {}
|
|
140
|
+
vars1, vars2 = tree1.subtrees[0].operator, tree2.subtrees[0].operator
|
|
141
|
+
return false unless vars1.size == vars2.size
|
|
142
|
+
vars1.zip(vars2).each_with_index {|(var1, var2), i|
|
|
143
|
+
refs1[var1], last1[var1] = [level,i], refs1[var1]
|
|
144
|
+
refs2[var2], last2[var2] = [level,i], refs2[var2]
|
|
145
|
+
}
|
|
146
|
+
result = pairs[1..-1].all? {|subtree1, subtree2|
|
|
147
|
+
instantiated_by? subtree1, subtree2, refs1, refs2, level+1
|
|
148
|
+
}
|
|
149
|
+
vars1.zip(vars2).each_with_index {|(var1, var2), i|
|
|
150
|
+
last1[var1] ? (refs1[var1] = last1[var1]) : (refs1.delete var1)
|
|
151
|
+
last2[var2] ? (refs2[var2] = last2[var2]) : (refs2.delete var2)
|
|
152
|
+
}
|
|
153
|
+
result
|
|
154
|
+
when :not, :and, :or, :implies, :iff, :equals, :predicate
|
|
155
|
+
pairs.all? {|subtree1, subtree2|
|
|
156
|
+
instantiated_by? subtree1, subtree2, refs1, refs2, level+1
|
|
157
|
+
}
|
|
158
|
+
else raise
|
|
159
|
+
end
|
|
160
|
+
elsif tree1.operator.is_a? String
|
|
161
|
+
if refs1[tree1.operator] == :pending
|
|
162
|
+
# instantiation must be a term
|
|
163
|
+
return false if tree2.boolean?
|
|
164
|
+
# the pending binding occurred at the top, so its instantiation cannot
|
|
165
|
+
# make use of any subsequent bindings
|
|
166
|
+
return false unless (refs2.keys & tree2.free_variables).empty?
|
|
167
|
+
refs1[tree1.operator] = tree2
|
|
168
|
+
true
|
|
169
|
+
elsif refs1[tree1.operator].is_a? Tree
|
|
170
|
+
# reference is a term and has no bindings (checked earlier), so the tree
|
|
171
|
+
# cannot have any bindings either, and must match exactly
|
|
172
|
+
return false unless (refs2.keys & tree2.free_variables).empty?
|
|
173
|
+
refs1[tree1.operator].eql? tree2
|
|
174
|
+
elsif refs1[tree1.operator] and refs2[tree2.operator]
|
|
175
|
+
refs1[tree1.operator] == refs2[tree2.operator] # both variables
|
|
176
|
+
elsif refs1[tree1.operator] or refs2[tree2.operator]
|
|
177
|
+
false # one is a constant, the other is a variable
|
|
178
|
+
else
|
|
179
|
+
tree1.operator == tree2.operator # both constants
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
raise
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def instantiation_implication? tree
|
|
187
|
+
return false unless tree.operator == :implies
|
|
188
|
+
return false unless tree.subtrees[0].operator == :for_all
|
|
189
|
+
vars = tree.subtrees[0].subtrees[0].operator
|
|
190
|
+
pending = vars.map {|v| [v, :pending]}.to_h
|
|
191
|
+
pattern = tree.subtrees[0].subtrees[1]
|
|
192
|
+
instance = tree.subtrees[1]
|
|
193
|
+
instantiated_by? pattern, instance, pending
|
|
194
|
+
end
|
|
195
|
+
|
|
129
196
|
def first_orderize tree, by_arity = false
|
|
130
197
|
subtrees = tree.subtrees.collect {|subtree| first_orderize subtree, by_arity}
|
|
131
198
|
if tree.operator == :predicate
|
|
@@ -267,14 +334,16 @@ end
|
|
|
267
334
|
def find_minimal_subsets input_array
|
|
268
335
|
# find the minimal subsets for which yield returns true, assuming that if it
|
|
269
336
|
# returns false for a given set, it will return false for all of its subsets,
|
|
270
|
-
#
|
|
337
|
+
# that order and duplicates do not matter, and that the input yields true
|
|
271
338
|
input_array = input_array.uniq
|
|
272
339
|
pending, results = [input_array], {}
|
|
273
340
|
until pending.empty?
|
|
274
341
|
array = pending.shift
|
|
275
342
|
set = array.to_set
|
|
276
343
|
next if results.has_key? set
|
|
277
|
-
|
|
344
|
+
if results.empty?
|
|
345
|
+
results[set] = true # input array is assumed to yield true
|
|
346
|
+
elsif results.any? {|key, result| result == false and set.subset? key}
|
|
278
347
|
results[set] = false
|
|
279
348
|
else
|
|
280
349
|
results[set] = yield array
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oakproof
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.8'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tim Smith
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: "."
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|
|
@@ -32,10 +32,13 @@ files:
|
|
|
32
32
|
- src/proof.rb
|
|
33
33
|
- src/schema.rb
|
|
34
34
|
- src/utilities.rb
|
|
35
|
-
homepage:
|
|
35
|
+
homepage: https://oakproof.org/
|
|
36
36
|
licenses:
|
|
37
37
|
- AGPL-3.0
|
|
38
|
-
metadata:
|
|
38
|
+
metadata:
|
|
39
|
+
homepage_uri: https://oakproof.org/
|
|
40
|
+
source_code_uri: https://github.com/timlabs/oak
|
|
41
|
+
documentation_uri: https://oakproof.org/tutorial.html
|
|
39
42
|
post_install_message:
|
|
40
43
|
rdoc_options: []
|
|
41
44
|
require_paths:
|