mbt-gen 0.0.2 → 0.0.4

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mbt-gen.rb +451 -150
  3. data/lib/progress.rb +47 -5
  4. data/lib/solver-lib.rb +245 -98
  5. metadata +2 -2
data/lib/progress.rb CHANGED
@@ -1,9 +1,14 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
+ require 'fileutils'
4
+
3
5
  class Progress
4
6
 
5
- def initialize()
7
+ def initialize(logfilename)
6
8
  @last_line = ""
9
+ dir = File.dirname(logfilename)
10
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
11
+ @logfile = File.open(logfilename, "w")
7
12
  end
8
13
 
9
14
  def update_last_line(last_line)
@@ -13,27 +18,64 @@ class Progress
13
18
  end
14
19
 
15
20
  def print_line(line)
21
+ @logfile.puts(line)
22
+ @logfile.flush()
16
23
  print "\033[M#{line}\n#{@last_line}"
17
24
  $stdout.flush()
18
25
  end
19
26
  end
20
27
 
21
28
  class ProgressBar < Progress
22
- def initialize(max)
29
+ def initialize(max, logfilename)
30
+ super(logfilename)
23
31
  @max = max.to_f
24
32
  end
25
33
 
34
+ def set_max(value)
35
+ @max = value.to_f
36
+ end
37
+
26
38
  def render_bar(current, length)
27
39
  bars = (current.to_f * length.to_f).floor
28
40
  "=" * bars + ">" + " " * (length - bars)
29
41
  end
30
42
 
31
- def report_progress(progress)
43
+ def processing_time(start_time, prior_processessing_time)
44
+ total_sec = prior_processessing_time + (Time.now().to_i - start_time)
45
+ sec = total_sec
46
+ days = sec / (3600*24)
47
+ sec -= days * (3600*24)
48
+ hours = sec / 3600
49
+ sec -= hours * 3600
50
+ min = sec / 60
51
+ sec -= min * 60
52
+ if days > 0 then
53
+ return format("%d:%02d:%02d:%02d", days, hours, min, sec)
54
+ else
55
+ if hours > 0 then
56
+ return format("%02d:%02d:%02d", hours, min, sec)
57
+ else
58
+ return format("%02d:%02d", min, sec)
59
+ end
60
+ end
61
+ end
62
+
63
+ def report_progress(progress, start_time, prior_processessing_time, num_docs)
64
+ time_str = processing_time(start_time, prior_processessing_time)
32
65
  if progress then
33
66
  current = (progress.to_f / @max)
34
- update_last_line("[#{render_bar(current, 40)}] combinations covered: #{progress}/#{@max} (#{format("%.8f", current * 100.0)}%)")
67
+ update_last_line("#{time_str} [#{render_bar(current, 40)}] combinations covered: #{progress}/#{@max} (#{format("%.8f", current * 100.0)}%), docs generated: #{num_docs}")
35
68
  else
36
- update_last_line("[... ? ... ? ... ? ...] combinations covered: ?/∞ (?%)")
69
+ update_last_line("#{time_str} [... ? ... ? ... ? ...] combinations covered: ?/∞ (?%), docs generated: #{num_docs}")
70
+ end
71
+ end
72
+
73
+ def self.logging(max, logfile, &block)
74
+ progress = ProgressBar.new(max, logfile)
75
+ begin
76
+ block.call(progress)
77
+ ensure
78
+ progress.print_line("Output written to #{logfile}")
37
79
  end
38
80
  end
39
81
  end
data/lib/solver-lib.rb CHANGED
@@ -5,9 +5,9 @@ require 'open3'
5
5
 
6
6
  class SolverSession
7
7
 
8
- def initialize(z3in, z3outAndErr, logFileName)
9
- @z3in = z3in
10
- @z3outAndErr = z3outAndErr
8
+ def initialize(solverFactory, logFileName)
9
+ @solverFactory = solverFactory
10
+ @solver = @solverFactory.new_solver()
11
11
  @protocol_filename = logFileName
12
12
  @protocol = File.open(@protocol_filename, "w")
13
13
  @assertCounter = -1
@@ -15,13 +15,26 @@ class SolverSession
15
15
  @consts = {}
16
16
  end
17
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
+
18
31
  def emptyReadBuffer(progress)
19
32
  empty = false
20
33
  while !empty do
21
34
  begin
22
35
  c = ""
23
- @z3outAndErr.read_nonblock(1, c)
24
- line = c + @z3outAndErr.gets
36
+ @solver.z3outAndErr.read_nonblock(1, c)
37
+ line = c + @solver.z3outAndErr.gets
25
38
  checkLineForSolverError(line, progress)
26
39
  @protocol.puts("; Solver Response: #{line}")
27
40
  rescue Errno::EWOULDBLOCK
@@ -45,16 +58,27 @@ class SolverSession
45
58
 
46
59
  def to_solver(progress, line)
47
60
  emptyReadBuffer(progress);
48
- @protocol.puts(line)
49
- begin
50
- @z3in.puts(line)
51
- rescue Errno::EINVAL
52
- progress.print_line "ERROR: could not write the line #{line.inspect} to the solver."
53
- writeProtocolToFile("failure.smt2")
54
- rescue Errno::EPIPE
55
- writeProtocolToFile("failure.smt2")
56
- throw RuntimeError.new("Broken Pipe")
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
57
80
  end
81
+ @protocol.puts(line)
58
82
  end
59
83
 
60
84
  def checkLineForSolverError(line, progress)
@@ -65,14 +89,14 @@ class SolverSession
65
89
 
66
90
 
67
91
  def from_solver(progress)
68
- result = @z3outAndErr.gets
92
+ result = @solver.z3outAndErr.gets(chomp: true)
69
93
  checkLineForSolverError(result, progress)
70
94
  @protocol.puts("; Solver Response: #{result}")
71
95
  return result
72
96
  end
73
97
 
74
98
  def replace_strings(line)
75
- line.gsub(/"([^"\\]|\\.)*"/, "")
99
+ line.gsub(/"[^"]*"/, "")
76
100
  end
77
101
 
78
102
  def read_multiline_from_solver()
@@ -80,21 +104,23 @@ class SolverSession
80
104
  for_counting = ""
81
105
  @protocol.puts
82
106
  @protocol.puts("; BEGIN Multiline Solver Response:")
83
- line = @z3outAndErr.gets
107
+ line = @solver.z3outAndErr.gets(chomp: true)
84
108
  result += line
85
- for_counting += replace_strings(line)
109
+ for_counting = replace_strings(line)
86
110
  @protocol.puts("; #{line}")
87
111
 
88
112
  num_opening = for_counting.count("(")
89
113
  num_closing = for_counting.count(")")
90
- while num_opening > num_closing do
91
- line = @z3outAndErr.gets
92
- result += line
93
- for_counting += replace_strings(line)
94
- @protocol.puts("; #{line}")
95
-
96
- num_opening = for_counting.count("(")
97
- 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
98
124
  end
99
125
  @protocol.puts("; END Multiline Solver Response")
100
126
  return result
@@ -113,6 +139,13 @@ class SolverSession
113
139
  return nextAssertionID
114
140
  end
115
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
+
116
149
  def getAssociationForAssertionID(assertionID)
117
150
  return @associations[assertionID]
118
151
  end
@@ -126,93 +159,103 @@ class SolverSession
126
159
  def get_const_info(name)
127
160
  @consts[name]
128
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
+ @solver.handleSolverError(progress, self, options, result)
169
+ end
129
170
  end
130
171
 
131
- class Z3Solver
172
+ class SolverFactory
173
+
174
+ attr_reader :z3path, :z3version
132
175
 
133
176
  def initialize(progress, z3path)
134
- z3version = `#{z3path} --version`.chomp()
135
- if $?.exitstatus == 0 then
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
136
186
  progress.print_line "INFO: Using #{z3version} found in z3path=#{z3path}"
137
- @z3in, @z3outAndErr, @z3thr = Open3.popen2e(z3path, '-in', '-smt2')
138
- Process.detach(@z3thr.pid)
187
+ @z3path = z3path
188
+ @z3version = z3version
139
189
  else
140
190
  progress.print_line "WARN: could not find z3 in z3path=#{z3path.inspect}"
141
191
  `which z3`
142
192
  if $?.exitstatus == 0 then
143
193
  z3version = `z3 --version`.chomp()
144
194
  progress.print_line "However, #{z3version} is availlable on the PATH. Using this one."
145
- @z3in, @z3outAndErr, @z3thr = Open3.popen2e('z3', '-in', '-smt2')
146
- Process.detach(@z3thr.pid)
195
+ @z3path = 'z3'
196
+ @z3version = z3version
147
197
  else
148
- raise RuntimeError.new("ERROR: unable to find Z3. Exiting! (Please provide its location in the config file using the key :z3path)")
198
+ raise RuntimeError.new("ERROR: unable to find Z3 solver. Exiting! (Please provide its location in the config file using the key :z3path)")
149
199
  end
150
200
  end
151
- @z3in.sync = true
152
-
153
- @sessionIdCounter = 0
154
201
  end
155
202
 
156
- def close()
157
- @z3in.close()
158
- @z3outAndErr.close()
159
- Thread.kill(@z3thr)
203
+ def new_solver()
204
+ Z3Solver.new(@progress, @z3path)
160
205
  end
161
206
 
162
- def query(&block)
163
- solver = SolverSession.new(@z3in, @z3outAndErr)
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]})")
164
212
 
165
- block.call(solver)
213
+ session = SolverSession.new(self, options[:solverLog])
214
+ block.call(session)
166
215
 
167
- solver.toSolver "(check-sat)"
168
- result = solver.fromSolver()
169
- if result.chomp == "sat"
216
+ session.to_solver(@progress, "(check-sat)")
217
+ result = session.from_solver(@progress)
218
+ explanation = true
219
+ if result == "sat"
170
220
  # model is not contradictory -> continue
171
- elsif result.chomp == "unsat"
221
+ elsif result == "unsat"
172
222
  # model is contradictory -> add a message
173
223
 
174
- solver.toSolver("(get-unsat-core)")
175
- unsat_core = solver.fromSolver()
176
- explanation = build_explanation_based_on_unsat_core(solver, sid, unsat_core)
177
- messages << {:type => :issue, :text => "Model is contradictory", :explanation => explanation}
178
- solver.writeProtocolToFile("contradictions.smt2")
224
+ session.to_solver(@progress, "(get-unsat-core)")
225
+ unsat_core = session.from_solver(@progress)
226
+ explanation = session.parse_unsat_core(unsat_core)
179
227
  else
180
- handleSolverError(solver, result)
228
+ session.handleSolverError(@progress, options, result)
181
229
  end
182
230
 
183
- messages = []
184
- solver.toSolver "(reset)"
185
- solver.toSolver "(set-logic ALL)"
186
- solver.toSolver "(set-option :produce-unsat-cores true)"
187
- return messages
231
+ return explanation
188
232
  end
189
233
 
190
234
  # main entry point!
191
- def query_model(progress, options, &block)
235
+ def query_model(options, &block)
192
236
  unless options[:solverLog]
193
237
  raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
194
238
  end
195
- progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
196
- solver = SolverSession.new(@z3in, @z3outAndErr, options[:solverLog])
239
+ @progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
240
+ session = SolverSession.new(self, options[:solverLog])
197
241
 
198
- block.call(solver)
199
-
200
- solver.to_solver(progress, "(check-sat)")
201
- result = solver.from_solver(progress)
242
+ block.call(session)
243
+ session.to_solver(@progress, "(check-sat)")
244
+ result = session.from_solver(@progress)
202
245
  if result.chomp == "sat"
203
246
  # model is not contradictory -> extract model
204
247
  const_list = []
205
248
  const_info = {}
206
- solver.for_each_const do |name, info|
249
+ session.for_each_const do |name, info|
207
250
  const_list << name
208
251
  const_info[name] = info
209
252
  end
210
- solver.to_solver(progress, "(get-value (#{const_list.join(" ")}))")
211
- model_str = solver.read_multiline_from_solver()
253
+ session.to_solver(@progress, "(get-value (#{const_list.join(" ")}))")
254
+ model_str = session.read_multiline_from_solver()
212
255
  model = {}
213
- foreach_field_in_model_str(model_str, solver, const_info) do |varname, value, info|
256
+ Z3Solver.foreach_field_in_model_str(model_str, const_info) do |varname, value, info|
214
257
  unless info then
215
- progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
258
+ @progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
216
259
  end
217
260
  xpath = info[:xpath]
218
261
  if model.has_key?(xpath) then
@@ -221,7 +264,7 @@ class Z3Solver
221
264
  raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field, not a #{model[xpath][:type]}.")
222
265
  end
223
266
  unless value == "true" or value == "false" then
224
- raise RuntimeError.new("inconsistent model value filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
267
+ raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
225
268
  end
226
269
  if value == "true" then
227
270
  model[xpath][:filled] = true
@@ -249,10 +292,13 @@ class Z3Solver
249
292
  end
250
293
  end
251
294
  elsif varname.end_with?("-size") then
252
- unless model[xpath][:type] == :list then
253
- raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list, not a #{model[xpath][:type]}.")
295
+ unless model[xpath][:type] == :list || model[xpath][:type] == :field then
296
+ raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list or a field, not a #{model[xpath][:type].inspect}.")
297
+ end
298
+ if model[xpath][:type] == :field then
299
+ model[xpath][:type] = :list
254
300
  end
255
- model[xpath][:value] = value
301
+ model[xpath][:size] = Integer(value)
256
302
  elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
257
303
  unless model[xpath][:type] == :struct then
258
304
  raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a struct, not a #{model[xpath][:type]}.")
@@ -262,7 +308,7 @@ class Z3Solver
262
308
  else
263
309
  if varname.end_with?("-filled") then
264
310
  unless value == "true" or value == "false" then
265
- raise RuntimeError.new("inconsistent model value filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
311
+ raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
266
312
  end
267
313
  if value == "true" then
268
314
  model[xpath] = { :type => :field, :filled => true }
@@ -274,7 +320,7 @@ class Z3Solver
274
320
  model[xpath] = { :type => :field, :value => value }
275
321
  elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
276
322
  unless value == "true" or value == "false" then
277
- raise RuntimeError.new("inconsistent model value exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
323
+ raise RuntimeError.new("inconsistent model value: exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
278
324
  end
279
325
  model[xpath] = { :type => :struct, :exists => value }
280
326
  elsif varname.end_with?("-size") then
@@ -286,21 +332,27 @@ class Z3Solver
286
332
  elsif result.chomp == "unsat"
287
333
  # model is contradictory -> add a message
288
334
 
289
- solver.to_solver(progress, "(get-unsat-core)")
290
- unsat_core = solver.from_solver(progress)
291
- solver.writeProtocolToFile(progress, options[:solverLog])
292
- progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
335
+ session.to_solver(@progress, "(get-unsat-core)")
336
+ unsat_core = session.from_solver(@progress)
337
+ session.writeProtocolToFile(@progress, options[:solverLog])
338
+ @progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
293
339
  return nil
294
340
  else
295
- handleSolverError(progress, solver, options, result)
341
+ session.handleSolverError(@progress, options, result)
296
342
  end
297
343
 
298
- messages = []
299
- solver.to_solver "(reset)"
300
- solver.to_solver "(set-logic ALL)"
301
- solver.to_solver "(set-option :produce-unsat-cores true)"
344
+ return nil
345
+ end
302
346
 
303
- return messages
347
+ def convert_solver_value(type, datatype, value, xpath)
348
+ check_type(type, value, xpath)
349
+ if datatype == :date then
350
+ return (Time.at(0).to_date + Integer(value)).to_s
351
+ elsif datatype == :timestamp then
352
+ return Time.at(Integer(value)).to_s
353
+ else
354
+ return value
355
+ end
304
356
  end
305
357
 
306
358
  def check_type(type, value, xpath)
@@ -321,24 +373,119 @@ class Z3Solver
321
373
  end
322
374
  end
323
375
 
324
- def convert_solver_value(type, datatype, value, xpath)
325
- check_type(type, value, xpath)
326
- if datatype == :date then
327
- return (Time.at(0).to_date + Integer(value)).to_s
328
- elsif datatype == :timestamp then
329
- return Time.at(Integer(value)).to_s
376
+ end
377
+
378
+ class Z3Solver
379
+
380
+ attr_reader :z3in, :z3outAndErr
381
+
382
+ def initialize(progress, z3path)
383
+ @progress = progress
384
+ @z3in, @z3outAndErr, @z3thr = Open3.popen2e(z3path, '-in', '-smt2')
385
+ Process.detach(@z3thr.pid)
386
+ @z3in.sync = true
387
+ @sessionIdCounter = 0
388
+ end
389
+
390
+ def close()
391
+ begin
392
+ @z3in.close() unless @z3in.closed?
393
+ rescue Errno::EINVAL
394
+ end
395
+ begin
396
+ @z3outAndErr.close() unless @z3outAndErr.closed?
397
+ rescue Errno::EINVAL
398
+ end
399
+ Thread.kill(@z3thr)
400
+ end
401
+
402
+ def display_assertion(assertion, session)
403
+ if assertion =~ /validation_rule_(.*)_instance_(.*)/ then
404
+ return "Validation rule #{$1} of structure #{$2.gsub("_", "/")}"
330
405
  else
331
- return value
406
+ info = session.getAssociationForAssertionID(assertion)
407
+ if info then
408
+ if info[:assertion_type] == :doc_field_filled then
409
+ return "Field #{info[:xpath]} contains value #{info[:value].inspect} in document"
410
+ elsif info[:assertion_type] == :doc_field_empty then
411
+ return "Field #{info[:xpath]} is empty in document"
412
+ elsif info[:assertion_type] == :doc_structure_exists then
413
+ return "Structure #{info[:xpath]} exists in document"
414
+ elsif info[:assertion_type] == :doc_structure_not_exists then
415
+ return "Structure #{info[:xpath]} does not exist in document"
416
+ elsif info[:assertion_type] == :constraint then
417
+ if info[:constraint_type] == :min then
418
+ return "Field #{info[:xpath]} has a minimum value of #{info[:value]}."
419
+ elsif info[:constraint_type] == :max then
420
+ return "Field #{info[:xpath]} has a maximum value of #{info[:value]}."
421
+ elsif info[:constraint_type] == :required then
422
+ return "Field #{info[:xpath]} is required."
423
+ else
424
+ p info
425
+ raise RuntimeError.new("encountered unknown :constraint_type #{info[:constraint_type].inspect} for assertionID #{assertion.inspect}")
426
+ end
427
+ else
428
+ raise RuntimeError.new("encountered unknown :assertion_type #{info[:assertion_type].inspect} for assertionID #{assertion.inspect}")
429
+ end
430
+ else
431
+ return assertion
432
+ end
433
+ end
434
+ end
435
+
436
+ def parse_unsat_core(unsat_core, session)
437
+ unless unsat_core =~ /\((.*)\)/ then
438
+ raise RuntimeError.new("could not parse unsat core: #{unsat_core.inspect}")
439
+ end
440
+ list = $1.split(" ")
441
+
442
+ result = "EXPLANATION:\n"
443
+ result += "The following values\n"
444
+
445
+ list.filter{|a| a.start_with?("doc-")}.each do |assertion|
446
+ result += "- #{display_assertion(assertion, session)} (#{assertion})\n"
447
+ end
448
+ result += "\nviolate the following constraints\n"
449
+ list.filter{|a| !a.start_with?("doc-")}.each do |assertion|
450
+ result += "- #{display_assertion(assertion, session)} (#{assertion})\n"
451
+ end
452
+ return result
453
+ end
454
+
455
+ def self.find_matching_bracket(str)
456
+ i = 0
457
+ open = 0
458
+ in_str = false
459
+ while open <= 0 do
460
+ if str[i] == '(' and !in_str then
461
+ open += 1
462
+ elsif str[i] == '"' then
463
+ in_str = !in_str
464
+ end
465
+ i += 1
466
+ end
467
+ while open > 0 do
468
+ if str[i] == '(' && !in_str then
469
+ open += 1
470
+ elsif str[i] == ')' && !in_str then
471
+ open -= 1
472
+ elsif str[i] == '"' then
473
+ in_str = !in_str
474
+ end
475
+ i += 1
332
476
  end
477
+ str[0,i]
333
478
  end
334
479
 
335
- def foreach_field_in_model_str(str, solver, const_info, &block)
480
+ def self.foreach_field_in_model_str(str, const_info, &block)
336
481
  unless str =~ /^\((.*)\)$/m
337
482
  raise RuntimeError.new("could not parse model: #{str.inspect}")
338
483
  end
339
484
  str = $1
340
485
 
341
- str.split("\n").each do |line|
486
+ while str.size() > 0 do
487
+ line = Z3Solver.find_matching_bracket(str)
488
+ str = str[line.size()..]
342
489
  unless line =~ /\(([^\ ]*)\ (.*)\)/
343
490
  raise RuntimeError.new("could not parse model line: #{line.inspect}")
344
491
  end
@@ -356,8 +503,8 @@ class Z3Solver
356
503
  return nil
357
504
  end
358
505
 
359
- def handleSolverError(progress, solver, options, result)
360
- solver.writeProtocolToFile(progress, options[:solverLog])
506
+ def handleSolverError(progress, session, options, result)
507
+ session.writeProtocolToFile(progress, options[:solverLog])
361
508
  raise RuntimeError.new("Solver Error: Solver returned '#{result}', for more information see #{options[:solverLog]}")
362
509
  end
363
510
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbt-gen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - FM-enthusiast
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-26 00:00:00.000000000 Z
11
+ date: 2026-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri