oakproof 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
data/src/proof.rb
ADDED
@@ -0,0 +1,471 @@
|
|
1
|
+
require_relative 'parser.rb'
|
2
|
+
require_relative 'commands.rb'
|
3
|
+
require_relative 'utilities.rb'
|
4
|
+
|
5
|
+
class Proof
|
6
|
+
def self.process_file input, options = {}
|
7
|
+
process input, :file, options
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.process_text input, options = {}
|
11
|
+
process input, :text, options
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.process input, type, options
|
15
|
+
addons = {printer: Printer.new, tracker: Tracker.new}
|
16
|
+
addons[:tracker].begin_assume nil if options[:marker]
|
17
|
+
manager = DerivationManager.new addons[:printer], options
|
18
|
+
proof = Proof.new manager, {marker: options[:marker]}
|
19
|
+
begin
|
20
|
+
status = include proof, input, type, nil, addons
|
21
|
+
finalize proof, status, addons, {marker: options[:marker]}
|
22
|
+
rescue ProofException => e
|
23
|
+
print_exception e, addons[:printer]
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.include proof, input, type, include_line, addons
|
29
|
+
printer, tracker = addons.values_at :printer, :tracker
|
30
|
+
case type
|
31
|
+
when :file
|
32
|
+
dirname = File.dirname input
|
33
|
+
filename = File.basename input
|
34
|
+
begin
|
35
|
+
input = File.read input
|
36
|
+
rescue
|
37
|
+
exception = (include_line ? ProofException : BaseException)
|
38
|
+
raise exception, "could not open file \"#{input}\""
|
39
|
+
end
|
40
|
+
when :text
|
41
|
+
filename = ''
|
42
|
+
else raise
|
43
|
+
end
|
44
|
+
printer.begin_file filename
|
45
|
+
tracker.begin_file filename, include_line
|
46
|
+
status = :ok
|
47
|
+
Parser.new.parse_each(input) {|sentence, action, content, reasons, label,
|
48
|
+
fileline|
|
49
|
+
next if action == :empty
|
50
|
+
printer.line fileline
|
51
|
+
if action == :exit
|
52
|
+
printer.exit
|
53
|
+
status = :exited
|
54
|
+
break
|
55
|
+
end
|
56
|
+
if content.is_a? Content
|
57
|
+
content = process_content content, proof.theses[-1]
|
58
|
+
end
|
59
|
+
# puts "content for line #{fileline} is: #{content.inspect}"
|
60
|
+
id = {label: label, filename: filename, fileline: fileline}
|
61
|
+
case action
|
62
|
+
when :include then
|
63
|
+
# include relative to path of current proof file
|
64
|
+
content = File.expand_path content, dirname if type == :file
|
65
|
+
status = include proof, content, :file, fileline, addons
|
66
|
+
break if status != :ok
|
67
|
+
when :suppose then proof.suppose content, id
|
68
|
+
when :now then proof.now
|
69
|
+
when :end then proof.end_block
|
70
|
+
when :assume
|
71
|
+
proof.assume content, id
|
72
|
+
tracker.assume fileline
|
73
|
+
when :axiom
|
74
|
+
proof.axiom content, id
|
75
|
+
tracker.axiom
|
76
|
+
when :derive then proof.derive content, reasons, id
|
77
|
+
when :so then proof.so content, reasons, id
|
78
|
+
when :so_assume
|
79
|
+
proof.so_assume content, id
|
80
|
+
tracker.assume fileline
|
81
|
+
when :proof then proof.proof content, id
|
82
|
+
when :begin_assume
|
83
|
+
proof.begin_assume
|
84
|
+
tracker.begin_assume fileline
|
85
|
+
when :end_assume
|
86
|
+
proof.end_assume
|
87
|
+
tracker.end_assume fileline
|
88
|
+
when :marker
|
89
|
+
proof.marker
|
90
|
+
tracker.end_assume fileline
|
91
|
+
else raise "unrecognized action #{action}"
|
92
|
+
end
|
93
|
+
}
|
94
|
+
printer.end_file
|
95
|
+
tracker.end_file
|
96
|
+
status
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.finalize proof, status, addons, options
|
100
|
+
printer, tracker = addons.values_at :printer, :tracker
|
101
|
+
|
102
|
+
if not proof.scopes.empty?
|
103
|
+
message = case proof.scopes.last
|
104
|
+
when :suppose then 'active supposition'
|
105
|
+
when :now then 'active "now" block'
|
106
|
+
when Array then 'active "proof" block'
|
107
|
+
when :assume then 'active assume block'
|
108
|
+
end
|
109
|
+
elsif options[:marker] and proof.assuming?
|
110
|
+
message = '-m option used without marker'
|
111
|
+
end
|
112
|
+
raise EndException, message if message
|
113
|
+
|
114
|
+
printer.accepted status
|
115
|
+
if not tracker.assumptions.empty?
|
116
|
+
printer.assumptions tracker.assumptions, options[:marker]
|
117
|
+
else
|
118
|
+
printer.axioms tracker.axioms
|
119
|
+
printer.conclude status
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.print_exception e, printer
|
124
|
+
case e
|
125
|
+
when ParseException
|
126
|
+
if e.line_number
|
127
|
+
printer.line e.line_number
|
128
|
+
printer.parse_error e.message
|
129
|
+
else
|
130
|
+
printer.file_message e.message
|
131
|
+
end
|
132
|
+
when DeriveException # already printed
|
133
|
+
when BaseException then printer.base_error e.message
|
134
|
+
when EndException then printer.end_error e.message
|
135
|
+
else printer.line_error e.message
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.process_content content, thesis
|
140
|
+
if not thesis
|
141
|
+
return content unless content.uses.include? 'thesis'
|
142
|
+
raise ProofException, 'thesis used outside of proof block'
|
143
|
+
elsif content.uses.include? 'thesis'
|
144
|
+
if content.schema
|
145
|
+
raise ProofException, 'cannot use thesis in schema'
|
146
|
+
elsif not content.binds.empty?
|
147
|
+
raise ProofException, "cannot use thesis in binding"
|
148
|
+
elsif not content.tie_ins.empty?
|
149
|
+
raise ProofException, "cannot use thesis in tie-in"
|
150
|
+
end
|
151
|
+
begin # content was just a Tree
|
152
|
+
tree = substitute content.sentence, {'thesis' => thesis.sentence}
|
153
|
+
rescue SubstituteException => e
|
154
|
+
message = "cannot quantify variable #{e}: conflicts with thesis"
|
155
|
+
raise ProofException, message
|
156
|
+
end
|
157
|
+
return Content.new tree
|
158
|
+
else
|
159
|
+
conflict = (thesis.uses & content.binds).first
|
160
|
+
return content unless conflict
|
161
|
+
raise ProofException, "cannot bind variable #{conflict}: part of thesis"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private_class_method :process, :include, :finalize, :print_exception,
|
166
|
+
:process_content
|
167
|
+
end
|
168
|
+
|
169
|
+
class DerivationManager
|
170
|
+
def initialize printer, options
|
171
|
+
@printer = printer
|
172
|
+
@fix, @reduce, @wait_on_unknown = options.values_at :fix, :reduce, :wait
|
173
|
+
end
|
174
|
+
|
175
|
+
def derive proof, content, line_numbers, question_mark
|
176
|
+
result, checked = proof.check content, line_numbers
|
177
|
+
if question_mark
|
178
|
+
if result == :valid
|
179
|
+
@printer.line_message 'derivation is already valid, no need for ?'
|
180
|
+
else
|
181
|
+
@printer.line_message 'trying to resolve ?'
|
182
|
+
results = proof.resolve_question_mark content, line_numbers
|
183
|
+
if results.empty?
|
184
|
+
@printer.line_message 'could not resolve ?'
|
185
|
+
else
|
186
|
+
to = results.collect {|i| proof.citation_string [i]}.join "\n "
|
187
|
+
@printer.line_message_block "resolved ? into:\n #{to}"
|
188
|
+
@printer.message 'proof incomplete due to ?'
|
189
|
+
end
|
190
|
+
end
|
191
|
+
raise DeriveException
|
192
|
+
end
|
193
|
+
if result == :valid
|
194
|
+
if @reduce
|
195
|
+
reduction = proof.reduce content, line_numbers
|
196
|
+
if reduction
|
197
|
+
from_lines, to_sets = reduction
|
198
|
+
from = proof.citation_string from_lines
|
199
|
+
to = to_sets.collect {|set| proof.citation_string set}.join "\n "
|
200
|
+
message = "citation can be reduced from:\n #{from}\nto:\n #{to}"
|
201
|
+
@printer.line_message_block message
|
202
|
+
end
|
203
|
+
end
|
204
|
+
else
|
205
|
+
message = case result
|
206
|
+
when :invalid then 'invalid derivation'
|
207
|
+
when :unknown then 'could not determine validity of derivation'
|
208
|
+
else raise
|
209
|
+
end
|
210
|
+
@printer.line_message message + " \"#{content.sentence}\""
|
211
|
+
if @fix
|
212
|
+
@printer.option_message '-f', 'looking for a fix'
|
213
|
+
result = proof.fix content, line_numbers
|
214
|
+
if result
|
215
|
+
@printer.option_message '-f', 'found a fix, reducing'
|
216
|
+
result = proof.reduce_fix result, content, line_numbers
|
217
|
+
to = proof.citation_string result
|
218
|
+
@printer.option_message '-f',
|
219
|
+
"derivation will work if you add:\n #{to}"
|
220
|
+
else
|
221
|
+
@printer.option_message '-f', 'no fix found'
|
222
|
+
end
|
223
|
+
elsif result == :unknown and @wait_on_unknown
|
224
|
+
@printer.option_message '-w', 'checking validity without work limit'
|
225
|
+
@printer.option_message '-w',
|
226
|
+
'(may never finish, press ctrl-c to abort)'
|
227
|
+
result = proof.check_wait_forever checked
|
228
|
+
message = case result
|
229
|
+
when :invalid then 'invalid derivation'
|
230
|
+
# use ceil rather than round to avoid "1.0 times the work limit"
|
231
|
+
when Numeric then "valid derivation, but took #{result.ceil 1} " \
|
232
|
+
"times the work limit"
|
233
|
+
when :unknown then 'eprover gave up'
|
234
|
+
else raise result.inspect
|
235
|
+
end
|
236
|
+
@printer.option_message '-w', message
|
237
|
+
@printer.message 'proof unsuccessful'
|
238
|
+
end
|
239
|
+
raise DeriveException
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class Printer
|
245
|
+
# maintains the invariant: if @stack is empty, @wrapper is clear
|
246
|
+
|
247
|
+
def initialize
|
248
|
+
@wrapper = WordWrapper.new
|
249
|
+
@stack = []
|
250
|
+
end
|
251
|
+
|
252
|
+
def accepted status
|
253
|
+
raise if not @stack.empty?
|
254
|
+
case status
|
255
|
+
when :exited then @wrapper.puts 'all lines accepted prior to exit'
|
256
|
+
when :ok then @wrapper.puts 'all lines accepted'
|
257
|
+
else raise
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def assumptions assumptions, marker
|
262
|
+
raise if not @stack.empty?
|
263
|
+
# assumption locations
|
264
|
+
assumptions.each {|filename, assumptions|
|
265
|
+
if assumptions.size == 1 and assumptions[0].is_a? Numeric
|
266
|
+
@wrapper.print "#{filename}: assumption on line "
|
267
|
+
else
|
268
|
+
@wrapper.print "#{filename}: assumptions on lines "
|
269
|
+
end
|
270
|
+
values = assumptions.collect {|x| x.is_a?(Numeric) ? x : x.join('-')}
|
271
|
+
@wrapper.puts values.join ', '
|
272
|
+
}
|
273
|
+
# assumption counts
|
274
|
+
blocks, lines = 0, 0
|
275
|
+
assumptions.values.each {|assumptions|
|
276
|
+
assumptions.each {|x|
|
277
|
+
if x.is_a? Numeric
|
278
|
+
lines += 1
|
279
|
+
elsif x.first != :start and x.last != :end
|
280
|
+
blocks += 1
|
281
|
+
end
|
282
|
+
}
|
283
|
+
}
|
284
|
+
counts = []
|
285
|
+
counts << 'marker' if marker
|
286
|
+
counts << "#{blocks} assume block" if blocks == 1
|
287
|
+
counts << "#{blocks} assume blocks" if blocks > 1
|
288
|
+
counts << "#{lines} assumption" if lines == 1
|
289
|
+
counts << "#{lines} assumptions" if lines > 1
|
290
|
+
raise if counts.empty?
|
291
|
+
@wrapper.puts "proof incomplete due to #{counts.join ' and '}"
|
292
|
+
end
|
293
|
+
|
294
|
+
def axioms axioms
|
295
|
+
raise if not @stack.empty?
|
296
|
+
axioms.each {|filename, axiom_count|
|
297
|
+
s = (axiom_count == 1 ? 'axiom' : 'axioms')
|
298
|
+
@wrapper.puts "#{axiom_count} #{s} in #{filename}"
|
299
|
+
}
|
300
|
+
end
|
301
|
+
|
302
|
+
def base_error message
|
303
|
+
raise if not @stack.empty?
|
304
|
+
@wrapper.puts "error: #{message}"
|
305
|
+
end
|
306
|
+
|
307
|
+
def begin_file filename
|
308
|
+
@wrapper.puts if not @wrapper.clear?
|
309
|
+
@stack << {file: filename, line: nil}
|
310
|
+
end
|
311
|
+
|
312
|
+
def conclude status
|
313
|
+
raise if not @stack.empty?
|
314
|
+
case status
|
315
|
+
when :exited then @wrapper.puts 'proof incomplete due to exit'
|
316
|
+
when :ok then @wrapper.puts 'proof successful!'
|
317
|
+
else raise
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def end_error message
|
322
|
+
raise if not @stack.empty?
|
323
|
+
@wrapper.puts "error at end of input: #{message}"
|
324
|
+
end
|
325
|
+
|
326
|
+
def end_file
|
327
|
+
raise if @stack.empty?
|
328
|
+
if not @stack[-1][:line]
|
329
|
+
@wrapper.puts "#{@stack[-1][:file]}: no lines to process"
|
330
|
+
elsif not @wrapper.clear?
|
331
|
+
@wrapper.puts
|
332
|
+
end
|
333
|
+
@stack.pop
|
334
|
+
end
|
335
|
+
|
336
|
+
def exit
|
337
|
+
line = current_line
|
338
|
+
@wrapper.puts if not @wrapper.clear?
|
339
|
+
@wrapper.puts "exit at line #{line}: skipping remaining lines"
|
340
|
+
end
|
341
|
+
|
342
|
+
def file_message message
|
343
|
+
raise if @stack.empty?
|
344
|
+
@wrapper.puts if not @wrapper.clear?
|
345
|
+
@wrapper.puts "#{@stack[-1][:file]}: #{message}"
|
346
|
+
end
|
347
|
+
|
348
|
+
def line i
|
349
|
+
raise if @stack.empty?
|
350
|
+
@wrapper.print "#{@stack[-1][:file]}: processing line" if @wrapper.clear?
|
351
|
+
@wrapper.print " #{i}"
|
352
|
+
@stack[-1][:line] = i
|
353
|
+
end
|
354
|
+
|
355
|
+
def line_error message
|
356
|
+
line = current_line
|
357
|
+
@wrapper.puts if not @wrapper.clear?
|
358
|
+
@wrapper.puts "error at line #{line}: #{message}"
|
359
|
+
end
|
360
|
+
|
361
|
+
def line_message message
|
362
|
+
line = current_line
|
363
|
+
@wrapper.puts if not @wrapper.clear?
|
364
|
+
@wrapper.puts "line #{line}: #{message}"
|
365
|
+
end
|
366
|
+
|
367
|
+
def line_message_block message
|
368
|
+
line = current_line
|
369
|
+
@wrapper.puts if not @wrapper.clear?
|
370
|
+
@wrapper.puts
|
371
|
+
@wrapper.puts "line #{line}: #{message}"
|
372
|
+
@wrapper.puts
|
373
|
+
end
|
374
|
+
|
375
|
+
def message message
|
376
|
+
@wrapper.puts if not @wrapper.clear?
|
377
|
+
@wrapper.puts message
|
378
|
+
end
|
379
|
+
|
380
|
+
def option_message option, message
|
381
|
+
line = current_line
|
382
|
+
@wrapper.puts if not @wrapper.clear?
|
383
|
+
@wrapper.puts "line #{line}: #{option} option: #{message}"
|
384
|
+
end
|
385
|
+
|
386
|
+
def parse_error message
|
387
|
+
line = current_line
|
388
|
+
@wrapper.puts if not @wrapper.clear?
|
389
|
+
@wrapper.puts "parse failed at line #{line}: #{message}"
|
390
|
+
end
|
391
|
+
|
392
|
+
private #####################################################################
|
393
|
+
|
394
|
+
def current_line
|
395
|
+
raise if @stack.empty?
|
396
|
+
@stack[-1][:line] or raise
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
class Tracker
|
401
|
+
# keeps track of assumptions and axioms
|
402
|
+
|
403
|
+
attr_reader :assumptions, :axioms
|
404
|
+
|
405
|
+
def initialize
|
406
|
+
@assumptions = Hash.new {|hash, key| hash[key] = []}
|
407
|
+
@axioms = Hash.new 0
|
408
|
+
|
409
|
+
@file_stack = []
|
410
|
+
@assume_stack = []
|
411
|
+
end
|
412
|
+
|
413
|
+
def assume fileline
|
414
|
+
@assumptions[current_file] << fileline if @assume_stack.empty?
|
415
|
+
end
|
416
|
+
|
417
|
+
def axiom
|
418
|
+
@axioms[current_file] += 1
|
419
|
+
end
|
420
|
+
|
421
|
+
def begin_assume fileline
|
422
|
+
if not @assume_stack.empty?
|
423
|
+
@assume_stack << :nested
|
424
|
+
elsif not @file_stack.empty?
|
425
|
+
@assume_stack << [current_file, fileline]
|
426
|
+
else
|
427
|
+
@assume_stack << [nil, nil]
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def begin_file filename, include_line
|
432
|
+
@file_stack << [filename, include_line]
|
433
|
+
end
|
434
|
+
|
435
|
+
def current_file
|
436
|
+
@file_stack.last[0]
|
437
|
+
end
|
438
|
+
|
439
|
+
def end_file
|
440
|
+
raise if @file_stack.empty? # nothing to end
|
441
|
+
filename, line = @file_stack.pop
|
442
|
+
return if @assume_stack.empty?
|
443
|
+
if @assume_stack[0][0] == filename # assume began in or under this file
|
444
|
+
@assumptions[filename] << [@assume_stack[0][1], :end]
|
445
|
+
# make the assume begin at the include line in the file above
|
446
|
+
@assume_stack[0] = [current_file, line] unless @file_stack.empty?
|
447
|
+
else # assume began before this file
|
448
|
+
@assumptions[filename] << [:start, :end]
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def end_assume fileline
|
453
|
+
raise if @assume_stack.empty? # nothing to end
|
454
|
+
if @assume_stack.last == :nested
|
455
|
+
@assume_stack.pop
|
456
|
+
else
|
457
|
+
assume_filename, assume_line = @assume_stack.pop
|
458
|
+
@file_stack.each_index.reverse_each {|i|
|
459
|
+
stack_filename = @file_stack[i][0]
|
460
|
+
# include line in this file
|
461
|
+
include_line = (@file_stack[i+1] ? @file_stack[i+1][1] : fileline)
|
462
|
+
if stack_filename != assume_filename # assume began before here
|
463
|
+
@assumptions[stack_filename] << [:start, include_line]
|
464
|
+
else # assume began here
|
465
|
+
@assumptions[stack_filename] << [assume_line, include_line]
|
466
|
+
break
|
467
|
+
end
|
468
|
+
}
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
data/src/schema.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
module SchemaModule
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def check_schema_format metas, condition, pattern
|
5
|
+
if condition
|
6
|
+
return false unless check_schema_format_condition condition, metas
|
7
|
+
end
|
8
|
+
check_schema_format_pattern pattern, metas
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_constraints pattern, instance, variables
|
12
|
+
# find constraints for meta variables, and make sure everything else matches
|
13
|
+
if pattern.operator.is_a? Array # list of quantified variables
|
14
|
+
return nil unless instance.operator.is_a? Array
|
15
|
+
return nil unless pattern.operator.size == instance.operator.size
|
16
|
+
constraints = []
|
17
|
+
pattern.operator.zip(instance.operator) {|v1, v2|
|
18
|
+
if variables.include? v1 # meta variable generates a constraint
|
19
|
+
constraints << [v1, Tree.new(v2, [])]
|
20
|
+
else
|
21
|
+
return nil unless v1 == v2 # others must match exactly
|
22
|
+
end
|
23
|
+
}
|
24
|
+
constraints
|
25
|
+
elsif variables.include? pattern.operator
|
26
|
+
[[pattern.operator, instance]]
|
27
|
+
elsif pattern.operator == :substitution
|
28
|
+
[[pattern, instance]]
|
29
|
+
else
|
30
|
+
return nil if pattern.operator != instance.operator
|
31
|
+
return nil if pattern.subtrees.size != instance.subtrees.size
|
32
|
+
constraints = []
|
33
|
+
pattern.subtrees.zip(instance.subtrees) {|s1, s2|
|
34
|
+
result = find_constraints s1, s2, variables
|
35
|
+
return nil if not result
|
36
|
+
constraints.concat result
|
37
|
+
}
|
38
|
+
constraints
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def instantiate_schema schema, instance
|
43
|
+
# puts "schema: #{schema}"
|
44
|
+
# puts "instance: #{instance}"
|
45
|
+
|
46
|
+
constraints = find_constraints schema.pattern, instance, schema.metas
|
47
|
+
raise ProofException if not constraints
|
48
|
+
# puts "constraints are:"
|
49
|
+
# constraints.each {|constraint| puts "#{constraint[0]} = #{constraint[1]}"}
|
50
|
+
# puts
|
51
|
+
|
52
|
+
resolved = resolve_constraints constraints
|
53
|
+
raise ProofException if not resolved
|
54
|
+
# puts "resolved:"
|
55
|
+
# p resolved
|
56
|
+
|
57
|
+
# every meta must have an assignment
|
58
|
+
raise ProofException unless (schema.metas - resolved.keys).empty?
|
59
|
+
|
60
|
+
return tree_for_true if not schema.condition
|
61
|
+
apply_resolved schema.condition, resolved
|
62
|
+
end
|
63
|
+
|
64
|
+
def resolve_constraints constraints
|
65
|
+
absolutes = constraints.select {|target, x| target.is_a? String}
|
66
|
+
relatives = constraints - absolutes
|
67
|
+
|
68
|
+
# puts "absolutes:"
|
69
|
+
# absolutes.each {|constraint| puts "#{constraint[0]} = #{constraint[1]}"}
|
70
|
+
# puts "relatives:"
|
71
|
+
# relatives.each {|constraint| puts "#{constraint[0]} = #{constraint[1]}"}
|
72
|
+
# puts
|
73
|
+
|
74
|
+
substitution = {}
|
75
|
+
absolutes.each {|variable, x|
|
76
|
+
if substitution[variable]
|
77
|
+
return nil unless substitution[variable].eql? x
|
78
|
+
else
|
79
|
+
substitution[variable] = x
|
80
|
+
end
|
81
|
+
}
|
82
|
+
|
83
|
+
relatives.each {|relative_substitution, x|
|
84
|
+
variable = relative_substitution.subtrees[0].operator
|
85
|
+
map = relative_substitution.subtrees[1]
|
86
|
+
|
87
|
+
# multi-substitute not supported yet
|
88
|
+
return nil if map.subtrees.size > 2
|
89
|
+
|
90
|
+
list = map.subtrees[1]
|
91
|
+
left = list.subtrees[1]
|
92
|
+
right = list.subtrees[2]
|
93
|
+
|
94
|
+
# schema must include all relative variables in absolute form
|
95
|
+
if not substitution[variable]
|
96
|
+
raise ProofException, "could not resolve schema variable #{variable}"
|
97
|
+
end
|
98
|
+
|
99
|
+
left = substitute(left, substitution).operator
|
100
|
+
return nil unless left.is_a? String
|
101
|
+
right = substitute right, substitution
|
102
|
+
|
103
|
+
# puts "substituted was #{substitution[variable]}"
|
104
|
+
substituted = substitute substitution[variable], {left => right}
|
105
|
+
# puts "x is #{x}"
|
106
|
+
# puts "substituted is #{substituted}"
|
107
|
+
# puts "for variable #{variable}"
|
108
|
+
# puts "left = #{left}"
|
109
|
+
# puts "right = #{right}"
|
110
|
+
return nil unless substituted.eql? x
|
111
|
+
}
|
112
|
+
|
113
|
+
substitution
|
114
|
+
end
|
115
|
+
|
116
|
+
private #######################################################################
|
117
|
+
|
118
|
+
def apply_resolved requirement, resolved
|
119
|
+
case requirement.operator
|
120
|
+
when :and, :or, :not, :implies, :iff
|
121
|
+
subtrees = requirement.subtrees.collect {|subtree|
|
122
|
+
apply_resolved subtree, resolved
|
123
|
+
}
|
124
|
+
Tree.new requirement.operator, subtrees
|
125
|
+
when :predicate
|
126
|
+
raise unless requirement.subtrees[0].operator == 'free'
|
127
|
+
variable = resolved[requirement.subtrees[1].operator].operator
|
128
|
+
formula = resolved[requirement.subtrees[2].operator]
|
129
|
+
raise ProofException unless variable.is_a? String
|
130
|
+
# puts "variable = #{variable}"
|
131
|
+
# puts "formula = #{formula}"
|
132
|
+
# puts "formula free vars = #{formula.free_variables}"
|
133
|
+
if formula.free_variables.include? variable
|
134
|
+
tree_for_true
|
135
|
+
else
|
136
|
+
tree_for_false
|
137
|
+
end
|
138
|
+
else raise
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def check_schema_format_condition tree, metas
|
143
|
+
case tree.operator
|
144
|
+
when :and, :or, :not, :implies, :iff
|
145
|
+
tree.subtrees.all? {|subtree|
|
146
|
+
check_schema_format_condition subtree, metas
|
147
|
+
}
|
148
|
+
when :predicate
|
149
|
+
return false unless tree.subtrees.size == 3
|
150
|
+
return false unless tree.subtrees[0].operator == 'free'
|
151
|
+
return false unless tree.subtrees[1].subtrees.empty?
|
152
|
+
return false unless tree.subtrees[2].subtrees.empty?
|
153
|
+
return false unless metas.include? tree.subtrees[1].operator
|
154
|
+
return false unless metas.include? tree.subtrees[2].operator
|
155
|
+
true
|
156
|
+
else
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def check_schema_format_pattern tree, metas
|
162
|
+
case tree.operator
|
163
|
+
when :substitution
|
164
|
+
metas.include? tree.subtrees[0].operator
|
165
|
+
else
|
166
|
+
tree.subtrees.all? {|subtree| check_schema_format_pattern subtree, metas}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|