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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f15b0ead7b7f0d5f1dade59eb2a777769b8bc50f006e976fe99befea8aeae4b9
4
- data.tar.gz: f49f7228719fa47a580bfeef65c3b1139f721afbd24f059d7ede21e6e2a6447b
3
+ metadata.gz: 41f24b1c7255008c0d60800dfd0b24dd4e886edbd283eaa91232a2b15bcf389e
4
+ data.tar.gz: ac82cddd55a371da32407fa2a580a2290f784f1c780d60569b6a2601877a65aa
5
5
  SHA512:
6
- metadata.gz: 9a57aedc97b65aef33feae93b2afd8e3bba209fd8f7ab6a012dd316fec287c086279caa676fd293432a4d57e7df819a4d223543a763eae14fefaf8ab5a8abbd0
7
- data.tar.gz: c0091be99a7d8f7370b4f61c2d5b965a8076a4bb1d09f1661fab8cef4e7a3db818c4e5aa63f522c32d25e769d419747ee041bdbbb2f4cde50299ebd84720e6a9
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
- # 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
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
- end
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
- if content.body
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 & content.binds).empty?
213
+ (uses_or_binds & binds).empty?
193
214
  }
194
- tied_in = tied_in_for content.body, tie_ins
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, content.body
217
+ tree = implication_tree tied_in, body
197
218
  end
198
- tree = bind_variables content.binds, tree, :for_some
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
- @active_contexts = [[]]
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
- derive_internal content, line_numbers, question_mark, id
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
- last_context = @active_contexts.pop
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
- derive_internal content, last_context, false, id
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
- @active_contexts.last.concat last_context
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
- @active_contexts << []
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
- @active_contexts << []
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 @active_contexts[-1].empty?
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 @active_contexts[-1]
169
- @active_contexts[-1] = []
170
- derive_internal content, line_numbers, question_mark, id
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 @active_contexts[-1].empty?
184
+ if @active_buffers[-1].empty?
175
185
  raise ProofException, 'nothing for "so" to use'
176
186
  end
177
- @active_contexts[-1] = []
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
- @active_contexts << []
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 'checking tree:'
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
- @active_contexts[-1] << n
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, id
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
@@ -134,9 +134,7 @@ def grammar_rules
134
134
  [:exp0b, :prefix0, :end],
135
135
  [:exp0b, :prefix_, :end],
136
136
 
137
- [:prefix1, :not, :end],
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, :else, :atom_list_adjacent],
231
- [:atom_block2, :word_same_line, :atom_block2],
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, :atom_list_adjacent2],
234
- [:atom_block2, :else, :atom_list_adjacent2],
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, [:preposition, :exp3], :end],
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, /\+|\-|\*|\÷|\/|\^|⊆|⊊|⊂|\|\||{}|\[\]|∪|∩|</, :end],
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.7.1'
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
- condition = node.branches.last if node.branches.last.value == :condition
200
- process_conditions properties, variables, condition
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 process_conditions properties, variables, condition
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
- right_side = tree_from_grammar condition.branches[1] if condition
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, right_side, variable_tree)
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.length >= 2 and subtrees[-2].value == :preposition
386
- predicate = tree_from_grammar subtrees[-3]
387
- other = tree_from_grammar subtrees[-1]
388
- preposition_tree = Tree.new :predicate, [
389
- predicate, subject, other
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
- is = true if prev.branches[0].value == :prefix2
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
- when :word, :word_same_line, :definable, :definable_raw, :atom
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
- tree = Tree.new :not, [tree]
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
- if not conditions.compact.empty?
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 pretty_print level = 0
835
+ def pretty_to_s level = 0
831
836
  s = ' ' * level + to_s
832
- if s.length < 80
833
- puts s
834
- else
835
- puts ' ' * level << @operator.to_s
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
- if not proof.scopes.empty?
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, thesis
140
- if not thesis
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' => thesis.sentence}
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
- conflict = (thesis.uses & content.binds).first
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 = options.values_at :fix, :reduce, :wait
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 = IO.console.winsize[1] - 1 # subtract 1 for portability
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
- zipped = tree1.subtrees[0].operator.zip tree2.subtrees[0].operator
98
- zipped.each_with_index {|(var1, var2), i|
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
- zipped.each_with_index {|(var1, var2), i|
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
- # and that order and duplicates do not matter
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
- if results.any? {|key, result| result == false and set.subset? key}
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.7.1
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: 2024-06-21 00:00:00.000000000 Z
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: http://oakproof.org/
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: