mbt-gen 0.0.4 → 0.0.5
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/lib/mbt-gen.rb +2058 -1976
- data/lib/progress.rb +6 -0
- data/lib/solver-lib.rb +572 -513
- metadata +2 -2
data/lib/solver-lib.rb
CHANGED
|
@@ -1,513 +1,572 @@
|
|
|
1
|
-
#!/usr/bin/ruby
|
|
2
|
-
|
|
3
|
-
require 'open3'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class SolverSession
|
|
7
|
-
|
|
8
|
-
def initialize(solverFactory, logFileName)
|
|
9
|
-
@solverFactory = solverFactory
|
|
10
|
-
@solver = @solverFactory.new_solver()
|
|
11
|
-
@protocol_filename = logFileName
|
|
12
|
-
@protocol = File.open(@protocol_filename, "w")
|
|
13
|
-
@assertCounter = -1
|
|
14
|
-
@associations = {}
|
|
15
|
-
@consts = {}
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def switch_solver(progress)
|
|
19
|
-
@solver.close()
|
|
20
|
-
@solver = @solverFactory.new_solver()
|
|
21
|
-
# re-feed the session protocol into the new solver to get it up-to-date
|
|
22
|
-
@protocol.close()
|
|
23
|
-
File.open(@protocol_filename, "r") do |protocol|
|
|
24
|
-
protocol.each_line do |line|
|
|
25
|
-
to_solver(progress, line)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
@protocol = File.open(@protocol_filename, "a")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def emptyReadBuffer(progress)
|
|
32
|
-
empty = false
|
|
33
|
-
while !empty do
|
|
34
|
-
begin
|
|
35
|
-
c = ""
|
|
36
|
-
@solver.z3outAndErr.read_nonblock(1, c)
|
|
37
|
-
line = c + @solver.z3outAndErr.gets
|
|
38
|
-
checkLineForSolverError(line, progress)
|
|
39
|
-
@protocol.puts("; Solver Response: #{line}")
|
|
40
|
-
rescue Errno::EWOULDBLOCK
|
|
41
|
-
# there is nothing to read -> just continue
|
|
42
|
-
empty = true
|
|
43
|
-
rescue Errno::EAGAIN
|
|
44
|
-
# there is nothing to read -> just continue
|
|
45
|
-
empty = true
|
|
46
|
-
rescue EOFError
|
|
47
|
-
# there is nothing to read -> just continue
|
|
48
|
-
empty = true
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def declare_const(progress, name, type, info)
|
|
54
|
-
info[:type] = type
|
|
55
|
-
@consts[name] = info
|
|
56
|
-
to_solver(progress, "(declare-const #{name} #{type})")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def to_solver(progress, line)
|
|
60
|
-
emptyReadBuffer(progress);
|
|
61
|
-
success = false
|
|
62
|
-
while (!success) do
|
|
63
|
-
begin
|
|
64
|
-
@solver.z3in.puts(line)
|
|
65
|
-
success = true
|
|
66
|
-
rescue Errno::EINVAL => e
|
|
67
|
-
progress.print_line("DEBUG: Invalid argument to puts(): #{line.inspect}")
|
|
68
|
-
progress.print_line("An Error occured: #{e.message}")
|
|
69
|
-
e.backtrace.each { |stline| progress.print_line(stline) }
|
|
70
|
-
progress.print_line("=> Switching Solver")
|
|
71
|
-
switch_solver(progress)
|
|
72
|
-
progress.print_line("=> Switch successful. Trying again.")
|
|
73
|
-
rescue Errno::EPIPE => e
|
|
74
|
-
progress.print_line("An Error occured: #{e.message}")
|
|
75
|
-
e.backtrace.each { |stline| progress.print_line(stline) }
|
|
76
|
-
progress.print_line("=> Switching Solver")
|
|
77
|
-
switch_solver(progress)
|
|
78
|
-
progress.print_line("=> Switch successful. Trying again.")
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
@protocol.puts(line)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def checkLineForSolverError(line, progress)
|
|
85
|
-
if line =~ /\(error \"(.*)\"\)/ then
|
|
86
|
-
progress.print_line("WARN: Solver reported ERROR: #{$1}")
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def from_solver(progress)
|
|
92
|
-
result = @solver.z3outAndErr.gets(chomp: true)
|
|
93
|
-
checkLineForSolverError(result, progress)
|
|
94
|
-
@protocol.puts("; Solver Response: #{result}")
|
|
95
|
-
return result
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def replace_strings(line)
|
|
99
|
-
line.gsub(/"[^"]*"/, "")
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def read_multiline_from_solver()
|
|
103
|
-
result = ""
|
|
104
|
-
for_counting = ""
|
|
105
|
-
@protocol.puts
|
|
106
|
-
@protocol.puts("; BEGIN Multiline Solver Response:")
|
|
107
|
-
line = @solver.z3outAndErr.gets(chomp: true)
|
|
108
|
-
result += line
|
|
109
|
-
for_counting = replace_strings(line)
|
|
110
|
-
@protocol.puts("; #{line}")
|
|
111
|
-
|
|
112
|
-
num_opening = for_counting.count("(")
|
|
113
|
-
num_closing = for_counting.count(")")
|
|
114
|
-
while num_opening > num_closing && line != nil do
|
|
115
|
-
line = @solver.z3outAndErr.gets(chomp: true)
|
|
116
|
-
if line
|
|
117
|
-
result += line
|
|
118
|
-
for_counting = replace_strings(line)
|
|
119
|
-
@protocol.puts("; #{line}")
|
|
120
|
-
|
|
121
|
-
num_opening += for_counting.count("(")
|
|
122
|
-
num_closing += for_counting.count(")")
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
@protocol.puts("; END Multiline Solver Response")
|
|
126
|
-
return result
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def writeProtocolToFile(progress, filename)
|
|
130
|
-
dir = File.dirname(filename)
|
|
131
|
-
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
132
|
-
FileUtils.cp(@protocol_filename, filename) unless @protocol_filename == filename
|
|
133
|
-
progress.print_line "Solver protocol written to file #{filename}"
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def generateNextAssertionIDAndAssociateWith(hash)
|
|
137
|
-
nextAssertionID = "a#{@assertCounter += 1}"
|
|
138
|
-
@associations[nextAssertionID] = hash
|
|
139
|
-
return nextAssertionID
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def associateAssertionIDWith(assertionID, hash)
|
|
143
|
-
if @associations.has_key?(assertionID) then
|
|
144
|
-
raise RuntimeError.new("AssertionID #{assertionID.inspect} already has info associated with it!")
|
|
145
|
-
end
|
|
146
|
-
@associations[assertionID] = hash
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def getAssociationForAssertionID(assertionID)
|
|
150
|
-
return @associations[assertionID]
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def for_each_const(&block)
|
|
154
|
-
@consts.each do |key, value|
|
|
155
|
-
block.call(key, value)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def get_const_info(name)
|
|
160
|
-
@consts[name]
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def parse_unsat_core(unsat_core)
|
|
164
|
-
@solver.parse_unsat_core(unsat_core, self)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def handleSolverError(progress, options, result)
|
|
168
|
-
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
class SolverFactory
|
|
173
|
-
|
|
174
|
-
attr_reader :z3path, :z3version
|
|
175
|
-
|
|
176
|
-
def initialize(progress, z3path)
|
|
177
|
-
@progress = progress
|
|
178
|
-
z3version = nil
|
|
179
|
-
exitstatus = nil
|
|
180
|
-
begin
|
|
181
|
-
z3version = `#{z3path} --version`.chomp()
|
|
182
|
-
exitstatus = $?.exitstatus
|
|
183
|
-
rescue Errno::ENOENT
|
|
184
|
-
end
|
|
185
|
-
if exitstatus == 0 && z3version != nil then
|
|
186
|
-
progress.print_line "INFO: Using #{z3version} found in z3path=#{z3path}"
|
|
187
|
-
@z3path = z3path
|
|
188
|
-
@z3version = z3version
|
|
189
|
-
else
|
|
190
|
-
progress.print_line "WARN: could not find z3 in z3path=#{z3path.inspect}"
|
|
191
|
-
`which z3`
|
|
192
|
-
if $?.exitstatus == 0 then
|
|
193
|
-
z3version = `z3 --version`.chomp()
|
|
194
|
-
progress.print_line "However, #{z3version} is availlable on the PATH. Using this one."
|
|
195
|
-
@z3path = 'z3'
|
|
196
|
-
@z3version = z3version
|
|
197
|
-
else
|
|
198
|
-
raise RuntimeError.new("ERROR: unable to find Z3 solver. Exiting! (Please provide its location in the config file using the key :z3path)")
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def new_solver()
|
|
204
|
-
Z3Solver.new(@progress, @z3path)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def query(options, &block)
|
|
208
|
-
unless options[:solverLog]
|
|
209
|
-
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
210
|
-
end
|
|
211
|
-
@progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
212
|
-
|
|
213
|
-
session = SolverSession.new(self, options[:solverLog])
|
|
214
|
-
block.call(session)
|
|
215
|
-
|
|
216
|
-
session.to_solver(@progress, "(check-sat)")
|
|
217
|
-
result = session.from_solver(@progress)
|
|
218
|
-
explanation = true
|
|
219
|
-
if result == "sat"
|
|
220
|
-
# model is not contradictory -> continue
|
|
221
|
-
elsif result == "unsat"
|
|
222
|
-
# model is contradictory -> add a message
|
|
223
|
-
|
|
224
|
-
session.to_solver(@progress, "(get-unsat-core)")
|
|
225
|
-
unsat_core = session.from_solver(@progress)
|
|
226
|
-
explanation = session.parse_unsat_core(unsat_core)
|
|
227
|
-
else
|
|
228
|
-
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
return explanation
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
unless value == "true" or value == "false" then
|
|
323
|
-
raise RuntimeError.new("inconsistent model value:
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
end
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SolverSession
|
|
7
|
+
|
|
8
|
+
def initialize(solverFactory, logFileName)
|
|
9
|
+
@solverFactory = solverFactory
|
|
10
|
+
@solver = @solverFactory.new_solver()
|
|
11
|
+
@protocol_filename = logFileName
|
|
12
|
+
@protocol = File.open(@protocol_filename, "w")
|
|
13
|
+
@assertCounter = -1
|
|
14
|
+
@associations = {}
|
|
15
|
+
@consts = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def switch_solver(progress)
|
|
19
|
+
@solver.close()
|
|
20
|
+
@solver = @solverFactory.new_solver()
|
|
21
|
+
# re-feed the session protocol into the new solver to get it up-to-date
|
|
22
|
+
@protocol.close()
|
|
23
|
+
File.open(@protocol_filename, "r") do |protocol|
|
|
24
|
+
protocol.each_line do |line|
|
|
25
|
+
to_solver(progress, line)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
@protocol = File.open(@protocol_filename, "a")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def emptyReadBuffer(progress)
|
|
32
|
+
empty = false
|
|
33
|
+
while !empty do
|
|
34
|
+
begin
|
|
35
|
+
c = ""
|
|
36
|
+
@solver.z3outAndErr.read_nonblock(1, c)
|
|
37
|
+
line = c + @solver.z3outAndErr.gets
|
|
38
|
+
checkLineForSolverError(line, progress)
|
|
39
|
+
@protocol.puts("; Solver Response: #{line}")
|
|
40
|
+
rescue Errno::EWOULDBLOCK
|
|
41
|
+
# there is nothing to read -> just continue
|
|
42
|
+
empty = true
|
|
43
|
+
rescue Errno::EAGAIN
|
|
44
|
+
# there is nothing to read -> just continue
|
|
45
|
+
empty = true
|
|
46
|
+
rescue EOFError
|
|
47
|
+
# there is nothing to read -> just continue
|
|
48
|
+
empty = true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def declare_const(progress, name, type, info)
|
|
54
|
+
info[:type] = type
|
|
55
|
+
@consts[name] = info
|
|
56
|
+
to_solver(progress, "(declare-const #{name} #{type})")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_solver(progress, line)
|
|
60
|
+
emptyReadBuffer(progress);
|
|
61
|
+
success = false
|
|
62
|
+
while (!success) do
|
|
63
|
+
begin
|
|
64
|
+
@solver.z3in.puts(line)
|
|
65
|
+
success = true
|
|
66
|
+
rescue Errno::EINVAL => e
|
|
67
|
+
progress.print_line("DEBUG: Invalid argument to puts(): #{line.inspect}")
|
|
68
|
+
progress.print_line("An Error occured: #{e.message}")
|
|
69
|
+
e.backtrace.each { |stline| progress.print_line(stline) }
|
|
70
|
+
progress.print_line("=> Switching Solver")
|
|
71
|
+
switch_solver(progress)
|
|
72
|
+
progress.print_line("=> Switch successful. Trying again.")
|
|
73
|
+
rescue Errno::EPIPE => e
|
|
74
|
+
progress.print_line("An Error occured: #{e.message}")
|
|
75
|
+
e.backtrace.each { |stline| progress.print_line(stline) }
|
|
76
|
+
progress.print_line("=> Switching Solver")
|
|
77
|
+
switch_solver(progress)
|
|
78
|
+
progress.print_line("=> Switch successful. Trying again.")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
@protocol.puts(line)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def checkLineForSolverError(line, progress)
|
|
85
|
+
if line =~ /\(error \"(.*)\"\)/ then
|
|
86
|
+
progress.print_line("WARN: Solver reported ERROR: #{$1}")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def from_solver(progress)
|
|
92
|
+
result = @solver.z3outAndErr.gets(chomp: true)
|
|
93
|
+
checkLineForSolverError(result, progress)
|
|
94
|
+
@protocol.puts("; Solver Response: #{result}")
|
|
95
|
+
return result
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def replace_strings(line)
|
|
99
|
+
line.gsub(/"[^"]*"/, "")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def read_multiline_from_solver()
|
|
103
|
+
result = ""
|
|
104
|
+
for_counting = ""
|
|
105
|
+
@protocol.puts
|
|
106
|
+
@protocol.puts("; BEGIN Multiline Solver Response:")
|
|
107
|
+
line = @solver.z3outAndErr.gets(chomp: true)
|
|
108
|
+
result += line
|
|
109
|
+
for_counting = replace_strings(line)
|
|
110
|
+
@protocol.puts("; #{line}")
|
|
111
|
+
|
|
112
|
+
num_opening = for_counting.count("(")
|
|
113
|
+
num_closing = for_counting.count(")")
|
|
114
|
+
while num_opening > num_closing && line != nil do
|
|
115
|
+
line = @solver.z3outAndErr.gets(chomp: true)
|
|
116
|
+
if line
|
|
117
|
+
result += "#{line}\n"
|
|
118
|
+
for_counting = replace_strings(line)
|
|
119
|
+
@protocol.puts("; #{line}")
|
|
120
|
+
|
|
121
|
+
num_opening += for_counting.count("(")
|
|
122
|
+
num_closing += for_counting.count(")")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
@protocol.puts("; END Multiline Solver Response")
|
|
126
|
+
return result
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def writeProtocolToFile(progress, filename)
|
|
130
|
+
dir = File.dirname(filename)
|
|
131
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
132
|
+
FileUtils.cp(@protocol_filename, filename) unless @protocol_filename == filename
|
|
133
|
+
progress.print_line "Solver protocol written to file #{filename}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def generateNextAssertionIDAndAssociateWith(hash)
|
|
137
|
+
nextAssertionID = "a#{@assertCounter += 1}"
|
|
138
|
+
@associations[nextAssertionID] = hash
|
|
139
|
+
return nextAssertionID
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def associateAssertionIDWith(assertionID, hash)
|
|
143
|
+
if @associations.has_key?(assertionID) then
|
|
144
|
+
raise RuntimeError.new("AssertionID #{assertionID.inspect} already has info associated with it!")
|
|
145
|
+
end
|
|
146
|
+
@associations[assertionID] = hash
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def getAssociationForAssertionID(assertionID)
|
|
150
|
+
return @associations[assertionID]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def for_each_const(&block)
|
|
154
|
+
@consts.each do |key, value|
|
|
155
|
+
block.call(key, value)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def get_const_info(name)
|
|
160
|
+
@consts[name]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def parse_unsat_core(unsat_core)
|
|
164
|
+
@solver.parse_unsat_core(unsat_core, self)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def handleSolverError(progress, options, result)
|
|
168
|
+
ModelUtils.handleSolverError(progress, self, options, result)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class SolverFactory
|
|
173
|
+
|
|
174
|
+
attr_reader :z3path, :z3version
|
|
175
|
+
|
|
176
|
+
def initialize(progress, z3path)
|
|
177
|
+
@progress = progress
|
|
178
|
+
z3version = nil
|
|
179
|
+
exitstatus = nil
|
|
180
|
+
begin
|
|
181
|
+
z3version = `#{z3path} --version`.chomp()
|
|
182
|
+
exitstatus = $?.exitstatus
|
|
183
|
+
rescue Errno::ENOENT
|
|
184
|
+
end
|
|
185
|
+
if exitstatus == 0 && z3version != nil then
|
|
186
|
+
progress.print_line "INFO: Using #{z3version} found in z3path=#{z3path}"
|
|
187
|
+
@z3path = z3path
|
|
188
|
+
@z3version = z3version
|
|
189
|
+
else
|
|
190
|
+
progress.print_line "WARN: could not find z3 in z3path=#{z3path.inspect}"
|
|
191
|
+
`which z3`
|
|
192
|
+
if $?.exitstatus == 0 then
|
|
193
|
+
z3version = `z3 --version`.chomp()
|
|
194
|
+
progress.print_line "However, #{z3version} is availlable on the PATH. Using this one."
|
|
195
|
+
@z3path = 'z3'
|
|
196
|
+
@z3version = z3version
|
|
197
|
+
else
|
|
198
|
+
raise RuntimeError.new("ERROR: unable to find Z3 solver. Exiting! (Please provide its location in the config file using the key :z3path)")
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def new_solver()
|
|
204
|
+
Z3Solver.new(@progress, @z3path)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def query(options, &block)
|
|
208
|
+
unless options[:solverLog]
|
|
209
|
+
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
210
|
+
end
|
|
211
|
+
@progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
212
|
+
|
|
213
|
+
session = SolverSession.new(self, options[:solverLog])
|
|
214
|
+
block.call(session)
|
|
215
|
+
|
|
216
|
+
session.to_solver(@progress, "(check-sat)")
|
|
217
|
+
result = session.from_solver(@progress)
|
|
218
|
+
explanation = true
|
|
219
|
+
if result == "sat"
|
|
220
|
+
# model is not contradictory -> continue
|
|
221
|
+
elsif result == "unsat"
|
|
222
|
+
# model is contradictory -> add a message
|
|
223
|
+
|
|
224
|
+
session.to_solver(@progress, "(get-unsat-core)")
|
|
225
|
+
unsat_core = session.from_solver(@progress)
|
|
226
|
+
explanation = session.parse_unsat_core(unsat_core)
|
|
227
|
+
else
|
|
228
|
+
ModelUtils.handleSolverError(@progress, options, result)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
return explanation
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def display_assertion(assertion, solver)
|
|
235
|
+
if assertion =~ /validation_rule_(.*)_instance_(.*)/ then
|
|
236
|
+
return "Validation rule #{$1} of structure #{$2.gsub("_", "/")}"
|
|
237
|
+
else
|
|
238
|
+
info = solver.getAssociationForAssertionID(assertion)
|
|
239
|
+
if info then
|
|
240
|
+
if info[:assertion_type] == :doc_field_filled then
|
|
241
|
+
return "Field #{info[:xpath]} contains value #{info[:value].inspect} in document"
|
|
242
|
+
elsif info[:assertion_type] == :doc_field_empty then
|
|
243
|
+
return "Field #{info[:xpath]} is empty in document"
|
|
244
|
+
elsif info[:assertion_type] == :doc_structure_exists then
|
|
245
|
+
return "Structure #{info[:xpath]} exists in document"
|
|
246
|
+
elsif info[:assertion_type] == :doc_structure_not_exists then
|
|
247
|
+
return "Structure #{info[:xpath]} does not exist in document"
|
|
248
|
+
elsif info[:assertion_type] == :constraint then
|
|
249
|
+
if info[:constraint_type] == :min then
|
|
250
|
+
return "Field #{info[:xpath]} has a minimum value of #{info[:value]}."
|
|
251
|
+
elsif info[:constraint_type] == :max then
|
|
252
|
+
return "Field #{info[:xpath]} has a maximum value of #{info[:value]}."
|
|
253
|
+
elsif info[:constraint_type] == :required then
|
|
254
|
+
return "Field #{info[:xpath]} is required."
|
|
255
|
+
elsif info[:constraint_type] == :values then
|
|
256
|
+
return "Field #{info[:xpath]} is an enum field and can only contain the values #{info[:values].inspect}."
|
|
257
|
+
else
|
|
258
|
+
p info
|
|
259
|
+
raise RuntimeError.new("encountered unknown :constraint_type #{info[:constraint_type].inspect} for assertionID #{assertion.inspect}")
|
|
260
|
+
end
|
|
261
|
+
else
|
|
262
|
+
raise RuntimeError.new("encountered unknown :assertion_type #{info[:assertion_type].inspect} for assertionID #{assertion.inspect}")
|
|
263
|
+
end
|
|
264
|
+
else
|
|
265
|
+
return assertion
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def parse_unsat_core(solver, unsat_core)
|
|
271
|
+
# TODO: implement
|
|
272
|
+
unless unsat_core =~ /\((.*)\)/ then
|
|
273
|
+
raise RuntimeError.new("could not parse unsat core: #{unsat_core.inspect}")
|
|
274
|
+
end
|
|
275
|
+
list = $1.split(" ")
|
|
276
|
+
|
|
277
|
+
result = "EXPLANATION:\n"
|
|
278
|
+
result += "The following values\n"
|
|
279
|
+
|
|
280
|
+
list.filter{|a| a.start_with?("doc-")}.each do |assertion|
|
|
281
|
+
result += "- #{display_assertion(assertion, solver)} (#{assertion})\n"
|
|
282
|
+
end
|
|
283
|
+
result += "\nviolate the following constraints\n"
|
|
284
|
+
list.filter{|a| !a.start_with?("doc-")}.each do |assertion|
|
|
285
|
+
result += "- #{display_assertion(assertion, solver)} (#{assertion})\n"
|
|
286
|
+
end
|
|
287
|
+
return result
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# main entry point!
|
|
291
|
+
def query_model(options, &block)
|
|
292
|
+
unless options[:solverLog]
|
|
293
|
+
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
294
|
+
end
|
|
295
|
+
@progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
296
|
+
session = SolverSession.new(self, options[:solverLog])
|
|
297
|
+
|
|
298
|
+
block.call(session)
|
|
299
|
+
session.to_solver(@progress, "(check-sat)")
|
|
300
|
+
result = session.from_solver(@progress)
|
|
301
|
+
if result.chomp == "sat"
|
|
302
|
+
# model is not contradictory -> extract model
|
|
303
|
+
const_list = []
|
|
304
|
+
const_info = {}
|
|
305
|
+
session.for_each_const do |name, info|
|
|
306
|
+
const_list << name
|
|
307
|
+
const_info[name] = info
|
|
308
|
+
end
|
|
309
|
+
session.to_solver(@progress, "(get-value (#{const_list.join(" ")}))")
|
|
310
|
+
model_str = session.read_multiline_from_solver()
|
|
311
|
+
model = {}
|
|
312
|
+
ModelUtils.foreach_field_in_model_str(model_str, const_info) do |varname, value, info|
|
|
313
|
+
unless info then
|
|
314
|
+
@progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
|
|
315
|
+
end
|
|
316
|
+
xpath = info[:xpath]
|
|
317
|
+
if model.has_key?(xpath) then
|
|
318
|
+
if varname.end_with?("-filled") then
|
|
319
|
+
unless model[xpath][:type] == :field then
|
|
320
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field, not a #{model[xpath][:type]}.")
|
|
321
|
+
end
|
|
322
|
+
unless value == "true" or value == "false" then
|
|
323
|
+
raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
324
|
+
end
|
|
325
|
+
if value == "true" then
|
|
326
|
+
model[xpath][:filled] = true
|
|
327
|
+
else
|
|
328
|
+
model[xpath][:filled] = false
|
|
329
|
+
end
|
|
330
|
+
elsif varname.end_with?("-value") then
|
|
331
|
+
unless model[xpath][:type] == :field || model[xpath][:type] == :list then
|
|
332
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field or a list, not a #{model[xpath][:type]}.")
|
|
333
|
+
end
|
|
334
|
+
if model[xpath][:type] == :field then
|
|
335
|
+
model[xpath][:value] = convert_solver_value(info[:type], info[:datatype], value, xpath)
|
|
336
|
+
elsif model[xpath][:type] == :list then
|
|
337
|
+
unless varname =~ /-index-(\d+)-value$/
|
|
338
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} is a list value and hence its varname should end like -index-X-value, not #{varname.inspect}.")
|
|
339
|
+
end
|
|
340
|
+
index = Integer($1)
|
|
341
|
+
model[xpath][:xpath_element] = info[:xpath_element]
|
|
342
|
+
if model[xpath].has_key?(:value) then
|
|
343
|
+
val = convert_solver_value(info[:type], info[:datatype], value, xpath)
|
|
344
|
+
model[xpath][:value].insert(index, val)
|
|
345
|
+
else
|
|
346
|
+
val = convert_solver_value(info[:type], info[:datatype], value, xpath)
|
|
347
|
+
model[xpath][:value] = [val]
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
elsif varname.end_with?("-size") then
|
|
351
|
+
unless model[xpath][:type] == :list || model[xpath][:type] == :field then
|
|
352
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list or a field, not a #{model[xpath][:type].inspect}.")
|
|
353
|
+
end
|
|
354
|
+
if model[xpath][:type] == :field then
|
|
355
|
+
model[xpath][:type] = :list
|
|
356
|
+
end
|
|
357
|
+
model[xpath][:size] = Integer(value)
|
|
358
|
+
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
359
|
+
unless model[xpath][:type] == :struct then
|
|
360
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a struct, not a #{model[xpath][:type]}.")
|
|
361
|
+
end
|
|
362
|
+
model[xpath][:exists] = value
|
|
363
|
+
end
|
|
364
|
+
else
|
|
365
|
+
if varname.end_with?("-filled") then
|
|
366
|
+
unless value == "true" or value == "false" then
|
|
367
|
+
raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
368
|
+
end
|
|
369
|
+
if value == "true" then
|
|
370
|
+
model[xpath] = { :type => :field, :filled => true }
|
|
371
|
+
else
|
|
372
|
+
model[xpath] = { :type => :field, :filled => false }
|
|
373
|
+
end
|
|
374
|
+
elsif varname.end_with?("-value") then
|
|
375
|
+
check_type(info[:type], value, xpath)
|
|
376
|
+
model[xpath] = { :type => :field, :value => value }
|
|
377
|
+
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
378
|
+
unless value == "true" or value == "false" then
|
|
379
|
+
raise RuntimeError.new("inconsistent model value: exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
380
|
+
end
|
|
381
|
+
model[xpath] = { :type => :struct, :exists => value }
|
|
382
|
+
elsif varname.end_with?("-size") then
|
|
383
|
+
model[xpath] = { :type => :list, :size => value }
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
return model
|
|
388
|
+
elsif result.chomp == "unsat"
|
|
389
|
+
# model is contradictory -> add a message
|
|
390
|
+
|
|
391
|
+
session.to_solver(@progress, "(get-unsat-core)")
|
|
392
|
+
unsat_core = session.from_solver(@progress)
|
|
393
|
+
session.writeProtocolToFile(@progress, options[:solverLog])
|
|
394
|
+
@progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
|
|
395
|
+
return nil
|
|
396
|
+
else
|
|
397
|
+
ModelUtils.handleSolverError(@progress, session, options, result)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
return nil
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def convert_solver_value(type, datatype, value, xpath)
|
|
404
|
+
check_type(type, value, xpath)
|
|
405
|
+
if datatype == :date then
|
|
406
|
+
return (Time.at(0).to_date + Integer(value)).to_s
|
|
407
|
+
elsif datatype == :timestamp then
|
|
408
|
+
return Time.at(Integer(value)).to_s
|
|
409
|
+
else
|
|
410
|
+
return value
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def check_type(type, value, xpath)
|
|
415
|
+
if type == "String" then
|
|
416
|
+
unless value =~ /\".*\"/ then
|
|
417
|
+
raise RuntimeError.new("inconsistent model value: model value for #{xpath} should be a String, but was #{value}.")
|
|
418
|
+
end
|
|
419
|
+
elsif type == "Bool"
|
|
420
|
+
unless value == "true" or value == "false" then
|
|
421
|
+
raise RuntimeError.new("inconsistent model value for #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
422
|
+
end
|
|
423
|
+
elsif type == "Int"
|
|
424
|
+
unless value =~ /\d+/ then
|
|
425
|
+
raise RuntimeError.new("inconsistent model value: model value for #{xpath} should be a Int, but was #{value}.")
|
|
426
|
+
end
|
|
427
|
+
else
|
|
428
|
+
raise RuntimeError.new("encountered unknown solver type: type of solver-var for #{xpath} is #{info[:type]}.")
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
class Z3Solver
|
|
435
|
+
|
|
436
|
+
attr_reader :z3in, :z3outAndErr
|
|
437
|
+
|
|
438
|
+
def initialize(progress, z3path)
|
|
439
|
+
@progress = progress
|
|
440
|
+
@z3in, @z3outAndErr, @z3thr = Open3.popen2e(z3path, '-in', '-smt2')
|
|
441
|
+
Process.detach(@z3thr.pid)
|
|
442
|
+
@z3in.sync = true
|
|
443
|
+
@sessionIdCounter = 0
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def close()
|
|
447
|
+
begin
|
|
448
|
+
@z3in.close() unless @z3in.closed?
|
|
449
|
+
rescue Errno::EINVAL
|
|
450
|
+
end
|
|
451
|
+
begin
|
|
452
|
+
@z3outAndErr.close() unless @z3outAndErr.closed?
|
|
453
|
+
rescue Errno::EINVAL
|
|
454
|
+
end
|
|
455
|
+
Thread.kill(@z3thr)
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def display_assertion(assertion, session)
|
|
459
|
+
if assertion =~ /validation_rule_(.*)_instance_(.*)/ then
|
|
460
|
+
return "Validation rule #{$1} of structure #{$2.gsub("_", "/")}"
|
|
461
|
+
else
|
|
462
|
+
info = session.getAssociationForAssertionID(assertion)
|
|
463
|
+
if info then
|
|
464
|
+
if info[:assertion_type] == :doc_field_filled then
|
|
465
|
+
return "Field #{info[:xpath]} contains value #{info[:value].inspect} in document"
|
|
466
|
+
elsif info[:assertion_type] == :doc_field_empty then
|
|
467
|
+
return "Field #{info[:xpath]} is empty in document"
|
|
468
|
+
elsif info[:assertion_type] == :doc_structure_exists then
|
|
469
|
+
return "Structure #{info[:xpath]} exists in document"
|
|
470
|
+
elsif info[:assertion_type] == :doc_structure_not_exists then
|
|
471
|
+
return "Structure #{info[:xpath]} does not exist in document"
|
|
472
|
+
elsif info[:assertion_type] == :constraint then
|
|
473
|
+
if info[:constraint_type] == :min then
|
|
474
|
+
return "Field #{info[:xpath]} has a minimum value of #{info[:value]}."
|
|
475
|
+
elsif info[:constraint_type] == :max then
|
|
476
|
+
return "Field #{info[:xpath]} has a maximum value of #{info[:value]}."
|
|
477
|
+
elsif info[:constraint_type] == :required then
|
|
478
|
+
return "Field #{info[:xpath]} is required."
|
|
479
|
+
else
|
|
480
|
+
p info
|
|
481
|
+
raise RuntimeError.new("encountered unknown :constraint_type #{info[:constraint_type].inspect} for assertionID #{assertion.inspect}")
|
|
482
|
+
end
|
|
483
|
+
else
|
|
484
|
+
raise RuntimeError.new("encountered unknown :assertion_type #{info[:assertion_type].inspect} for assertionID #{assertion.inspect}")
|
|
485
|
+
end
|
|
486
|
+
else
|
|
487
|
+
return assertion
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def parse_unsat_core(unsat_core, session)
|
|
493
|
+
unless unsat_core =~ /\((.*)\)/ then
|
|
494
|
+
raise RuntimeError.new("could not parse unsat core: #{unsat_core.inspect}")
|
|
495
|
+
end
|
|
496
|
+
list = $1.split(" ")
|
|
497
|
+
|
|
498
|
+
result = "EXPLANATION:\n"
|
|
499
|
+
result += "The following values\n"
|
|
500
|
+
|
|
501
|
+
list.filter{|a| a.start_with?("doc-")}.each do |assertion|
|
|
502
|
+
result += "- #{display_assertion(assertion, session)} (#{assertion})\n"
|
|
503
|
+
end
|
|
504
|
+
result += "\nviolate the following constraints\n"
|
|
505
|
+
list.filter{|a| !a.start_with?("doc-")}.each do |assertion|
|
|
506
|
+
result += "- #{display_assertion(assertion, session)} (#{assertion})\n"
|
|
507
|
+
end
|
|
508
|
+
return result
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def self.find_matching_bracket(str)
|
|
512
|
+
i = 0
|
|
513
|
+
open = 0
|
|
514
|
+
in_str = false
|
|
515
|
+
while open <= 0 do
|
|
516
|
+
if str[i] == '(' and !in_str then
|
|
517
|
+
open += 1
|
|
518
|
+
elsif str[i] == '"' then
|
|
519
|
+
in_str = !in_str
|
|
520
|
+
end
|
|
521
|
+
i += 1
|
|
522
|
+
end
|
|
523
|
+
while open > 0 do
|
|
524
|
+
if str[i] == '(' && !in_str then
|
|
525
|
+
open += 1
|
|
526
|
+
elsif str[i] == ')' && !in_str then
|
|
527
|
+
open -= 1
|
|
528
|
+
elsif str[i] == '"' then
|
|
529
|
+
in_str = !in_str
|
|
530
|
+
end
|
|
531
|
+
i += 1
|
|
532
|
+
end
|
|
533
|
+
str[0,i]
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
class ModelUtils
|
|
539
|
+
|
|
540
|
+
def self.foreach_field_in_model_str(str, const_info, &block)
|
|
541
|
+
unless str =~ /^\((.*)\)$/m
|
|
542
|
+
raise RuntimeError.new("could not parse model: #{str.inspect}")
|
|
543
|
+
end
|
|
544
|
+
str = $1
|
|
545
|
+
|
|
546
|
+
while str.size() > 0 do
|
|
547
|
+
line = Z3Solver.find_matching_bracket(str)
|
|
548
|
+
str = str[line.size()..]
|
|
549
|
+
unless line =~ /\(([^\ ]*)\ (.*)\)/
|
|
550
|
+
raise RuntimeError.new("could not parse model line: #{line.inspect}")
|
|
551
|
+
end
|
|
552
|
+
varname = $1
|
|
553
|
+
value = $2
|
|
554
|
+
if value =~ /\(-\ (\d+)\)/ then
|
|
555
|
+
simplified = "-#{$1}"
|
|
556
|
+
value = simplified
|
|
557
|
+
end
|
|
558
|
+
info = const_info[varname]
|
|
559
|
+
|
|
560
|
+
block.call(varname, value, info)
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
return nil
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def self.handleSolverError(progress, session, options, result)
|
|
567
|
+
session.writeProtocolToFile(progress, options[:solverLog])
|
|
568
|
+
raise RuntimeError.new("Solver Error: Solver returned '#{result}', for more information see #{options[:solverLog]}")
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
end
|
|
572
|
+
|