Class: SolverFactory

Inherits:
Object
  • Object
show all
Defined in:
lib/solver-lib.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(progress, z3path) ⇒ SolverFactory

Returns a new instance of SolverFactory.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/solver-lib.rb', line 176

def initialize(progress, z3path)
  @progress = progress
  z3version = nil
  exitstatus = nil
  begin
    z3version = `#{z3path} --version`.chomp()
    exitstatus = $?.exitstatus
  rescue Errno::ENOENT
  end
  if exitstatus == 0 && z3version != nil then
    progress.print_line "INFO: Using #{z3version} found in z3path=#{z3path}"
    @z3path = z3path
    @z3version = z3version
  else
    progress.print_line "WARN: could not find z3 in z3path=#{z3path.inspect}"
    `which z3`
    if $?.exitstatus == 0 then
      z3version = `z3 --version`.chomp()
      progress.print_line "However, #{z3version} is availlable on the PATH. Using this one."
      @z3path = 'z3'
      @z3version = z3version
    else
      raise RuntimeError.new("ERROR: unable to find Z3 solver. Exiting! (Please provide its location in the config file using the key :z3path)")
    end
  end
end

Instance Attribute Details

#z3pathObject (readonly)

Returns the value of attribute z3path.



174
175
176
# File 'lib/solver-lib.rb', line 174

def z3path
  @z3path
end

#z3versionObject (readonly)

Returns the value of attribute z3version.



174
175
176
# File 'lib/solver-lib.rb', line 174

def z3version
  @z3version
end

Instance Method Details

#check_type(type, value, xpath) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/solver-lib.rb', line 358

def check_type(type, value, xpath)
  if type == "String" then
    unless value =~ /\".*\"/ then
      raise RuntimeError.new("inconsistent model value: model value for #{xpath} should be a String, but was #{value}.")
    end
  elsif type == "Bool"
    unless value == "true" or value == "false" then
      raise RuntimeError.new("inconsistent model value for #{xpath} should be a Bool, but was #{value.inspect}.")
    end
  elsif type == "Int"
    unless value =~ /\d+/ then
      raise RuntimeError.new("inconsistent model value: model value for #{xpath} should be a Int, but was #{value}.")
    end
  else
    raise RuntimeError.new("encountered unknown solver type: type of solver-var for #{xpath} is #{info[:type]}.")
  end
end

#convert_solver_value(type, datatype, value, xpath) ⇒ Object



347
348
349
350
351
352
353
354
355
356
# File 'lib/solver-lib.rb', line 347

def convert_solver_value(type, datatype, value, xpath)
  check_type(type, value, xpath)
  if datatype == :date then
    return (Time.at(0).to_date + Integer(value)).to_s
  elsif datatype == :timestamp then
    return Time.at(Integer(value)).to_s
  else
    return value
  end
end

#new_solverObject



203
204
205
# File 'lib/solver-lib.rb', line 203

def new_solver()
  Z3Solver.new(@progress, @z3path)
end

#query(options, &block) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/solver-lib.rb', line 207

def query(options, &block)
  unless options[:solverLog]
    raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
  end
  @progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")

  session = SolverSession.new(self, options[:solverLog])
  block.call(session)

  session.to_solver(@progress, "(check-sat)")
  result = session.from_solver(@progress)
  explanation = true
  if result == "sat"
      # model is not contradictory -> continue
  elsif result == "unsat"
      # model is contradictory -> add a message

      session.to_solver(@progress, "(get-unsat-core)")
      unsat_core = session.from_solver(@progress)
      explanation = session.parse_unsat_core(unsat_core)
  else
    session.handleSolverError(@progress, options, result)
  end

  return explanation
end

#query_model(options, &block) ⇒ Object

main entry point!



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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/solver-lib.rb', line 235

def query_model(options, &block)
  unless options[:solverLog]
    raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
  end
  @progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
  session = SolverSession.new(self, options[:solverLog])

  block.call(session)
  session.to_solver(@progress, "(check-sat)")
  result = session.from_solver(@progress)
  if result.chomp == "sat"
      # model is not contradictory -> extract model
      const_list = []
      const_info = {}
      session.for_each_const do |name, info|
        const_list << name
        const_info[name] = info
      end
      session.to_solver(@progress, "(get-value (#{const_list.join(" ")}))")
      model_str = session.read_multiline_from_solver()
      model = {}
      Z3Solver.foreach_field_in_model_str(model_str, const_info) do |varname, value, info|
        unless info then
          @progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
        end
        xpath = info[:xpath]
        if model.has_key?(xpath) then
          if varname.end_with?("-filled") then
            unless model[xpath][:type] == :field then
              raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field, not a #{model[xpath][:type]}.")
            end
            unless value == "true" or value == "false" then
              raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
            end
            if value == "true" then
              model[xpath][:filled] = true
            else
              model[xpath][:filled] = false
            end
          elsif varname.end_with?("-value") then
            unless model[xpath][:type] == :field || model[xpath][:type] == :list then
              raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field or a list, not a #{model[xpath][:type]}.")
            end
            if model[xpath][:type] == :field then
              model[xpath][:value] = convert_solver_value(info[:type], info[:datatype], value, xpath)
            elsif model[xpath][:type] == :list then
              unless varname =~ /-index-(\d+)-value$/
                raise RuntimeError.new("inconsistent model: xpath #{xpath} is a list value and hence its varname should end like -index-X-value, not #{varname.inspect}.")
              end
              index = Integer($1)
              model[xpath][:xpath_element] = info[:xpath_element]
              if model[xpath].has_key?(:value) then
                val = convert_solver_value(info[:type], info[:datatype], value, xpath)
                model[xpath][:value].insert(index, val)
              else
                val = convert_solver_value(info[:type], info[:datatype], value, xpath)
                model[xpath][:value] = [val]
              end
            end
          elsif varname.end_with?("-size") then
            unless model[xpath][:type] == :list || model[xpath][:type] == :field then
              raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list or a field, not a #{model[xpath][:type].inspect}.")
            end
            if model[xpath][:type] == :field then
              model[xpath][:type] = :list
            end
            model[xpath][:size] = Integer(value)
          elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
            unless model[xpath][:type] == :struct then
              raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a struct, not a #{model[xpath][:type]}.")
            end
            model[xpath][:exists] = value
          end
        else
          if varname.end_with?("-filled") then
            unless value == "true" or value == "false" then
              raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
            end
            if value == "true" then
              model[xpath] = { :type => :field, :filled => true }
            else
              model[xpath] = { :type => :field, :filled => false }
            end
          elsif varname.end_with?("-value") then
            check_type(info[:type], value, xpath)
            model[xpath] = {  :type => :field, :value => value }
          elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
            unless value == "true" or value == "false" then
              raise RuntimeError.new("inconsistent model value: exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
            end
            model[xpath] = { :type => :struct, :exists => value }
          elsif varname.end_with?("-size") then
            model[xpath] = { :type => :list, :size => value }
          end
        end
      end
      return model
  elsif result.chomp == "unsat"
      # model is contradictory -> add a message

      session.to_solver(@progress, "(get-unsat-core)")
      unsat_core = session.from_solver(@progress)
      session.writeProtocolToFile(@progress, options[:solverLog])
      @progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
      return nil
  else
    session.handleSolverError(@progress, options, result)
  end

  return nil
end