mbt-gen 0.0.4 → 0.0.6

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 +2073 -1976
  3. data/lib/progress.rb +6 -0
  4. data/lib/solver-lib.rb +572 -513
  5. metadata +2 -2
data/lib/mbt-gen.rb CHANGED
@@ -1,1976 +1,2073 @@
1
- #!/usr/bin/ruby
2
-
3
- require 'date'
4
- require 'fileutils'
5
- require 'tmpdir'
6
- require 'yaml'
7
- require 'time'
8
-
9
- require 'nokogiri'
10
-
11
- require_relative 'solver-lib'
12
- require_relative 'progress'
13
- require_relative 'regexp-to-smtlib'
14
-
15
-
16
- CHUNK_SIZE = 10000
17
- DEFAULT_Z3_PATH = "z3"
18
-
19
- CONFIG_FILE = "config.yml"
20
- OUTPUT_FILE = "result.xml"
21
- CONTINUATION_FILE = "continuation.log"
22
- COMBINATION_LOG = "combinations.log"
23
- VARS_LOG = "vars.log"
24
-
25
- OUTPUT_DIR = "result"
26
- LISTS_DIR = "lists"
27
- QUERY_DIR = "queries"
28
- PARAMETERIZED_QUERY_DIR = "parameterized_queries"
29
- XML_TEMPLATE = ""
30
-
31
- DEFAULT_CONFIG = {
32
- :z3path => "z3",
33
- :generation_system_mode => "Production",
34
- :generation_system => "Production"
35
- }
36
-
37
- USAGE = <<EOF
38
- usage:
39
-
40
- GENERATING A SINGLE MESSAGE:
41
-
42
- ruby #{$PROGRAM_NAME} -list-validation-rules
43
- - outputs a list of all validation rule names that have been specified in #{$PROGRAM_NAME}.
44
-
45
- ruby #{$PROGRAM_NAME} <options>
46
- - generates a message of the given type, which
47
- - is valid XML
48
- - adheres to the schema, and
49
- - satisfies all validation rules specified for messages of this type.
50
- where <options> is a combination of
51
- -negate <validation_rule> - negates the named validation rule. This causes the resulting message to still satisfy all other validation rules, but to violate this one.
52
- NOTE: the result can be found in #{OUTPUT_FILE}
53
-
54
- GENERATING MESSAGES FOR ALL COMBINATIONS:
55
-
56
- ruby #{$PROGRAM_NAME} -list-keys
57
- - outputs a list of all keys that have been used in #{$PROGRAM_NAME} to mark fields or structures.
58
-
59
- ruby #{$PROGRAM_NAME} -list <key>
60
- - outputs a list of all fields and structures that have been marked with <key> in #{$PROGRAM_NAME}.
61
-
62
- ruby #{$PROGRAM_NAME} -count-docs-for-key <key>
63
- - calculates the number of combinations of all fields and structures marked with <key>
64
-
65
- ruby #{$PROGRAM_NAME} -generate-docs-for-key <key> <options>
66
- - generates all valid messages of the given type that can be derived from combinations of all the fields and structures marked with <key>.
67
- where <options> is a combination of
68
- -continue - continues a previously interrupted generation process. Must use the same <key> the the interrupted generation.ARGF
69
- -max-num-docs <limit> - stops generation process after generating <limit> documents.
70
- NOTE: enumerating large combination-spaces may take a long time.
71
- - Use -count-docs-for-key to measure the size of the combination-space beforehand.
72
- - Also: you can interrupt the generation process at any time using Ctrl+C. The intermediate result is stored in the directory #{OUTPUT_DIR} and the generation process can
73
- be restarted later by adding -continue to the command line.
74
- NOTE: do not expect the number of generated documents to equal the number of combinations as combinations may not have any valid message instances (for instance when they contradict the validation rules).
75
- NOTE: the result can be found in the directory #{OUTPUT_DIR}
76
-
77
- VALIDATING THE MODEL:
78
-
79
- ruby #{$PROGRAM_NAME} -validate <file.xml>
80
- - reads in the given XML file and validates it against the model - that is, it checks whether it conforms to the specified XML schema and to the validation rules.
81
- EOF
82
-
83
- class FatalError < RuntimeError
84
- end
85
-
86
- class NormalProgramTermination < RuntimeError
87
- end
88
-
89
- def raw_field(xpath)
90
- path_str = "base"
91
- path_str = xpath.gsub("/", "-") if xpath
92
- "field-#{path_str}"
93
- end
94
-
95
- def filled_var_for_field(xpath)
96
- "#{raw_field(xpath)}-filled"
97
- end
98
-
99
- def value_var_for_field(xpath)
100
- "#{raw_field(xpath)}-value"
101
- end
102
-
103
- def exists_var_for_structure(xpath)
104
- struct_str = "base"
105
- struct_str = xpath.gsub("/", "-") if xpath
106
- "struct-#{struct_str}-exists"
107
- end
108
-
109
- def size_var_for_list(xpath)
110
- "#{raw_field(xpath)}-size"
111
- end
112
-
113
- def value_var_for_list(xpath, index)
114
- "#{raw_field(xpath)}-index-#{index}-value"
115
- end
116
-
117
- def translate_datatype_to_SMTLIB(datatype)
118
- case datatype
119
- when :string
120
- return "String"
121
- when :int
122
- return "Int"
123
- when :bool
124
- return "Bool"
125
- when :date
126
- return "Int"
127
- when :timestamp
128
- return "Int"
129
- when :enum
130
- return "String"
131
- end
132
- end
133
-
134
- def translate_value_to_SMTLIB(value)
135
- if value.is_a?(Integer) then
136
- return value.to_s
137
- elsif value.is_a?(String) then
138
- return value.inspect
139
- elsif value.is_a?(Date) then
140
- return (value - (Time.at(0).to_date)).to_i # days since 1970.01.01
141
- elsif value.is_a?(Time) then
142
- return value.to_i
143
- else
144
- raise RuntimeError.new("cannot translate value to SMTLIB: #{value.inspect}")
145
- end
146
- end
147
-
148
- def min_constraint_for_expr(expr, lower_bound)
149
- "(>= #{expr} #{lower_bound})"
150
- end
151
-
152
- def max_constraint_for_expr(expr, upper_bound)
153
- "(<= #{expr} #{upper_bound})"
154
- end
155
-
156
- def regex_constraint_for_expr(expr, regex)
157
- "(str.in.re #{expr} #{Regexp_to_SMTLIB.translate_regexp_to_SMTLIB(regex)})"
158
- end
159
-
160
- def maxLength_contraint_for_expr(expr, maxLength)
161
- "(<= (str.len #{expr}) #{maxLength})"
162
- end
163
-
164
- def oneOf_contraint_for_expr(expr, values)
165
- list = values.map do |val|
166
- "(= #{expr} \"#{val}\")"
167
- end
168
- "(or #{list.join(" ")})"
169
- end
170
-
171
- def enum_constraint_for_expr(expr, values)
172
- "(or #{values.map{|x| "(= #{expr} #{translate_value_to_SMTLIB(x)})"}.join(" ")})"
173
- end
174
-
175
- def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef, xpath)
176
- varname = raw_field(xpath)
177
- if fielddef[:optional] == nil || fielddef[:optional] == false then
178
- # field is required
179
- assertionID = "required-#{varname}"
180
- info = {
181
- :assertion_type => :constraint,
182
- :xpath => xpath,
183
- :constraint_type => :required
184
- }
185
- solver.associateAssertionIDWith(assertionID, info)
186
- solver.to_solver(progress, "(assert (! #{varname}-filled :named #{assertionID}))")
187
- end
188
- datatype = fielddef[:datatype]
189
- case datatype
190
- when :int
191
- if fielddef[:min] then
192
- assertionID = "min-#{varname}"
193
- info = {
194
- :assertion_type => :constraint,
195
- :xpath => xpath,
196
- :datatype => :int,
197
- :value => fielddef[:min],
198
- :constraint_type => :min
199
- }
200
- solver.associateAssertionIDWith(assertionID, info)
201
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{min_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named #{assertionID}))")
202
- end
203
- if fielddef[:max] then
204
- assertionID = "max-#{varname}"
205
- info = {
206
- :assertion_type => :constraint,
207
- :xpath => xpath,
208
- :datatype => :int,
209
- :value => fielddef[:max],
210
- :constraint_type => :max
211
- }
212
- solver.associateAssertionIDWith(assertionID, info)
213
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{max_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named #{assertionID}))")
214
- end
215
- when :string
216
- if fielddef[:regex] then
217
- assertionID = "regex-#{varname}"
218
- info = {
219
- :assertion_type => :constraint,
220
- :xpath => xpath,
221
- :datatype => :string,
222
- :pattern => fielddef[:regex],
223
- :constraint_type => :regex
224
- }
225
- solver.associateAssertionIDWith(assertionID, info)
226
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{regex_constraint_for_expr("#{varname}-value", fielddef[:regex])}) :named #{assertionID}))")
227
- end
228
- # TODO: minLength?
229
- if fielddef[:maxLength] then
230
- assertionID = "maxLength-#{varname}"
231
- info = {
232
- :assertion_type => :constraint,
233
- :xpath => xpath,
234
- :datatype => :string,
235
- :value => fielddef[:maxLength],
236
- :constraint_type => :maxLength
237
- }
238
- solver.associateAssertionIDWith(assertionID, info)
239
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{maxLength_contraint_for_expr("#{varname}-value", fielddef[:maxLength])}) :named #{assertionID}))")
240
- end
241
- if fielddef[:from_list] then
242
- assertionID = "oneOf-#{varname}"
243
- list_filename = "#{LISTS_DIR}/#{fielddef[:from_list]}"
244
- info = {
245
- :assertion_type => :constraint,
246
- :xpath => xpath,
247
- :datatype => :string,
248
- :filename => list_filename,
249
- :constraint_type => :from_list
250
- }
251
- unless File.exist?(list_filename)
252
- raise RuntimeError.new("could not find list file #{list_filename}.")
253
- end
254
- values = File.read(list_filename).split("\n").map{|v| v.chomp()}
255
- solver.associateAssertionIDWith(assertionID, info)
256
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{oneOf_contraint_for_expr("#{varname}-value", values)}) :named #{assertionID}))")
257
- end
258
- when :enum
259
- assertionID = "enum-#{varname}"
260
- info = {
261
- :assertion_type => :constraint,
262
- :xpath => xpath,
263
- :datatype => :enum,
264
- :values => fielddef[:values],
265
- :constraint_type => :values
266
- }
267
- solver.associateAssertionIDWith(assertionID, info)
268
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{enum_constraint_for_expr("#{varname}-value", fielddef[:values])}) :named #{assertionID}))")
269
- end
270
- end
271
-
272
- def translate_list_def_to_SMTLIB_constraints(progress, solver, fielddef, varname)
273
- datatype = fielddef[:datatype]
274
- case datatype
275
- when :int
276
- if fielddef[:min] then
277
- fielddef[:model_maxLength].times do |i|
278
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{min_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named list-min-#{varname}-list-index-#{i}))")
279
- end
280
- end
281
- if fielddef[:max] then
282
- fielddef[:model_maxLength].times do |i|
283
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{max_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named list-max-#{varname}-list-index-#{i}))")
284
- end
285
- end
286
- when :string
287
- if fielddef[:regex] then
288
- fielddef[:model_maxLength].times do |i|
289
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{regex_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:regex])}) :named list-regex-#{varname}-index-#{i}))")
290
- end
291
- end
292
- # TODO: minLength?
293
- if fielddef[:maxLength] then
294
- fielddef[:model_maxLength].times do |i|
295
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{maxLength_contraint_for_expr("#{varname}-index-#{i}-value", fielddef[:maxLength])}) :named list-maxLength-#{varname}-index-#{i}))")
296
- end
297
- end
298
- when :enum
299
- fielddef[:model_maxLength].times do |i|
300
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{enum_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:values])}) :named list-enum-#{varname}-index-#{i}))")
301
- end
302
- end
303
- end
304
-
305
- def default_value_for_fielddef(fielddef)
306
- datatype = fielddef[:datatype]
307
- case datatype
308
- when :int
309
- return "0"
310
- when :string
311
- return "".inspect
312
- when :date
313
- return "0"
314
- when :timestamp
315
- return "0"
316
- when :enum
317
- return "".inspect
318
- else
319
- raise RuntimeError.new("unknown datatype: #{datatype.inspect}")
320
- end
321
- end
322
-
323
- def translate_structure_to_SMTLIB(progress, solver, struct, hash = {})
324
- prefix = hash[:prefix]
325
- config = hash[:config]
326
- options = hash[:options]
327
- translate_substructure_to_SMTLIB(progress, solver, struct, hash)
328
- translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
329
- if options[:add_SMTLIB] then
330
- solver.to_solver(progress, "; --- additional SMTLIB Code: ---")
331
- solver.to_solver(progress, options[:add_SMTLIB])
332
- end
333
- end
334
-
335
- def translate_substructure_to_SMTLIB(progress, solver, struct, hash = {})
336
- prefix = hash[:prefix]
337
- struct_varname = hash[:struct_varname]
338
- config = hash[:config]
339
- options = hash[:options]
340
- solver.to_solver(progress, "; --- Structure: ---")
341
- struct.each do |field, definition|
342
- if field == :additional_smtlib then
343
- solver.to_solver(progress, "; additional SMTLIB of structure #{prefix}")
344
- solver.to_solver(progress, translate_validation_rule_to_SMTLIB(definition, struct, prefix, config, options, "<additional SMTLIB of structure #{prefix}>"))
345
- elsif field == :validation_rules || field == :predicates || field == :parent_link || field == :struct_xpath_prefix
346
- next
347
- else
348
- solver.to_solver(progress, "; #{field.to_s}")
349
- if definition[:type] == :field then
350
- display_prefix = ""
351
- display_prefix = "#{prefix}/" if prefix
352
- xpath = field.to_s
353
- xpath = "#{prefix}/#{xpath}" if prefix
354
- solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
355
- solver.declare_const(progress, value_var_for_field(xpath), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :datatype => definition[:datatype]})
356
- solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{value_var_for_field(xpath)} #{default_value_for_fielddef(definition)})) :named empty-field-has-no-value-for-#{raw_field(xpath)}))")
357
- if (struct_varname)
358
- solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
359
- end
360
- translate_field_def_to_SMTLIB_constraints(progress, solver, definition, xpath)
361
- elsif definition[:type] == :list then
362
- display_prefix = ""
363
- display_prefix = "#{prefix}/" if prefix
364
- xpath = field.to_s
365
- xpath = "#{prefix}/#{xpath}" if prefix
366
- solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
367
- solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
368
- solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-list-field-has-zero-size-#{raw_field(xpath)}))")
369
- definition[:model_maxLength].times do |i|
370
- solver.declare_const(progress, value_var_for_list(xpath, i), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :xpath_element => definition[:xpath_element], :index => i, :datatype => definition[:datatype]})
371
- solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (= #{value_var_for_list(xpath, i)} #{default_value_for_fielddef(definition)})) :named empty-list-field-has-no-value-for-#{raw_field(xpath)}-index-#{i}))")
372
- end
373
- if (struct_varname)
374
- solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (= #{size_var_for_list(xpath)} 0)) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
375
- end
376
- translate_list_def_to_SMTLIB_constraints(progress, solver, definition, raw_field(xpath))
377
- elsif definition[:type] == :structure then
378
- xpath = field.to_s
379
- xpath = "#{prefix}/#{xpath}" if prefix
380
- solver.declare_const(progress, exists_var_for_structure(xpath), "Bool", {:xpath => xpath})
381
- if (struct_varname)
382
- solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{exists_var_for_structure(xpath)})) :named empty-struct-has-no-substructure-#{raw_field(xpath)}))")
383
- end
384
- translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => xpath, :struct_varname => exists_var_for_structure(xpath), :options => options, :config => config)
385
- else
386
- next
387
- end
388
- end
389
- end
390
- end
391
-
392
- def resolve_field_path(path, relative_to_struct, prefix)
393
- current = relative_to_struct
394
- result = nil
395
- path.split("/").each do |segment|
396
- unless current then
397
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
398
- end
399
- field_def = current[segment.to_sym]
400
- unless field_def then
401
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
402
- end
403
- if field_def[:type] == :structure then
404
- unless field_def[:ref] then
405
- raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
406
- end
407
- current = field_def[:ref]
408
- if prefix then
409
- prefix = "#{prefix}/#{segment}"
410
- else
411
- prefix = segment
412
- end
413
- elsif field_def[:type] == :field then
414
- current = nil
415
- result = segment
416
- if prefix
417
- result = "#{prefix}/#{result}"
418
- end
419
- elsif field_def[:type] == :list then
420
- current = nil
421
- result = segment
422
- if prefix
423
- result = "#{prefix}/#{result}"
424
- end
425
- end
426
- end
427
- return result
428
- end
429
-
430
- def resolve_field_def(path, relative_to_struct, prefix)
431
- current = relative_to_struct
432
- result = nil
433
- path.split("/").each do |segment|
434
- unless current then
435
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
436
- end
437
- field_def = current[segment.to_sym]
438
- unless field_def then
439
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
440
- end
441
- if field_def[:type] == :structure then
442
- unless field_def[:ref] then
443
- raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
444
- end
445
- current = field_def[:ref]
446
- if prefix then
447
- prefix = "#{prefix}/#{segment}"
448
- else
449
- prefix = segment
450
- end
451
- elsif field_def[:type] == :field then
452
- current = nil
453
- result = field_def
454
- elsif field_def[:type] == :list then
455
- current = nil
456
- result = field_def
457
- end
458
- end
459
- return result
460
- end
461
-
462
- def resolve_struct_path(path, relative_to_struct, prefix)
463
- current = relative_to_struct
464
- result = nil
465
- path.split("/").each do |segment|
466
- unless current then
467
- raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
468
- end
469
- if segment == "." then
470
- result = current[:struct_xpath_prefix]
471
- elsif segment == ".." then
472
- current = current[:parent_link]
473
- if prefix then
474
- prefix_segs = prefix.split("/")
475
- prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
476
- else
477
- prefix = current[:struct_xpath_prefix]
478
- end
479
- else
480
- field_def = current[segment.to_sym]
481
- unless field_def then
482
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
483
- end
484
- if field_def[:type] == :structure then
485
- current = field_def[:ref]
486
- if prefix then
487
- prefix = "#{prefix}/#{segment}"
488
- else
489
- prefix = segment
490
- end
491
- result = prefix
492
- end
493
- end
494
- end
495
- return result
496
- end
497
-
498
- def resolve_struct_def(path, relative_to_struct, prefix, context_desc = "")
499
- current = relative_to_struct
500
- result = nil
501
- path.split("/").each do |segment|
502
- unless current then
503
- raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
504
- end
505
- if segment == "." then
506
- result = current
507
- elsif segment == ".." then
508
- current = current[:parent_link]
509
- if prefix then
510
- prefix_segs = prefix.split("/")
511
- prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
512
- else
513
- prefix = current[:struct_xpath_prefix]
514
- end
515
- else
516
- field_def = current[segment.to_sym]
517
- unless field_def then
518
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
519
- end
520
- if field_def[:type] == :structure then
521
- current = field_def[:ref]
522
- if prefix then
523
- prefix = "#{prefix}/#{segment}"
524
- else
525
- prefix = segment
526
- end
527
- result = current
528
- end
529
- end
530
- end
531
- return result
532
- end
533
-
534
- def translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
535
- solver.to_solver(progress, "; --- Validation Rules ---")
536
- translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
537
- struct.each_pair do |key, value|
538
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
539
- if value[:type] == :structure then
540
- if prefix then
541
- new_prefix = "#{prefix.to_s}/#{key}"
542
- else
543
- new_prefix = key.to_s
544
- end
545
- translate_validation_rules_to_SMTLIB(progress, solver, value[:ref], new_prefix, config, options)
546
- end
547
- end
548
- end
549
-
550
- def translate_validation_rule_to_SMTLIB(rule, context_struct, prefix, config, options, rule_name)
551
- date_now = options[:date_now]
552
- timestamp_now = options[:timestamp_now]
553
- return rule.gsub(/FILLED\[[^\]]*\]/) do |m|
554
- unless m =~ /FILLED\[([^\]]*)\]/ then
555
- raise RuntimeError.new("could not parse FILLED[...]: #{m.inspect} in validation rule #{rule_name}")
556
- end
557
- field = resolve_field_path($1, context_struct, prefix)
558
- filled_var_for_field(field)
559
- end
560
- .gsub(/VALUE\[[^\]]*\]/) do |m|
561
- unless m =~ /VALUE\[([^\]]*)\]/ then
562
- raise RuntimeError.new("could not parse VALUE[...]: #{m.inspect} in validation rule #{rule_name}")
563
- end
564
- field = resolve_field_path($1, context_struct, prefix)
565
- value_var_for_field(field)
566
- end
567
- .gsub(/EXIST\[[^\]]*\]/) do |m|
568
- unless m =~ /EXIST\[([^\]]*)\]/ then
569
- raise RuntimeError.new("could not parse EXIST[...]: #{m.inspect} in validation rule #{rule_name}")
570
- end
571
- s = resolve_struct_path($1, context_struct, prefix)
572
- exists_var_for_structure(s)
573
- end
574
- .gsub(/SIZE\[[^\]]*\]/) do |m|
575
- unless m =~ /SIZE\[([^\]]*)\]/ then
576
- raise RuntimeError.new("could not parse SIZE[...]: #{m.inspect} in validation rule #{rule_name}")
577
- end
578
- l = resolve_field_path($1, context_struct, prefix)
579
- size_var_for_list(l)
580
- end
581
- .gsub(/LIST_CONTAINS\[[^\]]*,[^\]]*\]/) do |m|
582
- unless m =~ /LIST_CONTAINS\[([^\]]*),([^\]]*)\]/ then
583
- raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
584
- end
585
- l = resolve_field_path($1, context_struct, prefix)
586
- l_def = resolve_field_def($1, context_struct, prefix)
587
- value = $2
588
- terms = []
589
- l_def[:model_maxLength].times do |i|
590
- terms << "(and (< #{i} #{size_var_for_list(l)}) (= #{value_var_for_list(l, i)} #{value}))"
591
- end
592
- "(or #{terms.join(" ")})"
593
- end
594
- .gsub(/LIST_VALUE_FIRST_ELEMENT\[[^\]]*\]/) do |m|
595
- unless m =~ /LIST_VALUE_FIRST_ELEMENT\[([^\]]*)\]/ then
596
- raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
597
- end
598
- xpath = $1
599
- l = resolve_field_path(xpath, context_struct, prefix)
600
- value_var_for_list(l, 0)
601
- end
602
- .gsub("[Date.now]", date_now.to_s)
603
- .gsub("[Timestamp.now]", timestamp_now.to_s)
604
- .gsub(/PREDICATE\[[^\]]*,[^\]]*\]/) do |m|
605
- unless m =~ /PREDICATE\[([^\]]*),([^\]]*)\]/ then
606
- raise RuntimeError.new("could not parse PREDICATE[...]: #{m.inspect} in validation rule #{rule_name}")
607
- end
608
- path = $1
609
- key = $2.strip().to_sym
610
- sdef = resolve_struct_def(path, context_struct, prefix, "in validation rule #{rule_name}")
611
- unless sdef
612
- raise RuntimeError.new("could not resolve struct path #{path.inspect} in validation rule #{rule_name}")
613
- end
614
- preds = sdef[:predicates]
615
- mdk = preds[key]
616
- if mdk then
617
- if mdk[:smtlib] then
618
- translate_validation_rule_to_SMTLIB(mdk[:smtlib], sdef, sdef[:struct_xpath_prefix], config, options, "#{rule_name}.predicate.#{key}[#{sdef[:struct_xpath_prefix]}]" )
619
- else
620
- raise RuntimeError.new("Predicate #{key} for structure #{sdef[:struct_xpath_prefix]} does not have an SMTLIB definition, but is referenced in validation rule #{rule_name}")
621
- end
622
- else
623
- raise RuntimeError.new("No predicate #{key.inspect} defined for structure #{sdef[:struct_xpath_prefix]}, but one referenced in validation rule #{rule_name}")
624
- end
625
- end
626
- .gsub(/CONFIG\[[^\]]*\]/) do |m|
627
- unless m =~ /CONFIG\[([^\]]*)\]/ then
628
- raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
629
- end
630
- key = $1.to_sym
631
- if config.has_key?(key) then
632
- "\"#{config[key]}\""
633
- else
634
- raise RuntimeError.new("unknown CONFIG-key #{key.inspect} encountered in validation rule #{rule_name}" )
635
- end
636
- end
637
- .gsub(/FIELD_IN_QUERY\[[^\]]*\]/) do |m|
638
- unless m =~ /FIELD_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
639
- raise RuntimeError.new("could not parse FIELD_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
640
- end
641
- xpath = $1
642
- query_filename = $2
643
- fpath = resolve_field_path(xpath, context_struct, prefix)
644
- fdef = resolve_field_def(xpath, context_struct, prefix)
645
- values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
646
- if fdef[:datatype] == :string then
647
- terms = values.filter{|l| !l.start_with?("#")}.map do |val|
648
- "(= #{value_var_for_field(fpath)} \"#{val}\")"
649
- end
650
- elsif fdef[:datatype] == :int then
651
- terms = values.filter{|l| !l.start_with?("#")}.map do |val|
652
- "(= #{value_var_for_field(fpath)} #{Integer(val)})"
653
- end
654
- else
655
- raise RuntimeError.new("FIELD_IN_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string or int.")
656
- end
657
- "(or #{terms.join(" ")})"
658
- end
659
- .gsub(/FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[[^\]]*\]/) do |m|
660
- unless m =~ /FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[([^\]]*),([^\]]*),([^\]]*)\]/ then
661
- raise RuntimeError.new("could not parse FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[..,..,..]: #{m.inspect} in validation rule #{rule_name}")
662
- end
663
- field_xpath = $1
664
- param_smtlib = $2
665
- query_filename = $3
666
- fpath = resolve_field_path(field_xpath.strip(), context_struct, prefix)
667
- fdef = resolve_field_def(field_xpath.strip(), context_struct, prefix)
668
- query_result = YAML.load_file("#{PARAMETERIZED_QUERY_DIR}/#{query_filename.strip()}")
669
- terms = []
670
- if fdef[:datatype] == :string then
671
- terms << "(or #{query_result.keys.map { |key| "(= #{param_smtlib} \"#{key}\")" }.join(" ")})"
672
- query_result.each_pair do |key, values|
673
- terms << "(=> (= #{param_smtlib} \"#{key}\") (or #{values.map{ |v| "(= #{value_var_for_field(fpath)} \"#{v}\")" }.join(" ")}))"
674
- end
675
- else
676
- raise RuntimeError.new("FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string.")
677
- end
678
- "(and #{terms.join(" ")})"
679
- end
680
- .gsub(/CONFIG_IN_QUERY\[[^\]]*\]/) do |m|
681
- unless m =~ /CONFIG_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
682
- raise RuntimeError.new("could not parse CONFIG_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
683
- end
684
- key = $1.to_sym
685
- unless config.has_key?(key) then
686
- raise RuntimeError.new("unknown CONFIG key #{key.inspect} encountered in validation rule #{rule_name}")
687
- end
688
- query_filename = $2
689
- values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
690
- terms = values.filter{|l| !l.start_with?("#")}.map do |val|
691
- "(= \"#{config[key]}\" \"#{val}\")"
692
- end
693
- "(or #{terms.join(" ")})"
694
- end
695
- end
696
-
697
- def translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
698
- return unless struct[:validation_rules]
699
-
700
- solver.to_solver(progress, "; --- Validation Rules: ---")
701
- negated_rules = options[:negated_validation_rules] || []
702
- solver.to_solver(progress, "; negated rules: #{negated_rules.empty? ? "<none>" : negated_rules.join(", ")}")
703
-
704
- rules = struct[:validation_rules]
705
- rules.each do |rule_name, rule|
706
- rule = rule[:smtlib] # ignore the text representation
707
- next unless rule # warning about this omission is the job of validate_model()
708
- rule_SMTLIB = translate_validation_rule_to_SMTLIB(rule, struct, prefix, config, options, rule_name)
709
- if negated_rules.include?(rule_name) then
710
- rule_SMTLIB = "(not #{rule_SMTLIB})"
711
- end
712
- solver.to_solver(progress, "(assert (! #{rule_SMTLIB} :named validation_rule_#{rule_name}_instance_#{prefix.gsub("/", "_")}))")
713
- end
714
- end
715
-
716
- def parse_field_value(fielddef, value)
717
- value = value.gsub("\n", "")
718
- datatype = fielddef[:datatype]
719
- case datatype
720
- when :int
721
- return Integer(value)
722
- when :string
723
- return value
724
- when :enum
725
- return value
726
- when :date
727
- return Date.parse(value)
728
- when :timestamp
729
- return Time.new(value)
730
- end
731
- end
732
-
733
- def solver_value_to_string(solver_value)
734
- if solver_value =~ /\"(.*)\"/ then
735
- return $1
736
- else
737
- return solver_value
738
- end
739
- end
740
-
741
- def build_document_from_model(progress, struct, model)
742
- doc = Nokogiri::XML(XML_TEMPLATE)
743
- add_model_struct_to_doc(progress, struct, doc, model)
744
- return doc
745
- end
746
-
747
- def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
748
- struct.each do |key, value|
749
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
750
- if prefix then
751
- xpath = "#{prefix}/#{key}"
752
- else
753
- xpath = key.to_s
754
- end
755
- model_value = model[xpath]
756
- if value[:type] == :field then
757
- unless model_value then
758
- raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
759
- end
760
- if model_value[:filled] == true || model_value[:filled] == "true" then
761
- doc.add_child("<#{key}>#{solver_value_to_string(model_value[:value])}</#{key}>")
762
- end
763
- elsif value[:type] == :list then
764
- node = doc.add_child("<#{key}></#{key}>")
765
- if node.is_a?(Nokogiri::XML::NodeSet) then
766
- node = node.first
767
- end
768
- xpath_element = value[:xpath_element]
769
- if Integer(model_value[:size]) > 0 then
770
- model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
771
- node.add_child("<#{xpath_element}>#{solver_value_to_string(mvalue)}</#{xpath_element}>")
772
- end
773
- end
774
- elsif value[:type] == :structure then
775
- unless value[:ref] then
776
- raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
777
- end
778
- if model_value[:exists] == true || model_value[:exists] == "true" then
779
- node = doc.add_child("<#{key}></#{key}>")
780
- if node.is_a?(Nokogiri::XML::NodeSet) then
781
- node = node.first
782
- end
783
- add_model_struct_to_doc(progress, value[:ref], node, model, xpath)
784
- end
785
- else
786
- raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
787
- end
788
- end
789
- end
790
-
791
- def collect_validation_rule_names(struct)
792
- result = []
793
- struct.each do |key, value|
794
- next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
795
- if key == :validation_rules
796
- value.each do |key, value|
797
- result << key
798
- end
799
- else
800
- if value[:type] == :structure
801
- result += collect_validation_rule_names(value[:ref])
802
- end
803
- end
804
- end
805
- return result
806
- end
807
-
808
- def set_standard_options(progress, solver)
809
- solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
810
- solver.to_solver(progress, "(set-option :smt.core.minimize true) ; ensure that unsat cores are minimal")
811
- solver.to_solver(progress, "")
812
- end
813
-
814
- def generate_doc(progress, message, output_filename, config, options)
815
- raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
816
- log = options[:log] || nil
817
- log.puts "generating doc #{output_filename}" if log
818
- options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
819
-
820
- solverFactory = options[:solverFactory]
821
- model = solverFactory.query_model(options) do |solver|
822
- set_standard_options(progress, solver)
823
- log.puts "translating structure" if log
824
- translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
825
- solver.to_solver(progress, "")
826
- end
827
-
828
- if model then
829
- result_doc = build_document_from_model(progress, message, model)
830
- dir = File.dirname(output_filename)
831
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
832
- File.open(output_filename, "w") do |f|
833
- f.puts result_doc.to_xml()
834
- end
835
- progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
836
- return true
837
- else
838
- progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
839
- return false
840
- end
841
- end
842
-
843
- def z3ValueToRubyValue(value)
844
- if value =~ /\"(.*)\"/ then # string
845
- return $1
846
- elsif value =~ /\d+/ then # int
847
- return Integer(value)
848
- elsif value == "true"
849
- return true
850
- elsif value == "false" then
851
- return false
852
- else
853
- raise RuntimeError.new("enountered unknown type of value in z3 model: #{value.inspect}")
854
- end
855
- end
856
-
857
- def extract_key_field_values_from_model(key_fields, model)
858
- result = {}
859
- key_fields.each do |xpath, fieldDesc|
860
- fieldInfo = model[xpath]
861
- if fieldDesc[:type] == :field then
862
- if fieldInfo[:filled] then
863
- result[xpath] = z3ValueToRubyValue(fieldInfo[:value])
864
- else
865
- result[xpath] = nil
866
- end
867
- elsif fieldDesc[:type] == :struct || fieldDesc[:type] == :structure then
868
- if fieldInfo.has_key?(:exists) then
869
- result[xpath] = z3ValueToRubyValue(fieldInfo[:exists])
870
- else
871
- result[xpath] = false
872
- end
873
- else
874
- raise RuntimeError.new("encountered unknown :type in solver model: #{fieldDesc[:type].inspect}")
875
- end
876
- end
877
- return result
878
- end
879
-
880
- def all_values_for_field(field)
881
- unless field[:type] == :field then
882
- raise RuntimeError.new("something that is not a field definition was passed to all_values_for_field(): #{field.inspect}")
883
- end
884
- unless field[:datatype] == :enum then
885
- raise RuntimeError.new("all_values_for_field() is only applicable to fields of datatype enum! not #{field[:datatype].inspect}.")
886
- end
887
- values = field[:values].dup
888
- if field[:optional] == true then
889
- values << nil
890
- end
891
- return values
892
- end
893
-
894
- def calc_combinations_for_key_fields(key_fields)
895
- count = 1
896
- key_fields.each do |field|
897
- if field[1][:type] == :field then
898
- if field[1][:datatype] == :enum then
899
- num_values = field[1][:values].size
900
- num_values += 1 if field[1][:optional]
901
- elsif field[1][:datatype] == :int then
902
- if field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
903
- if field[1][:optional] then
904
- num_values = 2 + field[1][:max] - field[1][:min]
905
- else
906
- num_values = 1 + field[1][:max] - field[1][:min]
907
- end
908
- else
909
- return "potentially infinite"
910
- end
911
- elsif field[1][:datatype] == :string then
912
- return "potentially infinite"
913
- end
914
- elsif field[1][:type] == :structure then
915
- num_values = 2
916
- end
917
- count *= num_values
918
- end
919
- return count
920
- end
921
-
922
- def calc_combinations(message, key)
923
- key_fields = collect_key_fields(key, message)
924
- if key_fields.empty?
925
- raise FatalError.new("unknown key: #{key}")
926
- end
927
- return calc_combinations_for_key_fields(key_fields)
928
- end
929
-
930
- def read_docs_from_log_file(log_filename)
931
- result = []
932
- if File.exist?(log_filename) then
933
- File.open(log_filename, "r") do |f|
934
- f.each_line do |line|
935
- doc = {}
936
- if line =~ /(.*)\ <-\ \{(.*)\}/ then
937
- filename = $1
938
- combination = $2
939
- combination.split(", ").each do |assignment|
940
- if assignment =~ /(.*):\ (.*)/ then
941
- key = $1
942
- value = $2
943
- if value =~ /\"(.*)\"/ then
944
- value = $1
945
- elsif value == "nil" then
946
- value = nil
947
- end
948
- doc[key] = value
949
- else
950
- raise RuntimeError.new("could not parse logfile #{log_filename}: invalid assignment #{assignment.inspect}")
951
- end
952
- end
953
- else
954
- raise RuntimeError.new("could not parse logfile #{log_filename}: invalid line #{line.inspect}")
955
- end
956
- result << doc
957
- end
958
- end
959
- end
960
- return result
961
- end
962
-
963
- def prepare_output_doc_filename(number, output_dir)
964
- chunk_number = number / 100000
965
- dirname = "#{output_dir}/#{chunk_number}"
966
- unless Dir.exist?(dirname) then
967
- FileUtils.mkdir_p(dirname)
968
- end
969
- return "#{dirname}/doc#{number}.xml"
970
- end
971
-
972
- def values_for_key_field(field)
973
- if field[:type] == :field then
974
- if field[:datatype] == :enum then
975
- if field[:optional] then
976
- return field[:values] + [nil]
977
- else
978
- return field[:values]
979
- end
980
- elsif field[:datatype] == :int then
981
- if field.has_key?(:min) and field.has_key?(:max) and field[:min] <= field[:max] then
982
- return (field[:min]..field[:max]).to_a
983
- else
984
- raise RuntimeError.new("cannot determine values for unbounded :int field")
985
- end
986
- else
987
- raise RuntimeError.new("cannot determine values for datatype #{field[:datatype].inspect}")
988
- end
989
- elsif field[:type] == :structure then
990
- if field[:optional] then
991
- return [true, false]
992
- else
993
- return [true]
994
- end
995
- else
996
- raise RuntimeError.new("Encountered unknown :type in model: #{field[:type].inspect} - keys can only mark fields or structures.")
997
- end
998
- end
999
-
1000
- def collect_key_fields(fkey, struct, prefix = nil)
1001
- result = {}
1002
- struct.each do |key, value|
1003
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1004
- if value[:type] == :structure
1005
- if value.has_key?(:keys) and value[:keys].include?(fkey) then
1006
- xpath = key.to_s
1007
- xpath = "#{prefix}/#{xpath}" if prefix
1008
- result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values].include?(k)}
1009
- end
1010
- if prefix then
1011
- new_prefix = "#{prefix}/#{key}"
1012
- else
1013
- new_prefix = key
1014
- end
1015
- result = result.merge(collect_key_fields(fkey, value[:ref], new_prefix))
1016
- elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1017
- xpath = key.to_s
1018
- xpath = "#{prefix}/#{xpath}" if prefix
1019
- result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values, :min, :max].include?(k)}
1020
- end
1021
- end
1022
- return result
1023
- end
1024
-
1025
- def collect_key_fields_and_strctures(fkey, struct, prefix = nil)
1026
- fields = []
1027
- structures = []
1028
- struct.each do |key, value|
1029
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1030
- if value[:type] == :structure
1031
- if value.has_key?(:keys) and value[:keys].include?(fkey) then
1032
- xpath = key.to_s
1033
- xpath = "#{prefix}/#{xpath}" if prefix
1034
- structures << xpath
1035
- end
1036
- if prefix then
1037
- new_prefix = "#{prefix}/#{key}"
1038
- else
1039
- new_prefix = key
1040
- end
1041
- result_fields, result_structures = collect_key_fields_and_strctures(fkey, value[:ref], new_prefix)
1042
- fields += result_fields
1043
- structures += result_structures
1044
- elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1045
- xpath = key.to_s
1046
- xpath = "#{prefix}/#{xpath}" if prefix
1047
- fields << xpath
1048
- end
1049
- end
1050
- return fields, structures
1051
- end
1052
-
1053
- def collect_keys(struct)
1054
- result = []
1055
- struct.each do |key, value|
1056
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1057
- if value[:type] == :structure
1058
- if value.has_key?(:keys) then
1059
- result += value[:keys]
1060
- end
1061
- if value[:ref] then
1062
- result += collect_keys(value[:ref])
1063
- else
1064
- raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1065
- end
1066
- elsif value[:type] == :field and value.has_key?(:keys) then
1067
- result += value[:keys]
1068
- end
1069
- end
1070
- result.uniq
1071
- end
1072
-
1073
- def collect_validation_rules(struct)
1074
- result = []
1075
- struct.each do |key, value|
1076
- next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1077
- if key == :validation_rules then
1078
- result += value.keys
1079
- else
1080
- if value[:type] == :structure
1081
- if value[:ref] then
1082
- result += collect_validation_rules(value[:ref])
1083
- else
1084
- raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1085
- end
1086
- end
1087
- end
1088
- end
1089
- result.uniq
1090
- end
1091
-
1092
- def list_keys(message)
1093
- keys = collect_keys(message)
1094
- puts "keys: #{keys.join(", ")}"
1095
- end
1096
-
1097
- def list_fields_and_structures(message, fkey)
1098
- fields, structures = collect_key_fields_and_strctures(fkey, message)
1099
- puts "fields for key #{fkey}:"
1100
- fields.each { |f| puts " #{f}" }
1101
- puts "structures for key #{fkey}:"
1102
- structures.each { |s| puts " #{s}" }
1103
- end
1104
-
1105
- def list_validation_rules(message)
1106
- rules = collect_validation_rules(message)
1107
- puts "Validation Rules:"
1108
- rules.each { |r| puts " #{r}" }
1109
- end
1110
-
1111
- def value_of_type(value, type)
1112
- if type == :string or type == :enum then
1113
- "\"#{value}\""
1114
- elsif type == :int then
1115
- value.to_s
1116
- elsif type == :date then
1117
- (Date.parse(value) - Time.at(0).to_date).to_i.to_s
1118
- elsif type == :timestamp then
1119
- (Time.new(value) - Time.at(0)).to_i.to_s
1120
- else
1121
- raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
1122
- end
1123
- end
1124
-
1125
- class CoreEnumerationAlgo
1126
-
1127
- def initialize()
1128
- @num_tries = 0
1129
- @num_docs = 0
1130
- @on_new_doc = nil
1131
- @on_unsat = nil
1132
- end
1133
-
1134
-
1135
- # fix values of all variables left from switched
1136
- def enumerate_fixing_clause(assignment, vars, switched)
1137
- clauses = []
1138
- switched.times do |index|
1139
- var_xpath = vars[index][0]
1140
- var_def = vars[index][1]
1141
- if var_def[:type] == :field then
1142
- if assignment[var_xpath] == nil then
1143
- clauses << "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named fixing_clause_#{index}))"
1144
- else
1145
- clauses << "(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(assignment[var_xpath], var_def[:datatype])})) :named fixing_clause_#{index}))"
1146
- end
1147
- elsif var_def[:type] == :structure then
1148
- clauses << "(assert (! (= #{exists_var_for_structure(var_xpath)} #{assignment[var_xpath].inspect}) :named fixing_clause_#{index}))"
1149
- end
1150
- end
1151
- "; fixing clauses:\n#{clauses.join("\n")}"
1152
- end
1153
-
1154
- def summarize_fixing_clause(assignment, vars, switched)
1155
- fixed = switched.times.to_a.map do |index|
1156
- var_xpath = vars[index][0]
1157
- "#{var_xpath}=#{assignment[var_xpath].inspect}"
1158
- end
1159
- "fixed(#{fixed.join(", ")})"
1160
- end
1161
-
1162
- # prevent the var from taking values that already have been enumerated exhaustively
1163
- def enumerate_blocking_clause(var_xpath, var_def, blocked)
1164
- clauses = []
1165
- num = 0
1166
- if var_def[:type] == :field then
1167
- blocked.each do |value|
1168
- if value == nil then
1169
- clauses << "(assert (! (not (= #{filled_var_for_field(var_xpath)} false)) :named blocking_clause_#{num}))"
1170
- else
1171
- clauses << "(assert (! (not (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(value, var_def[:datatype])}))) :named blocking_clause_#{num}))"
1172
- end
1173
- num += 1
1174
- end
1175
- elsif var_def[:type] == :structure then
1176
- blocked.each do |value|
1177
- clauses << "(assert (! (not (= #{exists_var_for_structure(var_xpath)} #{value.inspect})) :named blocking_clause_#{num}))"
1178
- num += 1
1179
- end
1180
- end
1181
- "; blocking clauses:\n#{clauses.join("\n")}"
1182
- end
1183
-
1184
- def summarize_blocking_clause(var_xpath, blocked)
1185
- "blocked(#{var_xpath})=[#{blocked.map{|v| v.inspect}.join(", ")}]"
1186
- end
1187
-
1188
- def generate_sparse_doc(progress, message, key, config, options, code, output_doc_filename)
1189
- additional_options = {}
1190
- if code then
1191
- additional_options = { :add_SMTLIB => code }
1192
- end
1193
- return generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options.merge(additional_options))
1194
- end
1195
-
1196
- def log_filename_for_output_filename(output_filename)
1197
- "#{output_filename}.smt2"
1198
- end
1199
-
1200
- def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
1201
- raise RuntimeError.new("option :solverFactory is required.") unless options[:solverFactory]
1202
- log = options[:log] || nil
1203
- log.puts "generating doc #{output_filename}" if log
1204
- options[:solverLog] = log_filename_for_output_filename(output_filename)
1205
-
1206
- key_fields = collect_key_fields(key, message)
1207
-
1208
- solverFactory = options[:solverFactory]
1209
- model = solverFactory.query_model(options) do |solver|
1210
- set_standard_options(progress, solver)
1211
- log.puts "translating structure" if log
1212
- translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
1213
- solver.to_solver(progress, "")
1214
- end
1215
-
1216
- if model then
1217
- result_doc = build_document_from_model(progress, message, model)
1218
- dir = File.dirname(output_filename)
1219
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1220
- File.open(output_filename, "w") do |f|
1221
- f.puts result_doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
1222
- end
1223
- progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
1224
- return extract_key_field_values_from_model(key_fields, model)
1225
- else
1226
- progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
1227
- return nil
1228
- end
1229
- end
1230
-
1231
- def store_continuation_point(doc_number, state, assignment, new_assignment, filename, options, time)
1232
- File.open(filename, "w") do |f|
1233
- state_str = "state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")})"
1234
- assignment_str = "assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1235
- new_assignment_str = "new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1236
- f.puts("#{doc_number};#{@num_docs};#{state_str};#{assignment_str};#{new_assignment_str};#{time}")
1237
- end
1238
- end
1239
-
1240
- def self.parse_state(state_str)
1241
- unless state_str =~ /state\((.*)\)/
1242
- return nil
1243
- end
1244
- split = $1.split("#")
1245
- split.map do |e|
1246
- if e == "nil" then
1247
- nil
1248
- elsif e =~ /\[(.*)\]/ then
1249
- $1.split(",").map do |i|
1250
- if i.strip == "nil" then
1251
- nil
1252
- elsif i.strip == "true" then
1253
- true
1254
- elsif i.strip == "false" then
1255
- false
1256
- elsif i.strip =~ /\"(.*)\"/ then
1257
- $1
1258
- end
1259
- end
1260
- end
1261
- end
1262
- end
1263
-
1264
- def self.parse_assignment(str)
1265
- unless str =~ /assignment\((.*)\)/
1266
- return nil
1267
- end
1268
- split = $1.split(",")
1269
- result = {}
1270
- split.each do |e|
1271
- s2 = e.split(":")
1272
- key = s2[0]
1273
- value = s2[1]
1274
- if value == "nil" then
1275
- result[key] = nil
1276
- elsif value == "true" then
1277
- result[key] = true
1278
- elsif value == "false" then
1279
- result[key] = false
1280
- elsif value =~ /\"(.*)\"/
1281
- result[key] = $1
1282
- end
1283
- end
1284
- result
1285
- end
1286
-
1287
- def self.parse_new_assignment(str)
1288
- unless str =~ /new_assignment\((.*)\)/
1289
- return nil
1290
- end
1291
- split = $1.split(",")
1292
- result = {}
1293
- split.each do |e|
1294
- s2 = e.split(":")
1295
- key = s2[0]
1296
- value = s2[1]
1297
- if value == "nil" then
1298
- result[key] = nil
1299
- elsif value == "true" then
1300
- result[key] = true
1301
- elsif value == "false" then
1302
- result[key] = false
1303
- elsif value =~ /\"(.*)\"/
1304
- result[key] = $1
1305
- end
1306
- end
1307
- result
1308
- end
1309
-
1310
- def self.parse_continuation_point(line)
1311
- split = line.split(";")
1312
- doc_number = Integer(split[0])
1313
- num_docs = Integer(split[1])
1314
- state = CoreEnumerationAlgo.parse_state(split[2])
1315
- assignment = CoreEnumerationAlgo.parse_assignment(split[3])
1316
- new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
1317
- processessing_time = Integer(split[5])
1318
- return [doc_number, num_docs, state, assignment, new_assignment, processessing_time]
1319
- end
1320
-
1321
- # TODO: logging
1322
-
1323
- def display_combination_space(assignment, vars, switched, blocked)
1324
- # enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1325
-
1326
- # => all vars left of switched are fixed to their assigned values (when the value is nil, then they are empty)
1327
- # => the switched var is prevented to have any of the blocked values
1328
- res = switched.times.to_a.map do |index|
1329
- var_xpath = vars[index][0]
1330
- "#{var_xpath}=#{assignment[var_xpath].inspect}"
1331
- end + ["#{vars[switched][0]}\{#{blocked.map{|v| v.inspect}.join(",")}}"]
1332
- "{#{res.join(", ")}}"
1333
- end
1334
-
1335
-
1336
- # core algo
1337
- def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size(), start_time, prior_processing_time)
1338
- enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time) if switched < vars.size()-1
1339
- is_finite_state = keys_are_finite_state(vars)
1340
- if state[switched] == nil then
1341
- blocked = [assignment[vars[switched][0]]]
1342
- state[switched] = blocked
1343
- else
1344
- blocked = state[switched] + [assignment[vars[switched][0]]]
1345
- state[switched] = blocked
1346
- end
1347
- found = true
1348
- while found do
1349
- found = false
1350
- # - fix all vars left of switched to their values in assignment
1351
- # - block switched from obtaining the values in blocked
1352
- # progress.print_line "DEBUG: #{summarize_fixing_clause(assignment, vars, switched)}, #{summarize_blocking_clause(vars[switched][0], blocked)}"
1353
- clauses = enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1354
- output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1355
- new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
1356
- generated_doc_number = @num_tries
1357
- time = prior_processing_time + (Time.now().to_i - options[:timestamp_now])
1358
- store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}", options, time)
1359
- progress.print_line "STORING continuation point"
1360
- progress.print_line "|time=#{time}"
1361
- progress.print_line "|doc_number=#{generated_doc_number}"
1362
- progress.print_line "|num_docs=#{@num_docs}"
1363
- progress.print_line "|state=#{state.inspect}"
1364
- progress.print_line "|assignment=#{assignment.inspect}"
1365
- progress.print_line "|new_assignment=#{new_assignment.inspect}"
1366
-
1367
- if is_finite_state then
1368
- progress.report_progress(determine_progress(state, vars), start_time, prior_processing_time, @num_docs)
1369
- else
1370
- progress.report_progress(nil, start_time, prior_processing_time, @num_docs)
1371
- end
1372
- @num_tries += 1
1373
- if new_assignment then # sat
1374
- @num_docs += 1
1375
- @on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
1376
- if switched < vars.size()-1 then
1377
- enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1378
- end
1379
- blocked << new_assignment[vars[switched][0]]
1380
- state[switched] = blocked
1381
- found = true
1382
- else # unsat
1383
- @on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked), switched) if @on_unsat
1384
- found = false
1385
- end
1386
- end
1387
- state[switched] = nil
1388
- end
1389
-
1390
- # NOTE: can only be called when all key-fields are finite-state
1391
- def is_final_state(state, new_assignment, key_fields_list)
1392
- return state[1,state.size()] == [nil] * (state.size()-1) &&
1393
- state[0].to_set == values_for_key_field(key_fields_list[0][1]).to_set &&
1394
- new_assignment.empty?
1395
- end
1396
-
1397
- # NOTE: can only be called when all key-fields are finite-state
1398
- def determine_progress(state, vars)
1399
- combinations_per_value = [nil] * vars.size()
1400
- comb = 1
1401
- vars.size().times.to_a.reverse_each do |i|
1402
- combinations_per_value[i] = comb
1403
- comb = values_for_key_field(vars[i][1]).size() * comb
1404
- end
1405
-
1406
- blocked_combinations = 0
1407
- state.size().times do |i|
1408
- if state[i] then
1409
- blocked_combinations += state[i].size() * combinations_per_value[i]
1410
- end
1411
- end
1412
- blocked_combinations
1413
- end
1414
-
1415
- def num_combinations(key_def)
1416
- if key_def[:type] == :field then
1417
- if key_def[:datatype] == :enum then
1418
- key_def[:values].size() + (key_def[:optional] ? 1 : 0)
1419
- elsif key_def[:datatype] == :int then
1420
- if key_def.has_key?(:min) and key_def.has_key?(:max) and key_def[:min] <= key_def[:max] then
1421
- 1 + key_def[:max] - key_def[:min] + (key_def[:optional] ? 1 : 0)
1422
- else
1423
- nil
1424
- end
1425
- else
1426
- nil
1427
- end
1428
- elsif key_def[:type] == :structure then
1429
- 2
1430
- end
1431
- end
1432
-
1433
- def keys_are_finite_state(list)
1434
- list.each do |field|
1435
- name = field[0]
1436
- if field[1][:type] == :field then
1437
- if field[1][:datatype] == :enum then
1438
- # enums are finite-state
1439
- elsif field[1][:datatype] == :int and field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
1440
- # ints are finite-state when both bounds are given and :min <= :max
1441
- else
1442
- return false
1443
- end
1444
- end
1445
- end
1446
- return true
1447
- end
1448
-
1449
- def generate_docs_for_key(progress, message, key, output_dir, config, options)
1450
-
1451
- prior_processing_time = 0
1452
- start_time = options[:timestamp_now]
1453
- number_of_combinations = calc_combinations(message, key)
1454
- if number_of_combinations.is_a?(String) then
1455
- progress.set_max(100000000000000)
1456
- else
1457
- progress.set_max(number_of_combinations)
1458
- end
1459
- progress.report_progress(0.0, start_time, prior_processing_time, 0)
1460
- progress.print_line "generating sparse documents efficiently..."
1461
- progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
1462
- progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
1463
-
1464
- progress.print_line "preparing output dir #{output_dir}..."
1465
- if options[:continue] then
1466
- unless Dir.exist?(output_dir) then
1467
- raise FatalError.new("FATAL: directory #{output_dir} not found. There seems to be nothing to continue from...")
1468
- end
1469
- log_mode = "a"
1470
- else
1471
- unless Dir.exist?(output_dir) then
1472
- FileUtils.mkdir_p(output_dir)
1473
- end
1474
- log_mode = "w"
1475
- end
1476
- log_filename = "#{output_dir}/#{COMBINATION_LOG}"
1477
-
1478
- key_fields = collect_key_fields(key, message)
1479
- key_fields_list = key_fields.each_pair.to_a
1480
- key_fields_list.sort! {|a,b| num_combinations(b[1]) <=> num_combinations(a[1]) }
1481
-
1482
- if options[:continue] then
1483
- stored_key_fields_str = File.read("#{output_dir}/#{VARS_LOG}")
1484
- unless key_fields_list.to_yaml == stored_key_fields_str then
1485
- raise FatalError.new("vars are differing! please specify the same key in order to use -continue!")
1486
- end
1487
- progress.print_line "continuing enumerating documents for key #{key}!"
1488
- else
1489
- # store vars
1490
- File.open("#{output_dir}/#{VARS_LOG}", "w") do |f|
1491
- f.puts(key_fields_list.to_yaml)
1492
- end
1493
- end
1494
-
1495
- cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
1496
- if options[:continue] then
1497
- unless File.exist?(cont_filename) then
1498
- raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
1499
- end
1500
- doc_number, num_docs, state, assignment, new_assignment, prior_processing_time = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
1501
- if is_final_state(state, new_assignment, key_fields_list) then
1502
- progress.report_progress(number_of_combinations, start_time, prior_processing_time, num_docs)
1503
- raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
1504
- end
1505
- progress.print_line "RESUMING enumeration from the following continuation point:"
1506
- progress.print_line "|time=#{prior_processing_time}"
1507
- progress.print_line "|doc_number=#{doc_number}"
1508
- progress.print_line "|num_docs=#{num_docs}"
1509
- progress.print_line "|state=#{state.inspect}"
1510
- progress.print_line "|assignment=#{assignment.inspect}"
1511
- progress.print_line "|new_assignment=#{new_assignment.inspect}"
1512
- if keys_are_finite_state(key_fields_list) then
1513
- progress.report_progress(determine_progress(state, key_fields_list), start_time, prior_processing_time, num_docs)
1514
- else
1515
- progress.report_progress(nil, start_time, prior_processing_time, num_docs)
1516
- end
1517
-
1518
- if new_assignment && !new_assignment.empty? then
1519
- assignment = new_assignment
1520
- end
1521
-
1522
- # restore state at continuation point
1523
- @num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
1524
- number_of_combinations += 1 # account for this in the safety mechanism
1525
- @num_docs = num_docs
1526
- File.open(log_filename, log_mode) do |log|
1527
-
1528
- @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1529
- # combination logging
1530
- log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1531
-
1532
- if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1533
- raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1534
- end
1535
-
1536
- # safety mechanism
1537
- if @num_docs > number_of_combinations then
1538
- raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1539
- end
1540
- }
1541
- @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1542
- log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1543
- progress.print_line "doc#{num_doc} NOT generated"
1544
- progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1545
- }
1546
-
1547
- # core algo - resume from state
1548
- enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1549
- end
1550
- else
1551
- # init safety mechanism
1552
- @num_tries = 0
1553
- @num_docs = 0
1554
- # first try - a freely generated document
1555
- File.open(log_filename, log_mode) do |log|
1556
- output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1557
- @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1558
- # combination logging
1559
- log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1560
-
1561
- if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1562
- raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1563
- end
1564
-
1565
- # safety mechanism
1566
- unless number_of_combinations.is_a?(String) then
1567
- if @num_docs > number_of_combinations then
1568
- raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1569
- end
1570
- end
1571
- }
1572
- @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1573
- log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1574
- progress.print_line "doc#{num_doc} NOT generated"
1575
- progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1576
- }
1577
- key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
1578
- @num_tries += 1
1579
- if key_values then
1580
- @num_docs += 1
1581
- @on_new_doc.call(@num_tries, output_doc_filename, key_values)
1582
- else
1583
- # NOTE: this only occurs when the model is inconsistent and has no valid documents
1584
- progress.print_line("ERROR: Model inconsistent - no valid documents could be found.")
1585
- @on_unsat.call(@num_tries, log_filename_for_output_filename(output_doc_filename), "{}", 0)
1586
- end
1587
-
1588
- # core algo - start enumeration
1589
- enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options, start_time, prior_processing_time)
1590
- end
1591
- end
1592
- progress.print_line "No more valid documents could be found. -> Exiting"
1593
- progress.report_progress(number_of_combinations, start_time, prior_processing_time, @num_docs)
1594
- end
1595
- end
1596
-
1597
- def introduce_parent_links(message, parent = nil, prefix = nil)
1598
- if parent != nil then
1599
- message[:parent_link] = parent
1600
- end
1601
- if prefix != nil then
1602
- message[:struct_xpath_prefix] = prefix
1603
- end
1604
- message.each_pair do |key, value|
1605
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1606
- if value[:type] == :structure then
1607
- if prefix then
1608
- new_prefix = "#{prefix}/#{key}"
1609
- else
1610
- new_prefix = key
1611
- end
1612
- introduce_parent_links(value[:ref], message, new_prefix)
1613
- end
1614
- end
1615
- end
1616
-
1617
- def validate_model(message, prefix=nil)
1618
-
1619
- message.each_pair do |key, value|
1620
- if key == :validation_rules then
1621
- value.each_pair do |vr_key, vr_value|
1622
- unless vr_value[:text] && vr_value[:text] != "" then
1623
- puts "INFO: Model Problem: validation rule #{vr_key} does not have a textual representation."
1624
- end
1625
- unless vr_value[:smtlib] && vr_value[:smtlib] != "" then
1626
- puts "WARN: Model Problem: validation rule #{vr_key} does not have an SMTLIB representation. It will hence be omitted!"
1627
- end
1628
- end
1629
- elsif key == :predicates then
1630
- value.each_pair do |mkey, mvalue|
1631
- next unless mvalue
1632
- unless mvalue[:text] && mvalue[:text] != "" then
1633
- puts "INFO: Model Problem: structure #{prefix}'s predicate #{mkey} does not have a textual representation."
1634
- end
1635
- unless mvalue[:smtlib] && mvalue[:smtlib] != "" then
1636
- puts "WARN: Model Problem: structure #{prefix}'s predicate #{mkey} does not have an SMTLIB representation. It will be regarded as always true."
1637
- end
1638
- end
1639
- elsif key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1640
- next
1641
- else
1642
- if value[:type] == :structure then
1643
- unless value[:ref] then
1644
- raise RuntimeError.new("Model Problem: encountered structure without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1645
- end
1646
- if prefix then
1647
- new_prefix = "#{prefix}/#{key}"
1648
- else
1649
- new_prefix = key
1650
- end
1651
- validate_model(value[:ref], new_prefix)
1652
- elsif value[:type] == :field then
1653
- unless value[:datatype] then
1654
- raise RuntimeError.new("Model Problem: encountered field without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1655
- end
1656
- if value[:datatype] == :enum then
1657
- unless value[:values] then
1658
- raise RuntimeError.new("Model Problem: encountered enum field without a :values (key=#{key.inspect}, value=#{value.inspect})")
1659
- end
1660
- end
1661
- end
1662
- end
1663
- end
1664
- end
1665
-
1666
- def validate_doc_against_schema(struct, doc)
1667
- root = doc.root
1668
- sdef = struct[root.name.to_sym]
1669
- unless sdef[:type] == :structure then
1670
- puts "ERROR: schema validation failed: root element should be a structure"
1671
- end
1672
- # first, check that each doc element is defined for the struct
1673
- validate_doc_struct_against_schema(sdef[:ref], root, root.name)
1674
- # second, check that all required elements from the struct are actually present in the doc
1675
- validate_schema_against_doc_struct(sdef[:ref], root, root.name)
1676
- end
1677
-
1678
- def validate_doc_struct_against_schema(struct, doc, prefix = nil)
1679
- doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
1680
- fdef = struct[c.name.to_sym]
1681
- if prefix then
1682
- xpath = "#{prefix}/#{c.name}"
1683
- else
1684
- xpath = c.name
1685
- end
1686
- if fdef
1687
- if fdef[:type] == :structure then
1688
- return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
1689
- end
1690
- else
1691
- if prefix then
1692
- xpath = "#{prefix}/#{c.name}"
1693
- else
1694
- xpath = c.name
1695
- end
1696
- raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
1697
- end
1698
- end
1699
- end
1700
-
1701
- def validate_schema_against_doc_struct(struct, doc, prefix = nil)
1702
- struct.each do |key, value|
1703
- next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1704
- if prefix then
1705
- xpath = "#{prefix}/#{key}"
1706
- else
1707
- xpath = key
1708
- end
1709
- if value[:type] == :structure then
1710
- node = doc.at_xpath(key.to_s)
1711
- if node then
1712
- validate_schema_against_doc_struct(value[:ref], node, xpath)
1713
- else
1714
- if value[:optional] == false then
1715
- raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
1716
- end
1717
- end
1718
- elsif value[:type] == :field then
1719
- if value[:optional] == false then
1720
- unless doc.at_xpath(key.to_s) then
1721
- raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
1722
- end
1723
- end
1724
- elsif value[:type] == :list then
1725
- if value[:optional] == false then
1726
- unless doc.at_xpath(key.to_s) then
1727
- raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
1728
- end
1729
- end
1730
- list = doc.at_xpath(key.to_s)
1731
- if list then
1732
- list.children.to_a.each do |c|
1733
- next unless c.is_a?(Nokogiri::XML::Element)
1734
- unless c.name == value[:xpath_element] then
1735
- raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
1736
- end
1737
- end
1738
- end
1739
- else
1740
- raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
1741
- end
1742
- end
1743
- end
1744
-
1745
- def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
1746
- options = hash[:options]
1747
- config = hash[:config]
1748
-
1749
- solver.to_solver(progress, "; --- Document: ---")
1750
- translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
1751
- end
1752
-
1753
- def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
1754
- options = hash[:options]
1755
- config = hash[:config]
1756
-
1757
- struct.each do |key, value|
1758
- next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1759
- if prefix then
1760
- xpath = "#{prefix}/#{key.to_s}"
1761
- else
1762
- xpath = key.to_s
1763
- end
1764
- if value[:type] == :structure then
1765
- node = doc.at_xpath(xpath)
1766
- if node then
1767
- info = {
1768
- :assertion_type => :doc_structure_exists,
1769
- :xpath => xpath,
1770
- }
1771
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1772
- solver.associateAssertionIDWith(assertionID, info)
1773
- solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} true) :named #{assertionID}))")
1774
-
1775
- translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
1776
- else
1777
- info = {
1778
- :assertion_type => :doc_structure_not_exists,
1779
- :xpath => xpath,
1780
- }
1781
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1782
- solver.associateAssertionIDWith(assertionID, info)
1783
- solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
1784
- end
1785
- elsif value[:type] == :field then
1786
- node = doc.at_xpath(xpath)
1787
- if node then
1788
- fvalue = node.text
1789
- info = {
1790
- :assertion_type => :doc_field_filled,
1791
- :xpath => xpath,
1792
- :value => fvalue
1793
- }
1794
- fvalue_str = value_of_type(fvalue, value[:datatype])
1795
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1796
- solver.associateAssertionIDWith(assertionID, info)
1797
- solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
1798
- else
1799
- info = {
1800
- :assertion_type => :doc_field_empty,
1801
- :xpath => xpath,
1802
- }
1803
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1804
- solver.associateAssertionIDWith(assertionID, info)
1805
- solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1806
- end
1807
- elsif value[:type] == :list then
1808
- node = doc.at_xpath(xpath)
1809
- if node then
1810
- fvalues = []
1811
- node.children.to_a.each do |c|
1812
- next unless c.is_a?(Nokogiri::XML::Element)
1813
- fvalues << c.text
1814
- end
1815
- info = {
1816
- :assertion_type => :doc_list_filled,
1817
- :xpath => xpath,
1818
- :values => fvalues
1819
- }
1820
- fvalue_str = value_of_type(fvalues, value[:datatype])
1821
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1822
- solver.associateAssertionIDWith(assertionID, info)
1823
- clauses = []
1824
- fvalues.each_with_index do |fvalue, index|
1825
- clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
1826
- end
1827
- solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
1828
- else
1829
- info = {
1830
- :assertion_type => :doc_field_empty,
1831
- :xpath => xpath,
1832
- }
1833
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1834
- solver.associateAssertionIDWith(assertionID, info)
1835
- solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1836
- end
1837
- else
1838
- raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
1839
- end
1840
- end
1841
- end
1842
-
1843
- def validate_doc(progress, struct, doc, options, config)
1844
- raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
1845
- options[:solverLog] = "validation.smt2"
1846
-
1847
- solverFactory = options[:solverFactory]
1848
- result = solverFactory.query(options) do |solver|
1849
- set_standard_options(progress, solver)
1850
- translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
1851
- translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
1852
- solver.to_solver(progress, "")
1853
- end
1854
- unless result == true then
1855
- raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
1856
- end
1857
-
1858
- return true
1859
- end
1860
-
1861
- def generate_unused_logfile_name()
1862
- number = 0
1863
- result = "logs/#{number}.log"
1864
- while File.exist?(result) do
1865
- number += 1
1866
- result = "logs/#{number}.log"
1867
- end
1868
- result
1869
- end
1870
-
1871
- def generate(message, argv, message_config_filename = nil)
1872
- if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
1873
- puts USAGE
1874
- exit(0)
1875
- end
1876
-
1877
- options = { :negated_validation_rules => [], :date_now => (Date.today - Time.at(0).to_date).to_i, :timestamp_now => Time.now().to_i }
1878
-
1879
- opt = argv.shift
1880
- while opt do
1881
- if opt == "-negate" then
1882
- rule = argv.shift
1883
- options[:negated_validation_rules] << rule
1884
- elsif opt == "-list-keys" then
1885
- options[:list_keys] = true
1886
- elsif opt == "-list" then
1887
- options[:list] = argv.shift
1888
- elsif opt == "-list-validation-rules" then
1889
- options[:list_validation_rules] = true
1890
- elsif opt == "-generate-docs-from" then
1891
- options[:combination_source] = argv.shift
1892
- elsif opt == "-skip" then
1893
- options[:skip] = Integer(argv.shift)
1894
- elsif opt == "-generate-docs-for-key" then
1895
- options[:docs_for_key] = argv.shift
1896
- elsif opt == "-count-docs-for-key" then
1897
- options[:count_docs_for_key] = argv.shift
1898
- elsif opt == "-continue" then
1899
- options[:continue] = true
1900
- elsif opt == "-max-num-docs" then
1901
- options[:max_num_docs] = Integer(argv.shift)
1902
- puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
1903
- elsif opt == "-validate" then
1904
- if options.has_key?(:validate_files) then
1905
- options[:validate_files] << argv.shift
1906
- else
1907
- options[:validate_files] = [argv.shift]
1908
- end
1909
- else
1910
- puts "FATAL: unknown option #{opt.inspect}."
1911
- puts USAGE
1912
- raise FatalError.new("")
1913
- end
1914
- opt = argv.shift
1915
- end
1916
-
1917
- ProgressBar.logging(1, generate_unused_logfile_name()) do |progress|
1918
- unless options[:negated_validation_rules].empty? then
1919
- progress.print_line "negating rules: #{options[:negated_validation_rules].join(", ")}"
1920
-
1921
- rules = collect_validation_rule_names(message)
1922
- unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
1923
- unless unknown_negated_rules.empty? then
1924
- throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
1925
- end
1926
-
1927
- end
1928
-
1929
- if File.exist?(CONFIG_FILE) then
1930
- config = YAML.load_file(CONFIG_FILE)
1931
- else
1932
- config = DEFAULT_CONFIG
1933
- File.open(CONFIG_FILE, "w") do |f|
1934
- f.puts(config.to_yaml)
1935
- end
1936
- progress.print_line "No config file found. Default config written to #{CONFIG_FILE}"
1937
- end
1938
- if message_config_filename then
1939
- message_config = YAML.load_file(message_config_filename)
1940
- config = config.merge(message_config)
1941
- end
1942
-
1943
- introduce_parent_links(message)
1944
- z3path = config[:z3path] || DEFAULT_Z3_PATH
1945
- options[:solverFactory] = SolverFactory.new(progress, z3path)
1946
-
1947
- if options[:validate_files] then
1948
- progress.print_line "using #{options[:date_now]} as [Date.now]"
1949
- progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
1950
- options[:validate_files].each do |filename|
1951
- progress.print_line "validating file #{filename}"
1952
- doc = Nokogiri::XML(File.read(filename))
1953
- validate_doc_against_schema(message, doc)
1954
- validate_doc(progress, message, doc, options, config)
1955
- end
1956
- elsif options[:count_docs_for_key] then
1957
- progress.print_line "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
1958
- elsif options[:docs_for_key] then
1959
- progress.print_line "using #{options[:date_now]} as [Date.now]"
1960
- progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
1961
- validate_model(message)
1962
- CoreEnumerationAlgo.new().generate_docs_for_key(progress, message, options[:docs_for_key], OUTPUT_DIR, config, options)
1963
- elsif options[:list_keys] then
1964
- list_keys(message)
1965
- elsif options[:list] then
1966
- list_fields_and_structures(message, options[:list])
1967
- elsif options[:list_validation_rules] then
1968
- list_validation_rules(message)
1969
- else
1970
- progress.print_line "using #{options[:date_now]} as [Date.now]"
1971
- progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
1972
- validate_model(message)
1973
- generate_doc(progress, message, OUTPUT_FILE, config, options)
1974
- end
1975
- end
1976
- end
1
+ #!/usr/bin/ruby
2
+
3
+ require 'date'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'yaml'
7
+ require 'time'
8
+
9
+ require 'nokogiri'
10
+
11
+ require_relative 'solver-lib'
12
+ require_relative 'progress'
13
+ require_relative 'regexp-to-smtlib'
14
+
15
+
16
+ CHUNK_SIZE = 10000
17
+ DEFAULT_Z3_PATH = "z3"
18
+
19
+ CONFIG_FILE = "config.yml"
20
+ OUTPUT_FILE = "result.xml"
21
+ CONTINUATION_FILE = "continuation.log"
22
+ COMBINATION_LOG = "combinations.log"
23
+ VARS_LOG = "vars.log"
24
+
25
+ OUTPUT_DIR = "result"
26
+ LISTS_DIR = "lists"
27
+ QUERY_DIR = "queries"
28
+ PARAMETERIZED_QUERY_DIR = "parameterized_queries"
29
+ XML_TEMPLATE = ""
30
+
31
+ DEFAULT_CONFIG = {
32
+ :z3path => "z3",
33
+ :generation_system_mode => "Production",
34
+ :generation_system => "Production"
35
+ }
36
+
37
+ USAGE = <<EOF
38
+ usage:
39
+
40
+ GENERATING A SINGLE MESSAGE:
41
+
42
+ ruby #{$PROGRAM_NAME} -list-validation-rules
43
+ - outputs a list of all validation rule names that have been specified in #{$PROGRAM_NAME}.
44
+
45
+ ruby #{$PROGRAM_NAME} <options>
46
+ - generates a message of the given type, which
47
+ - is valid XML
48
+ - adheres to the schema, and
49
+ - satisfies all validation rules specified for messages of this type.
50
+ where <options> is a combination of
51
+ -negate <validation_rule> - negates the named validation rule. This causes the resulting message to still satisfy all other validation rules, but to violate this one.
52
+ NOTE: the result can be found in #{OUTPUT_FILE}
53
+
54
+ GENERATING MESSAGES FOR ALL COMBINATIONS:
55
+
56
+ ruby #{$PROGRAM_NAME} -list-keys
57
+ - outputs a list of all keys that have been used in #{$PROGRAM_NAME} to mark fields or structures.
58
+
59
+ ruby #{$PROGRAM_NAME} -list <key>
60
+ - outputs a list of all fields and structures that have been marked with <key> in #{$PROGRAM_NAME}.
61
+
62
+ ruby #{$PROGRAM_NAME} -count-docs-for-key <key>
63
+ - calculates the number of combinations of all fields and structures marked with <key>
64
+
65
+ ruby #{$PROGRAM_NAME} -generate-docs-for-key <key> <options>
66
+ - generates all valid messages of the given type that can be derived from combinations of all the fields and structures marked with <key>.
67
+ where <options> is a combination of
68
+ -continue - continues a previously interrupted generation process. Must use the same <key> the the interrupted generation.ARGF
69
+ -max-num-docs <limit> - stops generation process after generating <limit> documents.
70
+ NOTE: enumerating large combination-spaces may take a long time.
71
+ - Use -count-docs-for-key to measure the size of the combination-space beforehand.
72
+ - Also: you can interrupt the generation process at any time using Ctrl+C. The intermediate result is stored in the directory #{OUTPUT_DIR} and the generation process can
73
+ be restarted later by adding -continue to the command line.
74
+ NOTE: do not expect the number of generated documents to equal the number of combinations as combinations may not have any valid message instances (for instance when they contradict the validation rules).
75
+ NOTE: the result can be found in the directory #{OUTPUT_DIR}
76
+
77
+ VALIDATING THE MODEL:
78
+
79
+ ruby #{$PROGRAM_NAME} -validate <file.xml>
80
+ - reads in the given XML file and validates it against the model - that is, it checks whether it conforms to the specified XML schema and to the validation rules.
81
+ EOF
82
+
83
+ class FatalError < RuntimeError
84
+ end
85
+
86
+ class NormalProgramTermination < RuntimeError
87
+ end
88
+
89
+ def raw_field(xpath)
90
+ path_str = "base"
91
+ path_str = xpath.gsub("/", "-") if xpath
92
+ "field-#{path_str}"
93
+ end
94
+
95
+ def filled_var_for_field(xpath)
96
+ "#{raw_field(xpath)}-filled"
97
+ end
98
+
99
+ def value_var_for_field(xpath)
100
+ "#{raw_field(xpath)}-value"
101
+ end
102
+
103
+ def exists_var_for_structure(xpath, index = nil)
104
+ if index then
105
+ struct_str = "base"
106
+ struct_str = xpath.gsub("/", "-") if xpath
107
+ struct_str = "#{struct_str}-#{index}" if index
108
+ "struct-#{struct_str}-exists"
109
+ else
110
+ struct_str = "base"
111
+ struct_str = xpath.gsub("/", "-") if xpath
112
+ "struct-#{struct_str}-exists"
113
+ end
114
+ end
115
+
116
+ def size_var_for_list(xpath)
117
+ "#{raw_field(xpath)}-size"
118
+ end
119
+
120
+ def value_var_for_list(xpath, index)
121
+ "#{raw_field(xpath)}-index-#{index}-value"
122
+ end
123
+
124
+ def translate_datatype_to_SMTLIB(datatype)
125
+ case datatype
126
+ when :string
127
+ return "String"
128
+ when :int
129
+ return "Int"
130
+ when :bool
131
+ return "Bool"
132
+ when :date
133
+ return "Int"
134
+ when :timestamp
135
+ return "Int"
136
+ when :enum
137
+ return "String"
138
+ end
139
+ end
140
+
141
+ def translate_value_to_SMTLIB(value)
142
+ if value.is_a?(Integer) then
143
+ return value.to_s
144
+ elsif value.is_a?(String) then
145
+ return value.inspect
146
+ elsif value.is_a?(Date) then
147
+ return (value - (Time.at(0).to_date)).to_i # days since 1970.01.01
148
+ elsif value.is_a?(Time) then
149
+ return value.to_i
150
+ else
151
+ raise RuntimeError.new("cannot translate value to SMTLIB: #{value.inspect}")
152
+ end
153
+ end
154
+
155
+ def min_constraint_for_expr(expr, lower_bound)
156
+ "(>= #{expr} #{lower_bound})"
157
+ end
158
+
159
+ def max_constraint_for_expr(expr, upper_bound)
160
+ "(<= #{expr} #{upper_bound})"
161
+ end
162
+
163
+ def regex_constraint_for_expr(expr, regex)
164
+ "(str.in.re #{expr} #{Regexp_to_SMTLIB.translate_regexp_to_SMTLIB(regex)})"
165
+ end
166
+
167
+ def maxLength_contraint_for_expr(expr, maxLength)
168
+ "(<= (str.len #{expr}) #{maxLength})"
169
+ end
170
+
171
+ def oneOf_contraint_for_expr(expr, values)
172
+ list = values.map do |val|
173
+ "(= #{expr} \"#{val}\")"
174
+ end
175
+ "(or #{list.join(" ")})"
176
+ end
177
+
178
+ def enum_constraint_for_expr(expr, values)
179
+ "(or #{values.map{|x| "(= #{expr} #{translate_value_to_SMTLIB(x)})"}.join(" ")})"
180
+ end
181
+
182
+ def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef, xpath, struct_var)
183
+ varname = raw_field(xpath)
184
+ if fielddef[:optional] == nil || fielddef[:optional] == false then
185
+ # field is required
186
+ assertionID = "required-#{varname}"
187
+ info = {
188
+ :assertion_type => :constraint,
189
+ :xpath => xpath,
190
+ :constraint_type => :required
191
+ }
192
+ solver.associateAssertionIDWith(assertionID, info)
193
+ solver.to_solver(progress, "(assert (! (=> #{struct_var} #{varname}-filled) :named #{assertionID}))")
194
+ end
195
+ datatype = fielddef[:datatype]
196
+ case datatype
197
+ when :int
198
+ if fielddef[:min] then
199
+ assertionID = "min-#{varname}"
200
+ info = {
201
+ :assertion_type => :constraint,
202
+ :xpath => xpath,
203
+ :datatype => :int,
204
+ :value => fielddef[:min],
205
+ :constraint_type => :min
206
+ }
207
+ solver.associateAssertionIDWith(assertionID, info)
208
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{min_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named #{assertionID}))")
209
+ end
210
+ if fielddef[:max] then
211
+ assertionID = "max-#{varname}"
212
+ info = {
213
+ :assertion_type => :constraint,
214
+ :xpath => xpath,
215
+ :datatype => :int,
216
+ :value => fielddef[:max],
217
+ :constraint_type => :max
218
+ }
219
+ solver.associateAssertionIDWith(assertionID, info)
220
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{max_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named #{assertionID}))")
221
+ end
222
+ when :string
223
+ if fielddef[:regex] then
224
+ assertionID = "regex-#{varname}"
225
+ info = {
226
+ :assertion_type => :constraint,
227
+ :xpath => xpath,
228
+ :datatype => :string,
229
+ :pattern => fielddef[:regex],
230
+ :constraint_type => :regex
231
+ }
232
+ solver.associateAssertionIDWith(assertionID, info)
233
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{regex_constraint_for_expr("#{varname}-value", fielddef[:regex])}) :named #{assertionID}))")
234
+ end
235
+ # TODO: minLength?
236
+ if fielddef[:maxLength] then
237
+ assertionID = "maxLength-#{varname}"
238
+ info = {
239
+ :assertion_type => :constraint,
240
+ :xpath => xpath,
241
+ :datatype => :string,
242
+ :value => fielddef[:maxLength],
243
+ :constraint_type => :maxLength
244
+ }
245
+ solver.associateAssertionIDWith(assertionID, info)
246
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{maxLength_contraint_for_expr("#{varname}-value", fielddef[:maxLength])}) :named #{assertionID}))")
247
+ end
248
+ if fielddef[:from_list] then
249
+ assertionID = "oneOf-#{varname}"
250
+ list_filename = "#{LISTS_DIR}/#{fielddef[:from_list]}"
251
+ info = {
252
+ :assertion_type => :constraint,
253
+ :xpath => xpath,
254
+ :datatype => :string,
255
+ :filename => list_filename,
256
+ :constraint_type => :from_list
257
+ }
258
+ unless File.exist?(list_filename)
259
+ raise RuntimeError.new("could not find list file #{list_filename}.")
260
+ end
261
+ values = File.read(list_filename).split("\n").map{|v| v.chomp()}
262
+ solver.associateAssertionIDWith(assertionID, info)
263
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{oneOf_contraint_for_expr("#{varname}-value", values)}) :named #{assertionID}))")
264
+ end
265
+ when :enum
266
+ assertionID = "enum-#{varname}"
267
+ info = {
268
+ :assertion_type => :constraint,
269
+ :xpath => xpath,
270
+ :datatype => :enum,
271
+ :values => fielddef[:values],
272
+ :constraint_type => :values
273
+ }
274
+ solver.associateAssertionIDWith(assertionID, info)
275
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{enum_constraint_for_expr("#{varname}-value", fielddef[:values])}) :named #{assertionID}))")
276
+ end
277
+ end
278
+
279
+ def translate_list_def_to_SMTLIB_constraints(progress, solver, fielddef, varname)
280
+ datatype = fielddef[:datatype]
281
+ case datatype
282
+ when :int
283
+ if fielddef[:min] then
284
+ fielddef[:model_maxLength].times do |i|
285
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{min_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named list-min-#{varname}-list-index-#{i}))")
286
+ end
287
+ end
288
+ if fielddef[:max] then
289
+ fielddef[:model_maxLength].times do |i|
290
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{max_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named list-max-#{varname}-list-index-#{i}))")
291
+ end
292
+ end
293
+ when :string
294
+ if fielddef[:regex] then
295
+ fielddef[:model_maxLength].times do |i|
296
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{regex_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:regex])}) :named list-regex-#{varname}-index-#{i}))")
297
+ end
298
+ end
299
+ # TODO: minLength?
300
+ if fielddef[:maxLength] then
301
+ fielddef[:model_maxLength].times do |i|
302
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{maxLength_contraint_for_expr("#{varname}-index-#{i}-value", fielddef[:maxLength])}) :named list-maxLength-#{varname}-index-#{i}))")
303
+ end
304
+ end
305
+ when :enum
306
+ fielddef[:model_maxLength].times do |i|
307
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{enum_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:values])}) :named list-enum-#{varname}-index-#{i}))")
308
+ end
309
+ end
310
+ end
311
+
312
+ def default_value_for_fielddef(fielddef)
313
+ datatype = fielddef[:datatype]
314
+ case datatype
315
+ when :int
316
+ return "0"
317
+ when :string
318
+ return "".inspect
319
+ when :date
320
+ return "0"
321
+ when :timestamp
322
+ return "0"
323
+ when :enum
324
+ return "".inspect
325
+ else
326
+ raise RuntimeError.new("unknown datatype: #{datatype.inspect}")
327
+ end
328
+ end
329
+
330
+ def translate_structure_to_SMTLIB(progress, solver, struct, hash = {})
331
+ prefix = hash[:prefix]
332
+ config = hash[:config]
333
+ options = hash[:options]
334
+ translate_substructure_to_SMTLIB(progress, solver, struct, hash)
335
+ translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
336
+ if options[:add_SMTLIB] then
337
+ solver.to_solver(progress, "; --- additional SMTLIB Code: ---")
338
+ solver.to_solver(progress, options[:add_SMTLIB])
339
+ end
340
+ end
341
+
342
+ def translate_substructure_to_SMTLIB(progress, solver, struct, hash = {})
343
+ prefix = hash[:prefix]
344
+ struct_varname = hash[:struct_varname]
345
+ config = hash[:config]
346
+ options = hash[:options]
347
+ solver.to_solver(progress, "; --- Structure: ---")
348
+ struct.each do |field, definition|
349
+ if field == :additional_smtlib then
350
+ solver.to_solver(progress, "; additional SMTLIB of structure #{prefix}")
351
+ solver.to_solver(progress, translate_validation_rule_to_SMTLIB(definition, struct, prefix, config, options, "<additional SMTLIB of structure #{prefix}>"))
352
+ elsif field == :validation_rules || field == :predicates || field == :parent_link || field == :struct_xpath_prefix
353
+ next
354
+ else
355
+ solver.to_solver(progress, "; #{field.to_s}")
356
+ if definition[:type] == :field then
357
+ display_prefix = ""
358
+ display_prefix = "#{prefix}/" if prefix
359
+ xpath = field.to_s
360
+ xpath = "#{prefix}/#{xpath}" if prefix
361
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
362
+ solver.declare_const(progress, value_var_for_field(xpath), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :datatype => definition[:datatype]})
363
+ solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{value_var_for_field(xpath)} #{default_value_for_fielddef(definition)})) :named empty-field-has-no-value-for-#{raw_field(xpath)}))")
364
+ if (struct_varname)
365
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
366
+ end
367
+ translate_field_def_to_SMTLIB_constraints(progress, solver, definition, xpath, struct_varname)
368
+ elsif definition[:type] == :list then
369
+ display_prefix = ""
370
+ display_prefix = "#{prefix}/" if prefix
371
+ xpath = field.to_s
372
+ xpath = "#{prefix}/#{xpath}" if prefix
373
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
374
+ solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
375
+ solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-list-field-has-zero-size-#{raw_field(xpath)}))")
376
+ solver.to_solver(progress, "(assert (! (=> (= #{size_var_for_list(xpath)} 0) (not #{filled_var_for_field(xpath)})) :named list-has-zero-size-implies-empty-list-#{raw_field(xpath)}))")
377
+ solver.to_solver(progress, "(assert (! (=> #{filled_var_for_field(xpath)} (> #{size_var_for_list(xpath)} 0)) :named filled-list-field-has-positive-size-#{raw_field(xpath)}))")
378
+ solver.to_solver(progress, "(assert (! (=> (> #{size_var_for_list(xpath)} 0) #{filled_var_for_field(xpath)}) :named list-has-positive-size-implies-filled-list-#{raw_field(xpath)}))")
379
+ definition[:model_maxLength].times do |i|
380
+ solver.declare_const(progress, value_var_for_list(xpath, i), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :xpath_element => definition[:xpath_element], :index => i, :datatype => definition[:datatype]})
381
+ solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (= #{value_var_for_list(xpath, i)} #{default_value_for_fielddef(definition)})) :named empty-list-field-has-no-value-for-#{raw_field(xpath)}-index-#{i}))")
382
+ end
383
+ if (struct_varname)
384
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (= #{size_var_for_list(xpath)} 0)) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
385
+ end
386
+ translate_list_def_to_SMTLIB_constraints(progress, solver, definition, raw_field(xpath))
387
+ elsif definition[:type] == :objlist then
388
+ xpath = field.to_s
389
+ xpath = "#{prefix}/#{xpath}" if prefix
390
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
391
+ solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
392
+ if (struct_varname)
393
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-objlist-#{raw_field(xpath)}))")
394
+ end
395
+ if definition[:optional] == nil || definition[:optional] == false then
396
+ solver.to_solver(progress, "(assert (! (=> #{struct_varname} #{filled_var_for_field(xpath)}) :named required-objlist-#{raw_field(xpath)}))")
397
+ end
398
+ solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-objlist-has-zero-size-#{raw_field(xpath)}))")
399
+ solver.to_solver(progress, "(assert (! (=> (= #{size_var_for_list(xpath)} 0) (not #{filled_var_for_field(xpath)})) :named objlist-has-zero-size-implies-empty-objlist-#{raw_field(xpath)}))")
400
+ minSize = 0
401
+ solver.to_solver(progress, "(assert (! (=> #{filled_var_for_field(xpath)} (and (> #{size_var_for_list(xpath)} #{minSize}) (< #{size_var_for_list(xpath)} #{definition[:model_maxLength]}))) :named filled-objlist-has-positive-size-#{raw_field(xpath)}))")
402
+ solver.to_solver(progress, "(assert (! (=> (> #{size_var_for_list(xpath)} 0) #{filled_var_for_field(xpath)}) :named objlist-has-positive-size-implies-filled-objlist-#{raw_field(xpath)}))")
403
+ definition[:model_maxLength].times do |i|
404
+ ext_xpath = "#{xpath}/#{i}"
405
+ solver.declare_const(progress, exists_var_for_structure(xpath, i), "Bool", {:xpath => ext_xpath})
406
+ solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (not #{exists_var_for_structure(xpath, i)})) :named objlist-struct-exist1-for-#{raw_field(xpath)}-index-#{i}))")
407
+ solver.to_solver(progress, "(assert (! (=> (> #{size_var_for_list(xpath)} #{i}) #{exists_var_for_structure(xpath, i)}) :named objlist-struct-exist2-for-#{raw_field(xpath)}-index-#{i}))")
408
+ translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => ext_xpath, :struct_varname => exists_var_for_structure(xpath, i), :options => options, :config => config)
409
+ end
410
+ elsif definition[:type] == :structure then
411
+ xpath = field.to_s
412
+ xpath = "#{prefix}/#{xpath}" if prefix
413
+ solver.declare_const(progress, exists_var_for_structure(xpath), "Bool", {:xpath => xpath})
414
+ if (struct_varname)
415
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{exists_var_for_structure(xpath)})) :named empty-struct-has-no-substructure-#{raw_field(xpath)}))")
416
+ end
417
+ translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => xpath, :struct_varname => exists_var_for_structure(xpath), :options => options, :config => config)
418
+ else
419
+ raise RuntimeError.new("Model Error: encountered unknown :type #{definition[:type].inspect} for element #{field.inspect}")
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ def resolve_field_path(path, relative_to_struct, prefix)
426
+ current = relative_to_struct
427
+ result = nil
428
+ path.split("/").each do |segment|
429
+ unless current then
430
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
431
+ end
432
+ field_def = current[segment.to_sym]
433
+ unless field_def then
434
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
435
+ end
436
+ if field_def[:type] == :structure then
437
+ unless field_def[:ref] then
438
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
439
+ end
440
+ current = field_def[:ref]
441
+ if prefix then
442
+ prefix = "#{prefix}/#{segment}"
443
+ else
444
+ prefix = segment
445
+ end
446
+ elsif field_def[:type] == :field then
447
+ current = nil
448
+ result = segment
449
+ if prefix
450
+ result = "#{prefix}/#{result}"
451
+ end
452
+ elsif field_def[:type] == :list then
453
+ current = nil
454
+ result = segment
455
+ if prefix
456
+ result = "#{prefix}/#{result}"
457
+ end
458
+ elsif field_def[:type] == :objlist then
459
+ current = nil
460
+ result = segment
461
+ if prefix
462
+ result = "#{prefix}/#{result}"
463
+ end
464
+ end
465
+ end
466
+ return result
467
+ end
468
+
469
+ def resolve_field_def(path, relative_to_struct, prefix)
470
+ current = relative_to_struct
471
+ result = nil
472
+ path.split("/").each do |segment|
473
+ unless current then
474
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
475
+ end
476
+ field_def = current[segment.to_sym]
477
+ unless field_def then
478
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
479
+ end
480
+ if field_def[:type] == :structure then
481
+ unless field_def[:ref] then
482
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
483
+ end
484
+ current = field_def[:ref]
485
+ if prefix then
486
+ prefix = "#{prefix}/#{segment}"
487
+ else
488
+ prefix = segment
489
+ end
490
+ elsif field_def[:type] == :field then
491
+ current = nil
492
+ result = field_def
493
+ elsif field_def[:type] == :list then
494
+ current = nil
495
+ result = field_def
496
+ end
497
+ end
498
+ return result
499
+ end
500
+
501
+ def resolve_struct_path(path, relative_to_struct, prefix)
502
+ current = relative_to_struct
503
+ result = nil
504
+ path.split("/").each do |segment|
505
+ unless current then
506
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
507
+ end
508
+ if segment == "." then
509
+ result = current[:struct_xpath_prefix]
510
+ elsif segment == ".." then
511
+ current = current[:parent_link]
512
+ if prefix then
513
+ prefix_segs = prefix.split("/")
514
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
515
+ else
516
+ prefix = current[:struct_xpath_prefix]
517
+ end
518
+ else
519
+ field_def = current[segment.to_sym]
520
+ unless field_def then
521
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
522
+ end
523
+ if field_def[:type] == :structure then
524
+ current = field_def[:ref]
525
+ if prefix then
526
+ prefix = "#{prefix}/#{segment}"
527
+ else
528
+ prefix = segment
529
+ end
530
+ result = prefix
531
+ end
532
+ end
533
+ end
534
+ return result
535
+ end
536
+
537
+ def resolve_struct_def(path, relative_to_struct, prefix, context_desc = "")
538
+ current = relative_to_struct
539
+ result = nil
540
+ path.split("/").each do |segment|
541
+ unless current then
542
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
543
+ end
544
+ if segment == "." then
545
+ result = current
546
+ elsif segment == ".." then
547
+ current = current[:parent_link]
548
+ if prefix then
549
+ prefix_segs = prefix.split("/")
550
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
551
+ else
552
+ prefix = current[:struct_xpath_prefix]
553
+ end
554
+ else
555
+ field_def = current[segment.to_sym]
556
+ unless field_def then
557
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
558
+ end
559
+ if field_def[:type] == :structure then
560
+ current = field_def[:ref]
561
+ if prefix then
562
+ prefix = "#{prefix}/#{segment}"
563
+ else
564
+ prefix = segment
565
+ end
566
+ result = current
567
+ end
568
+ end
569
+ end
570
+ return result
571
+ end
572
+
573
+ def translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
574
+ solver.to_solver(progress, "; --- Validation Rules ---")
575
+ translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
576
+ struct.each_pair do |key, value|
577
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
578
+ if value[:type] == :structure then
579
+ if prefix then
580
+ new_prefix = "#{prefix.to_s}/#{key}"
581
+ else
582
+ new_prefix = key.to_s
583
+ end
584
+ translate_validation_rules_to_SMTLIB(progress, solver, value[:ref], new_prefix, config, options)
585
+ end
586
+ end
587
+ end
588
+
589
+ def translate_validation_rule_to_SMTLIB(rule, context_struct, prefix, config, options, rule_name)
590
+ date_now = options[:date_now]
591
+ timestamp_now = options[:timestamp_now]
592
+ return rule.gsub(/FILLED\[[^\]]*\]/) do |m|
593
+ unless m =~ /FILLED\[([^\]]*)\]/ then
594
+ raise RuntimeError.new("could not parse FILLED[...]: #{m.inspect} in validation rule #{rule_name}")
595
+ end
596
+ field = resolve_field_path($1, context_struct, prefix)
597
+ filled_var_for_field(field)
598
+ end
599
+ .gsub(/VALUE\[[^\]]*\]/) do |m|
600
+ unless m =~ /VALUE\[([^\]]*)\]/ then
601
+ raise RuntimeError.new("could not parse VALUE[...]: #{m.inspect} in validation rule #{rule_name}")
602
+ end
603
+ field = resolve_field_path($1, context_struct, prefix)
604
+ value_var_for_field(field)
605
+ end
606
+ .gsub(/EXIST\[[^\]]*\]/) do |m|
607
+ unless m =~ /EXIST\[([^\]]*)\]/ then
608
+ raise RuntimeError.new("could not parse EXIST[...]: #{m.inspect} in validation rule #{rule_name}")
609
+ end
610
+ s = resolve_struct_path($1, context_struct, prefix)
611
+ exists_var_for_structure(s)
612
+ end
613
+ .gsub(/SIZE\[[^\]]*\]/) do |m|
614
+ unless m =~ /SIZE\[([^\]]*)\]/ then
615
+ raise RuntimeError.new("could not parse SIZE[...]: #{m.inspect} in validation rule #{rule_name}")
616
+ end
617
+ l = resolve_field_path($1, context_struct, prefix)
618
+ size_var_for_list(l)
619
+ end
620
+ .gsub(/LIST_CONTAINS\[[^\]]*,[^\]]*\]/) do |m|
621
+ unless m =~ /LIST_CONTAINS\[([^\]]*),([^\]]*)\]/ then
622
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
623
+ end
624
+ l = resolve_field_path($1, context_struct, prefix)
625
+ l_def = resolve_field_def($1, context_struct, prefix)
626
+ value = $2
627
+ terms = []
628
+ l_def[:model_maxLength].times do |i|
629
+ terms << "(and (< #{i} #{size_var_for_list(l)}) (= #{value_var_for_list(l, i)} #{value}))"
630
+ end
631
+ "(or #{terms.join(" ")})"
632
+ end
633
+ .gsub(/LIST_VALUE\[[^\]]*,[^\]]*\]/) do |m|
634
+ unless m =~ /LIST_VALUE\[([^\]]*),([^\]]*)\]/ then
635
+ raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
636
+ end
637
+ xpath = $1
638
+ index = Integer($2)
639
+ l = resolve_field_path(xpath, context_struct, prefix)
640
+ value_var_for_list(l, index)
641
+ end
642
+ .gsub(/LIST_VALUE_FIRST_ELEMENT\[[^\]]*\]/) do |m| # DEPRECATED! Please use LIST_VALUE[...] instead.
643
+ unless m =~ /LIST_VALUE_FIRST_ELEMENT\[([^\]]*)\]/ then
644
+ raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
645
+ end
646
+ xpath = $1
647
+ l = resolve_field_path(xpath, context_struct, prefix)
648
+ value_var_for_list(l, 0)
649
+ end
650
+ .gsub("[Date.now]", date_now.to_s)
651
+ .gsub("[Timestamp.now]", timestamp_now.to_s)
652
+ .gsub(/PREDICATE\[[^\]]*,[^\]]*\]/) do |m|
653
+ unless m =~ /PREDICATE\[([^\]]*),([^\]]*)\]/ then
654
+ raise RuntimeError.new("could not parse PREDICATE[...]: #{m.inspect} in validation rule #{rule_name}")
655
+ end
656
+ path = $1
657
+ key = $2.strip().to_sym
658
+ sdef = resolve_struct_def(path, context_struct, prefix, "in validation rule #{rule_name}")
659
+ unless sdef
660
+ raise RuntimeError.new("could not resolve struct path #{path.inspect} in validation rule #{rule_name}")
661
+ end
662
+ preds = sdef[:predicates]
663
+ mdk = preds[key]
664
+ if mdk then
665
+ if mdk[:smtlib] then
666
+ translate_validation_rule_to_SMTLIB(mdk[:smtlib], sdef, sdef[:struct_xpath_prefix], config, options, "#{rule_name}.predicate.#{key}[#{sdef[:struct_xpath_prefix]}]" )
667
+ else
668
+ raise RuntimeError.new("Predicate #{key} for structure #{sdef[:struct_xpath_prefix]} does not have an SMTLIB definition, but is referenced in validation rule #{rule_name}")
669
+ end
670
+ else
671
+ raise RuntimeError.new("No predicate #{key.inspect} defined for structure #{sdef[:struct_xpath_prefix]}, but one referenced in validation rule #{rule_name}")
672
+ end
673
+ end
674
+ .gsub(/CONFIG\[[^\]]*\]/) do |m|
675
+ unless m =~ /CONFIG\[([^\]]*)\]/ then
676
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
677
+ end
678
+ key = $1.to_sym
679
+ if config.has_key?(key) then
680
+ "\"#{config[key]}\""
681
+ else
682
+ raise RuntimeError.new("unknown CONFIG-key #{key.inspect} encountered in validation rule #{rule_name}" )
683
+ end
684
+ end
685
+ .gsub(/FIELD_IN_QUERY\[[^\]]*\]/) do |m|
686
+ unless m =~ /FIELD_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
687
+ raise RuntimeError.new("could not parse FIELD_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
688
+ end
689
+ xpath = $1
690
+ query_filename = $2
691
+ fpath = resolve_field_path(xpath, context_struct, prefix)
692
+ fdef = resolve_field_def(xpath, context_struct, prefix)
693
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
694
+ if fdef[:datatype] == :string then
695
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
696
+ "(= #{value_var_for_field(fpath)} \"#{val}\")"
697
+ end
698
+ elsif fdef[:datatype] == :int then
699
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
700
+ "(= #{value_var_for_field(fpath)} #{Integer(val)})"
701
+ end
702
+ else
703
+ raise RuntimeError.new("FIELD_IN_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string or int.")
704
+ end
705
+ "(or #{terms.join(" ")})"
706
+ end
707
+ .gsub(/FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[[^\]]*\]/) do |m|
708
+ unless m =~ /FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[([^\]]*),([^\]]*),([^\]]*)\]/ then
709
+ raise RuntimeError.new("could not parse FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[..,..,..]: #{m.inspect} in validation rule #{rule_name}")
710
+ end
711
+ field_xpath = $1
712
+ param_smtlib = $2
713
+ query_filename = $3
714
+ fpath = resolve_field_path(field_xpath.strip(), context_struct, prefix)
715
+ fdef = resolve_field_def(field_xpath.strip(), context_struct, prefix)
716
+ query_result = YAML.load_file("#{PARAMETERIZED_QUERY_DIR}/#{query_filename.strip()}")
717
+ terms = []
718
+ if fdef[:datatype] == :string then
719
+ terms << "(or #{query_result.keys.map { |key| "(= #{param_smtlib} \"#{key}\")" }.join(" ")})"
720
+ query_result.each_pair do |key, values|
721
+ terms << "(=> (= #{param_smtlib} \"#{key}\") (or #{values.map{ |v| "(= #{value_var_for_field(fpath)} \"#{v}\")" }.join(" ")}))"
722
+ end
723
+ else
724
+ raise RuntimeError.new("FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string.")
725
+ end
726
+ "(and #{terms.join(" ")})"
727
+ end
728
+ .gsub(/CONFIG_IN_QUERY\[[^\]]*\]/) do |m|
729
+ unless m =~ /CONFIG_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
730
+ raise RuntimeError.new("could not parse CONFIG_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
731
+ end
732
+ key = $1.to_sym
733
+ unless config.has_key?(key) then
734
+ raise RuntimeError.new("unknown CONFIG key #{key.inspect} encountered in validation rule #{rule_name}")
735
+ end
736
+ query_filename = $2
737
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
738
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
739
+ "(= \"#{config[key]}\" \"#{val}\")"
740
+ end
741
+ "(or #{terms.join(" ")})"
742
+ end
743
+ end
744
+
745
+ def translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
746
+ return unless struct[:validation_rules]
747
+
748
+ solver.to_solver(progress, "; --- Validation Rules: ---")
749
+ negated_rules = options[:negated_validation_rules] || []
750
+ solver.to_solver(progress, "; negated rules: #{negated_rules.empty? ? "<none>" : negated_rules.join(", ")}")
751
+
752
+ rules = struct[:validation_rules]
753
+ rules.each do |rule_name, rule|
754
+ rule = rule[:smtlib] # ignore the text representation
755
+ next unless rule # warning about this omission is the job of validate_model()
756
+ rule_SMTLIB = translate_validation_rule_to_SMTLIB(rule, struct, prefix, config, options, rule_name)
757
+ if negated_rules.include?(rule_name) then
758
+ rule_SMTLIB = "(not #{rule_SMTLIB})"
759
+ end
760
+ solver.to_solver(progress, "(assert (! #{rule_SMTLIB} :named validation_rule_#{rule_name}_instance_#{prefix.gsub("/", "_")}))")
761
+ end
762
+ end
763
+
764
+ def parse_field_value(fielddef, value)
765
+ value = value.gsub("\n", "")
766
+ datatype = fielddef[:datatype]
767
+ case datatype
768
+ when :int
769
+ return Integer(value)
770
+ when :string
771
+ return value
772
+ when :enum
773
+ return value
774
+ when :date
775
+ return Date.parse(value)
776
+ when :timestamp
777
+ return Time.new(value)
778
+ end
779
+ end
780
+
781
+ def solver_value_to_string(solver_value)
782
+ if solver_value =~ /\"(.*)\"/ then
783
+ return $1
784
+ else
785
+ return solver_value
786
+ end
787
+ end
788
+
789
+ def build_document_from_model(progress, struct, model)
790
+ doc = Nokogiri::XML(XML_TEMPLATE)
791
+ add_model_struct_to_doc(progress, struct, doc, model)
792
+ return doc
793
+ end
794
+
795
+ def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
796
+ struct.each do |key, value|
797
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
798
+ if prefix then
799
+ xpath = "#{prefix}/#{key}"
800
+ else
801
+ xpath = key.to_s
802
+ end
803
+ model_value = model[xpath]
804
+ if value[:type] == :field then
805
+ unless model_value then
806
+ raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
807
+ end
808
+ if model_value[:filled] == true || model_value[:filled] == "true" then
809
+ doc.add_child("<#{key}>#{solver_value_to_string(model_value[:value])}</#{key}>")
810
+ end
811
+ elsif value[:type] == :list then
812
+ node = doc.add_child("<#{key}></#{key}>")
813
+ if node.is_a?(Nokogiri::XML::NodeSet) then
814
+ node = node.first
815
+ end
816
+ xpath_element = value[:xpath_element]
817
+ if Integer(model_value[:size]) > 0 then
818
+ model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
819
+ node.add_child("<#{xpath_element}>#{solver_value_to_string(mvalue)}</#{xpath_element}>")
820
+ end
821
+ end
822
+ elsif value[:type] == :objlist then
823
+ node = doc.add_child("<#{key}></#{key}>")
824
+ if node.is_a?(Nokogiri::XML::NodeSet) then
825
+ node = node.first
826
+ end
827
+ xpath_element = value[:xpath_element]
828
+ if Integer(model_value[:size]) > 0 then
829
+ model_value[:size].times do |index|
830
+ cnode = node.add_child("<#{xpath_element}></#{xpath_element}>")
831
+ if cnode.is_a?(Nokogiri::XML::NodeSet) then
832
+ cnode = cnode.first
833
+ end
834
+ add_model_struct_to_doc(progress, value[:ref], cnode, model, "#{xpath}/#{index}")
835
+ end
836
+ end
837
+ elsif value[:type] == :structure then
838
+ unless value[:ref] then
839
+ raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
840
+ end
841
+ if model_value[:exists] == true || model_value[:exists] == "true" then
842
+ node = doc.add_child("<#{key}></#{key}>")
843
+ if node.is_a?(Nokogiri::XML::NodeSet) then
844
+ node = node.first
845
+ end
846
+ add_model_struct_to_doc(progress, value[:ref], node, model, xpath)
847
+ end
848
+ else
849
+ raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
850
+ end
851
+ end
852
+ end
853
+
854
+ def collect_validation_rule_names(struct)
855
+ result = []
856
+ struct.each do |key, value|
857
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
858
+ if key == :validation_rules
859
+ value.each do |key, value|
860
+ result << key
861
+ end
862
+ else
863
+ if value[:type] == :structure
864
+ result += collect_validation_rule_names(value[:ref])
865
+ end
866
+ end
867
+ end
868
+ return result
869
+ end
870
+
871
+ def set_standard_options(progress, solver)
872
+ solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
873
+ solver.to_solver(progress, "(set-option :smt.core.minimize true) ; ensure that unsat cores are minimal")
874
+ solver.to_solver(progress, "")
875
+ end
876
+
877
+ def generate_doc(progress, message, output_filename, config, options)
878
+ raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
879
+ log = options[:log] || nil
880
+ log.puts "generating doc #{output_filename}" if log
881
+ options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
882
+
883
+ solverFactory = options[:solverFactory]
884
+ model = solverFactory.query_model(options) do |solver|
885
+ set_standard_options(progress, solver)
886
+ log.puts "translating structure" if log
887
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
888
+ solver.to_solver(progress, "")
889
+ end
890
+
891
+ if model then
892
+ result_doc = build_document_from_model(progress, message, model)
893
+ dir = File.dirname(output_filename)
894
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
895
+ File.open(output_filename, "w") do |f|
896
+ f.puts result_doc.to_xml()
897
+ end
898
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
899
+ return true
900
+ else
901
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
902
+ return false
903
+ end
904
+ end
905
+
906
+ def z3ValueToRubyValue(value)
907
+ if value =~ /\"(.*)\"/ then # string
908
+ return $1
909
+ elsif value =~ /\d+/ then # int
910
+ return Integer(value)
911
+ elsif value == "true"
912
+ return true
913
+ elsif value == "false" then
914
+ return false
915
+ else
916
+ raise RuntimeError.new("enountered unknown type of value in z3 model: #{value.inspect}")
917
+ end
918
+ end
919
+
920
+ def extract_key_field_values_from_model(key_fields, model)
921
+ result = {}
922
+ key_fields.each do |xpath, fieldDesc|
923
+ fieldInfo = model[xpath]
924
+ if fieldDesc[:type] == :field then
925
+ if fieldInfo[:filled] then
926
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:value])
927
+ else
928
+ result[xpath] = nil
929
+ end
930
+ elsif fieldDesc[:type] == :struct || fieldDesc[:type] == :structure then
931
+ if fieldInfo.has_key?(:exists) then
932
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:exists])
933
+ else
934
+ result[xpath] = false
935
+ end
936
+ else
937
+ raise RuntimeError.new("encountered unknown :type in solver model: #{fieldDesc[:type].inspect}")
938
+ end
939
+ end
940
+ return result
941
+ end
942
+
943
+ def all_values_for_field(field)
944
+ unless field[:type] == :field then
945
+ raise RuntimeError.new("something that is not a field definition was passed to all_values_for_field(): #{field.inspect}")
946
+ end
947
+ unless field[:datatype] == :enum then
948
+ raise RuntimeError.new("all_values_for_field() is only applicable to fields of datatype enum! not #{field[:datatype].inspect}.")
949
+ end
950
+ values = field[:values].dup
951
+ if field[:optional] == true then
952
+ values << nil
953
+ end
954
+ return values
955
+ end
956
+
957
+ def calc_combinations_for_key_fields(key_fields)
958
+ count = 1
959
+ key_fields.each do |field|
960
+ if field[1][:type] == :field then
961
+ if field[1][:datatype] == :enum then
962
+ num_values = field[1][:values].size
963
+ num_values += 1 if field[1][:optional]
964
+ elsif field[1][:datatype] == :int then
965
+ if field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
966
+ if field[1][:optional] then
967
+ num_values = 2 + field[1][:max] - field[1][:min]
968
+ else
969
+ num_values = 1 + field[1][:max] - field[1][:min]
970
+ end
971
+ else
972
+ return "potentially infinite"
973
+ end
974
+ elsif field[1][:datatype] == :string then
975
+ return "potentially infinite"
976
+ end
977
+ elsif field[1][:type] == :structure then
978
+ num_values = 2
979
+ end
980
+ count *= num_values
981
+ end
982
+ return count
983
+ end
984
+
985
+ def calc_combinations(message, key)
986
+ key_fields = collect_key_fields(key, message)
987
+ if key_fields.empty?
988
+ raise FatalError.new("unknown key: #{key}")
989
+ end
990
+ return calc_combinations_for_key_fields(key_fields)
991
+ end
992
+
993
+ def read_docs_from_log_file(log_filename)
994
+ result = []
995
+ if File.exist?(log_filename) then
996
+ File.open(log_filename, "r") do |f|
997
+ f.each_line do |line|
998
+ doc = {}
999
+ if line =~ /(.*)\ <-\ \{(.*)\}/ then
1000
+ filename = $1
1001
+ combination = $2
1002
+ combination.split(", ").each do |assignment|
1003
+ if assignment =~ /(.*):\ (.*)/ then
1004
+ key = $1
1005
+ value = $2
1006
+ if value =~ /\"(.*)\"/ then
1007
+ value = $1
1008
+ elsif value == "nil" then
1009
+ value = nil
1010
+ end
1011
+ doc[key] = value
1012
+ else
1013
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid assignment #{assignment.inspect}")
1014
+ end
1015
+ end
1016
+ else
1017
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid line #{line.inspect}")
1018
+ end
1019
+ result << doc
1020
+ end
1021
+ end
1022
+ end
1023
+ return result
1024
+ end
1025
+
1026
+ def prepare_output_doc_filename(number, output_dir)
1027
+ chunk_number = number / 100000
1028
+ dirname = "#{output_dir}/#{chunk_number}"
1029
+ unless Dir.exist?(dirname) then
1030
+ FileUtils.mkdir_p(dirname)
1031
+ end
1032
+ return "#{dirname}/doc#{number}.xml"
1033
+ end
1034
+
1035
+ def values_for_key_field(field)
1036
+ if field[:type] == :field then
1037
+ if field[:datatype] == :enum then
1038
+ if field[:optional] then
1039
+ return field[:values] + [nil]
1040
+ else
1041
+ return field[:values]
1042
+ end
1043
+ elsif field[:datatype] == :int then
1044
+ if field.has_key?(:min) and field.has_key?(:max) and field[:min] <= field[:max] then
1045
+ return (field[:min]..field[:max]).to_a
1046
+ else
1047
+ raise RuntimeError.new("cannot determine values for unbounded :int field")
1048
+ end
1049
+ else
1050
+ raise RuntimeError.new("cannot determine values for datatype #{field[:datatype].inspect}")
1051
+ end
1052
+ elsif field[:type] == :structure then
1053
+ if field[:optional] then
1054
+ return [true, false]
1055
+ else
1056
+ return [true]
1057
+ end
1058
+ else
1059
+ raise RuntimeError.new("Encountered unknown :type in model: #{field[:type].inspect} - keys can only mark fields or structures.")
1060
+ end
1061
+ end
1062
+
1063
+ def collect_key_fields(fkey, struct, prefix = nil)
1064
+ result = {}
1065
+ struct.each do |key, value|
1066
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1067
+ if value[:type] == :structure
1068
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1069
+ xpath = key.to_s
1070
+ xpath = "#{prefix}/#{xpath}" if prefix
1071
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values].include?(k)}
1072
+ end
1073
+ if prefix then
1074
+ new_prefix = "#{prefix}/#{key}"
1075
+ else
1076
+ new_prefix = key
1077
+ end
1078
+ result = result.merge(collect_key_fields(fkey, value[:ref], new_prefix))
1079
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1080
+ xpath = key.to_s
1081
+ xpath = "#{prefix}/#{xpath}" if prefix
1082
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values, :min, :max].include?(k)}
1083
+ end
1084
+ end
1085
+ return result
1086
+ end
1087
+
1088
+ def collect_key_fields_and_strctures(fkey, struct, prefix = nil)
1089
+ fields = []
1090
+ structures = []
1091
+ struct.each do |key, value|
1092
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1093
+ if value[:type] == :structure
1094
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1095
+ xpath = key.to_s
1096
+ xpath = "#{prefix}/#{xpath}" if prefix
1097
+ structures << xpath
1098
+ end
1099
+ if prefix then
1100
+ new_prefix = "#{prefix}/#{key}"
1101
+ else
1102
+ new_prefix = key
1103
+ end
1104
+ result_fields, result_structures = collect_key_fields_and_strctures(fkey, value[:ref], new_prefix)
1105
+ fields += result_fields
1106
+ structures += result_structures
1107
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1108
+ xpath = key.to_s
1109
+ xpath = "#{prefix}/#{xpath}" if prefix
1110
+ fields << xpath
1111
+ end
1112
+ end
1113
+ return fields, structures
1114
+ end
1115
+
1116
+ def collect_keys(struct)
1117
+ result = []
1118
+ struct.each do |key, value|
1119
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1120
+ if value[:type] == :structure
1121
+ if value.has_key?(:keys) then
1122
+ result += value[:keys]
1123
+ end
1124
+ if value[:ref] then
1125
+ result += collect_keys(value[:ref])
1126
+ else
1127
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1128
+ end
1129
+ elsif value[:type] == :field and value.has_key?(:keys) then
1130
+ result += value[:keys]
1131
+ end
1132
+ end
1133
+ result.uniq
1134
+ end
1135
+
1136
+ def collect_validation_rules(struct)
1137
+ result = []
1138
+ struct.each do |key, value|
1139
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1140
+ if key == :validation_rules then
1141
+ result += value.keys
1142
+ else
1143
+ if value[:type] == :structure
1144
+ if value[:ref] then
1145
+ result += collect_validation_rules(value[:ref])
1146
+ else
1147
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1148
+ end
1149
+ end
1150
+ end
1151
+ end
1152
+ result.uniq
1153
+ end
1154
+
1155
+ def list_keys(message)
1156
+ keys = collect_keys(message)
1157
+ puts "keys: #{keys.join(", ")}"
1158
+ end
1159
+
1160
+ def list_fields_and_structures(message, fkey)
1161
+ fields, structures = collect_key_fields_and_strctures(fkey, message)
1162
+ puts "fields for key #{fkey}:"
1163
+ fields.each { |f| puts " #{f}" }
1164
+ puts "structures for key #{fkey}:"
1165
+ structures.each { |s| puts " #{s}" }
1166
+ end
1167
+
1168
+ def list_validation_rules(message)
1169
+ rules = collect_validation_rules(message)
1170
+ puts "Validation Rules:"
1171
+ rules.each { |r| puts " #{r}" }
1172
+ end
1173
+
1174
+ def value_of_type(value, type)
1175
+ if type == :string or type == :enum then
1176
+ "\"#{value}\""
1177
+ elsif type == :int then
1178
+ value.to_s
1179
+ elsif type == :date then
1180
+ (Date.parse(value) - Time.at(0).to_date).to_i.to_s
1181
+ elsif type == :timestamp then
1182
+ (Time.new(value) - Time.at(0)).to_i.to_s
1183
+ else
1184
+ raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
1185
+ end
1186
+ end
1187
+
1188
+ class CoreEnumerationAlgo
1189
+
1190
+ def initialize()
1191
+ @num_tries = 0
1192
+ @num_docs = 0
1193
+ @on_new_doc = nil
1194
+ @on_unsat = nil
1195
+ end
1196
+
1197
+
1198
+ # fix values of all variables left from switched
1199
+ def enumerate_fixing_clause(assignment, vars, switched)
1200
+ clauses = []
1201
+ switched.times do |index|
1202
+ var_xpath = vars[index][0]
1203
+ var_def = vars[index][1]
1204
+ if var_def[:type] == :field then
1205
+ if assignment[var_xpath] == nil then
1206
+ clauses << "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named fixing_clause_#{index}))"
1207
+ else
1208
+ clauses << "(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(assignment[var_xpath], var_def[:datatype])})) :named fixing_clause_#{index}))"
1209
+ end
1210
+ elsif var_def[:type] == :structure then
1211
+ clauses << "(assert (! (= #{exists_var_for_structure(var_xpath)} #{assignment[var_xpath].inspect}) :named fixing_clause_#{index}))"
1212
+ end
1213
+ end
1214
+ "; fixing clauses:\n#{clauses.join("\n")}"
1215
+ end
1216
+
1217
+ def summarize_fixing_clause(assignment, vars, switched)
1218
+ fixed = switched.times.to_a.map do |index|
1219
+ var_xpath = vars[index][0]
1220
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1221
+ end
1222
+ "fixed(#{fixed.join(", ")})"
1223
+ end
1224
+
1225
+ # prevent the var from taking values that already have been enumerated exhaustively
1226
+ def enumerate_blocking_clause(var_xpath, var_def, blocked)
1227
+ clauses = []
1228
+ num = 0
1229
+ if var_def[:type] == :field then
1230
+ blocked.each do |value|
1231
+ if value == nil then
1232
+ clauses << "(assert (! (not (= #{filled_var_for_field(var_xpath)} false)) :named blocking_clause_#{num}))"
1233
+ else
1234
+ clauses << "(assert (! (not (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(value, var_def[:datatype])}))) :named blocking_clause_#{num}))"
1235
+ end
1236
+ num += 1
1237
+ end
1238
+ elsif var_def[:type] == :structure then
1239
+ blocked.each do |value|
1240
+ clauses << "(assert (! (not (= #{exists_var_for_structure(var_xpath)} #{value.inspect})) :named blocking_clause_#{num}))"
1241
+ num += 1
1242
+ end
1243
+ end
1244
+ "; blocking clauses:\n#{clauses.join("\n")}"
1245
+ end
1246
+
1247
+ def summarize_blocking_clause(var_xpath, blocked)
1248
+ "blocked(#{var_xpath})=[#{blocked.map{|v| v.inspect}.join(", ")}]"
1249
+ end
1250
+
1251
+ def generate_sparse_doc(progress, message, key, config, options, code, output_doc_filename)
1252
+ additional_options = {}
1253
+ if code then
1254
+ additional_options = { :add_SMTLIB => code }
1255
+ end
1256
+ return generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options.merge(additional_options))
1257
+ end
1258
+
1259
+ def log_filename_for_output_filename(output_filename)
1260
+ "#{output_filename}.smt2"
1261
+ end
1262
+
1263
+ def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
1264
+ raise RuntimeError.new("option :solverFactory is required.") unless options[:solverFactory]
1265
+ log = options[:log] || nil
1266
+ log.puts "generating doc #{output_filename}" if log
1267
+ options[:solverLog] = log_filename_for_output_filename(output_filename)
1268
+
1269
+ key_fields = collect_key_fields(key, message)
1270
+
1271
+ solverFactory = options[:solverFactory]
1272
+ model = solverFactory.query_model(options) do |solver|
1273
+ set_standard_options(progress, solver)
1274
+ log.puts "translating structure" if log
1275
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
1276
+ solver.to_solver(progress, "")
1277
+ end
1278
+
1279
+ if model then
1280
+ result_doc = build_document_from_model(progress, message, model)
1281
+ dir = File.dirname(output_filename)
1282
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1283
+ File.open(output_filename, "w") do |f|
1284
+ f.puts result_doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
1285
+ end
1286
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
1287
+ return extract_key_field_values_from_model(key_fields, model)
1288
+ else
1289
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
1290
+ return nil
1291
+ end
1292
+ end
1293
+
1294
+ def store_continuation_point(doc_number, state, assignment, new_assignment, filename, options, time)
1295
+ File.open(filename, "w") do |f|
1296
+ state_str = "state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")})"
1297
+ assignment_str = "assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1298
+ new_assignment_str = "new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1299
+ f.puts("#{doc_number};#{@num_docs};#{state_str};#{assignment_str};#{new_assignment_str};#{time}")
1300
+ end
1301
+ end
1302
+
1303
+ def self.parse_state(state_str)
1304
+ unless state_str =~ /state\((.*)\)/
1305
+ return nil
1306
+ end
1307
+ split = $1.split("#")
1308
+ split.map do |e|
1309
+ if e == "nil" then
1310
+ nil
1311
+ elsif e =~ /\[(.*)\]/ then
1312
+ $1.split(",").map do |i|
1313
+ if i.strip == "nil" then
1314
+ nil
1315
+ elsif i.strip == "true" then
1316
+ true
1317
+ elsif i.strip == "false" then
1318
+ false
1319
+ elsif i.strip =~ /\"(.*)\"/ then
1320
+ $1
1321
+ end
1322
+ end
1323
+ end
1324
+ end
1325
+ end
1326
+
1327
+ def self.parse_assignment(str)
1328
+ unless str =~ /assignment\((.*)\)/
1329
+ return nil
1330
+ end
1331
+ split = $1.split(",")
1332
+ result = {}
1333
+ split.each do |e|
1334
+ s2 = e.split(":")
1335
+ key = s2[0]
1336
+ value = s2[1]
1337
+ if value == "nil" then
1338
+ result[key] = nil
1339
+ elsif value == "true" then
1340
+ result[key] = true
1341
+ elsif value == "false" then
1342
+ result[key] = false
1343
+ elsif value =~ /\"(.*)\"/
1344
+ result[key] = $1
1345
+ end
1346
+ end
1347
+ result
1348
+ end
1349
+
1350
+ def self.parse_new_assignment(str)
1351
+ unless str =~ /new_assignment\((.*)\)/
1352
+ return nil
1353
+ end
1354
+ split = $1.split(",")
1355
+ result = {}
1356
+ split.each do |e|
1357
+ s2 = e.split(":")
1358
+ key = s2[0]
1359
+ value = s2[1]
1360
+ if value == "nil" then
1361
+ result[key] = nil
1362
+ elsif value == "true" then
1363
+ result[key] = true
1364
+ elsif value == "false" then
1365
+ result[key] = false
1366
+ elsif value =~ /\"(.*)\"/
1367
+ result[key] = $1
1368
+ end
1369
+ end
1370
+ result
1371
+ end
1372
+
1373
+ def self.parse_continuation_point(line)
1374
+ split = line.split(";")
1375
+ doc_number = Integer(split[0])
1376
+ num_docs = Integer(split[1])
1377
+ state = CoreEnumerationAlgo.parse_state(split[2])
1378
+ assignment = CoreEnumerationAlgo.parse_assignment(split[3])
1379
+ new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
1380
+ processessing_time = Integer(split[5])
1381
+ return [doc_number, num_docs, state, assignment, new_assignment, processessing_time]
1382
+ end
1383
+
1384
+ # TODO: logging
1385
+
1386
+ def display_combination_space(assignment, vars, switched, blocked)
1387
+ # enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1388
+
1389
+ # => all vars left of switched are fixed to their assigned values (when the value is nil, then they are empty)
1390
+ # => the switched var is prevented to have any of the blocked values
1391
+ res = switched.times.to_a.map do |index|
1392
+ var_xpath = vars[index][0]
1393
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1394
+ end + ["#{vars[switched][0]}\\{#{blocked.map{|v| v.inspect}.join(",")}}"]
1395
+ "{#{res.join(", ")}}"
1396
+ end
1397
+
1398
+
1399
+ # core algo
1400
+ def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size(), start_time, prior_processing_time)
1401
+ enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time) if switched < vars.size()-1
1402
+ is_finite_state = keys_are_finite_state(vars)
1403
+ if state[switched] == nil then
1404
+ blocked = [assignment[vars[switched][0]]]
1405
+ state[switched] = blocked
1406
+ else
1407
+ blocked = state[switched] + [assignment[vars[switched][0]]]
1408
+ state[switched] = blocked
1409
+ end
1410
+ found = true
1411
+ while found do
1412
+ found = false
1413
+ # - fix all vars left of switched to their values in assignment
1414
+ # - block switched from obtaining the values in blocked
1415
+ # progress.print_line "DEBUG: #{summarize_fixing_clause(assignment, vars, switched)}, #{summarize_blocking_clause(vars[switched][0], blocked)}"
1416
+ clauses = enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1417
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1418
+ new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
1419
+ generated_doc_number = @num_tries
1420
+ time = prior_processing_time + (Time.now().to_i - options[:timestamp_now])
1421
+ store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}", options, time)
1422
+ progress.print_line "STORING continuation point"
1423
+ progress.print_line "|time=#{time}"
1424
+ progress.print_line "|doc_number=#{generated_doc_number}"
1425
+ progress.print_line "|num_docs=#{@num_docs}"
1426
+ progress.print_line "|state=#{state.inspect}"
1427
+ progress.print_line "|assignment=#{assignment.inspect}"
1428
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1429
+
1430
+ if is_finite_state then
1431
+ progress.report_progress(determine_progress(state, vars), start_time, prior_processing_time, @num_docs)
1432
+ else
1433
+ progress.report_progress(nil, start_time, prior_processing_time, @num_docs)
1434
+ end
1435
+ @num_tries += 1
1436
+ if new_assignment then # sat
1437
+ @num_docs += 1
1438
+ @on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
1439
+ if switched < vars.size()-1 then
1440
+ enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1441
+ end
1442
+ blocked << new_assignment[vars[switched][0]]
1443
+ state[switched] = blocked
1444
+ found = true
1445
+ else # unsat
1446
+ @on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked), switched) if @on_unsat
1447
+ found = false
1448
+ end
1449
+ end
1450
+ state[switched] = nil
1451
+ end
1452
+
1453
+ # NOTE: can only be called when all key-fields are finite-state
1454
+ def is_final_state(state, new_assignment, key_fields_list)
1455
+ return state[1,state.size()] == [nil] * (state.size()-1) &&
1456
+ state[0].to_set == values_for_key_field(key_fields_list[0][1]).to_set &&
1457
+ new_assignment.empty?
1458
+ end
1459
+
1460
+ # NOTE: can only be called when all key-fields are finite-state
1461
+ def determine_progress(state, vars)
1462
+ combinations_per_value = [nil] * vars.size()
1463
+ comb = 1
1464
+ vars.size().times.to_a.reverse_each do |i|
1465
+ combinations_per_value[i] = comb
1466
+ comb = values_for_key_field(vars[i][1]).size() * comb
1467
+ end
1468
+
1469
+ blocked_combinations = 0
1470
+ state.size().times do |i|
1471
+ if state[i] then
1472
+ blocked_combinations += state[i].size() * combinations_per_value[i]
1473
+ end
1474
+ end
1475
+ blocked_combinations
1476
+ end
1477
+
1478
+ def num_combinations(key_def)
1479
+ if key_def[:type] == :field then
1480
+ if key_def[:datatype] == :enum then
1481
+ key_def[:values].size() + (key_def[:optional] ? 1 : 0)
1482
+ elsif key_def[:datatype] == :int then
1483
+ if key_def.has_key?(:min) and key_def.has_key?(:max) and key_def[:min] <= key_def[:max] then
1484
+ 1 + key_def[:max] - key_def[:min] + (key_def[:optional] ? 1 : 0)
1485
+ else
1486
+ nil
1487
+ end
1488
+ else
1489
+ nil
1490
+ end
1491
+ elsif key_def[:type] == :structure then
1492
+ 2
1493
+ end
1494
+ end
1495
+
1496
+ def keys_are_finite_state(list)
1497
+ list.each do |field|
1498
+ name = field[0]
1499
+ if field[1][:type] == :field then
1500
+ if field[1][:datatype] == :enum then
1501
+ # enums are finite-state
1502
+ elsif field[1][:datatype] == :int and field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
1503
+ # ints are finite-state when both bounds are given and :min <= :max
1504
+ else
1505
+ return false
1506
+ end
1507
+ end
1508
+ end
1509
+ return true
1510
+ end
1511
+
1512
+ def generate_docs_for_key(progress, message, key, output_dir, config, options)
1513
+
1514
+ prior_processing_time = 0
1515
+ start_time = options[:timestamp_now]
1516
+ number_of_combinations = calc_combinations(message, key)
1517
+ if number_of_combinations.is_a?(String) then
1518
+ progress.set_max(100000000000000)
1519
+ else
1520
+ progress.set_max(number_of_combinations)
1521
+ end
1522
+ progress.report_progress(0.0, start_time, prior_processing_time, 0)
1523
+ progress.print_line "generating sparse documents efficiently..."
1524
+ progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
1525
+ progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
1526
+
1527
+ progress.print_line "preparing output dir #{output_dir}..."
1528
+ if options[:continue] then
1529
+ unless Dir.exist?(output_dir) then
1530
+ raise FatalError.new("FATAL: directory #{output_dir} not found. There seems to be nothing to continue from...")
1531
+ end
1532
+ log_mode = "a"
1533
+ else
1534
+ unless Dir.exist?(output_dir) then
1535
+ FileUtils.mkdir_p(output_dir)
1536
+ end
1537
+ log_mode = "w"
1538
+ end
1539
+ log_filename = "#{output_dir}/#{COMBINATION_LOG}"
1540
+
1541
+ key_fields = collect_key_fields(key, message)
1542
+ key_fields_list = key_fields.each_pair.to_a
1543
+ key_fields_list.sort! {|a,b| num_combinations(b[1]) <=> num_combinations(a[1]) }
1544
+
1545
+ if options[:continue] then
1546
+ stored_key_fields_str = File.read("#{output_dir}/#{VARS_LOG}")
1547
+ unless key_fields_list.to_yaml == stored_key_fields_str then
1548
+ raise FatalError.new("vars are differing! please specify the same key in order to use -continue!")
1549
+ end
1550
+ progress.print_line "continuing enumerating documents for key #{key}!"
1551
+ else
1552
+ # store vars
1553
+ File.open("#{output_dir}/#{VARS_LOG}", "w") do |f|
1554
+ f.puts(key_fields_list.to_yaml)
1555
+ end
1556
+ end
1557
+
1558
+ cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
1559
+ if options[:continue] then
1560
+ unless File.exist?(cont_filename) then
1561
+ raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
1562
+ end
1563
+ doc_number, num_docs, state, assignment, new_assignment, prior_processing_time = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
1564
+ if is_final_state(state, new_assignment, key_fields_list) then
1565
+ progress.report_progress(number_of_combinations, start_time, prior_processing_time, num_docs)
1566
+ raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
1567
+ end
1568
+ progress.print_line "RESUMING enumeration from the following continuation point:"
1569
+ progress.print_line "|time=#{prior_processing_time}"
1570
+ progress.print_line "|doc_number=#{doc_number}"
1571
+ progress.print_line "|num_docs=#{num_docs}"
1572
+ progress.print_line "|state=#{state.inspect}"
1573
+ progress.print_line "|assignment=#{assignment.inspect}"
1574
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1575
+ if keys_are_finite_state(key_fields_list) then
1576
+ progress.report_progress(determine_progress(state, key_fields_list), start_time, prior_processing_time, num_docs)
1577
+ else
1578
+ progress.report_progress(nil, start_time, prior_processing_time, num_docs)
1579
+ end
1580
+
1581
+ if new_assignment && !new_assignment.empty? then
1582
+ assignment = new_assignment
1583
+ end
1584
+
1585
+ # restore state at continuation point
1586
+ @num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
1587
+ number_of_combinations += 1 # account for this in the safety mechanism
1588
+ @num_docs = num_docs
1589
+ File.open(log_filename, log_mode) do |log|
1590
+
1591
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1592
+ # combination logging
1593
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1594
+
1595
+ if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1596
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1597
+ end
1598
+
1599
+ # safety mechanism
1600
+ if @num_docs > number_of_combinations then
1601
+ raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1602
+ end
1603
+ }
1604
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1605
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1606
+ progress.print_line "doc#{num_doc} NOT generated"
1607
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1608
+ }
1609
+
1610
+ # core algo - resume from state
1611
+ enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1612
+ end
1613
+ else
1614
+ # init safety mechanism
1615
+ @num_tries = 0
1616
+ @num_docs = 0
1617
+ # first try - a freely generated document
1618
+ File.open(log_filename, log_mode) do |log|
1619
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1620
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1621
+ # combination logging
1622
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1623
+
1624
+ if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1625
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1626
+ end
1627
+
1628
+ # safety mechanism
1629
+ unless number_of_combinations.is_a?(String) then
1630
+ if @num_docs > number_of_combinations then
1631
+ raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1632
+ end
1633
+ end
1634
+ }
1635
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1636
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1637
+ progress.print_line "doc#{num_doc} NOT generated"
1638
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1639
+ }
1640
+ key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
1641
+ @num_tries += 1
1642
+ if key_values then
1643
+ @num_docs += 1
1644
+ @on_new_doc.call(@num_tries, output_doc_filename, key_values)
1645
+ else
1646
+ # NOTE: this only occurs when the model is inconsistent and has no valid documents
1647
+ progress.print_line("ERROR: Model inconsistent - no valid documents could be found.")
1648
+ @on_unsat.call(@num_tries, log_filename_for_output_filename(output_doc_filename), "{}", 0)
1649
+ end
1650
+
1651
+ # core algo - start enumeration
1652
+ enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options, start_time, prior_processing_time)
1653
+ end
1654
+ end
1655
+ progress.print_line "No more valid documents could be found. -> Exiting"
1656
+ progress.report_progress(number_of_combinations, start_time, prior_processing_time, @num_docs)
1657
+ end
1658
+ end
1659
+
1660
+ def introduce_parent_links(message, parent = nil, prefix = nil)
1661
+ if parent != nil then
1662
+ message[:parent_link] = parent
1663
+ end
1664
+ if prefix != nil then
1665
+ message[:struct_xpath_prefix] = prefix
1666
+ end
1667
+ message.each_pair do |key, value|
1668
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1669
+ if value[:type] == :structure then
1670
+ if prefix then
1671
+ new_prefix = "#{prefix}/#{key}"
1672
+ else
1673
+ new_prefix = key
1674
+ end
1675
+ introduce_parent_links(value[:ref], message, new_prefix)
1676
+ end
1677
+ end
1678
+ end
1679
+
1680
+ def validate_model(message, prefix=nil)
1681
+
1682
+ message.each_pair do |key, value|
1683
+ if key == :validation_rules then
1684
+ value.each_pair do |vr_key, vr_value|
1685
+ unless vr_value[:text] && vr_value[:text] != "" then
1686
+ puts "INFO: Model Problem: validation rule #{vr_key} does not have a textual representation."
1687
+ end
1688
+ unless vr_value[:smtlib] && vr_value[:smtlib] != "" then
1689
+ puts "WARN: Model Problem: validation rule #{vr_key} does not have an SMTLIB representation. It will hence be omitted!"
1690
+ end
1691
+ end
1692
+ elsif key == :predicates then
1693
+ value.each_pair do |mkey, mvalue|
1694
+ next unless mvalue
1695
+ unless mvalue[:text] && mvalue[:text] != "" then
1696
+ puts "INFO: Model Problem: structure #{prefix}'s predicate #{mkey} does not have a textual representation."
1697
+ end
1698
+ unless mvalue[:smtlib] && mvalue[:smtlib] != "" then
1699
+ puts "WARN: Model Problem: structure #{prefix}'s predicate #{mkey} does not have an SMTLIB representation. It will be regarded as always true."
1700
+ end
1701
+ end
1702
+ elsif key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1703
+ next
1704
+ else
1705
+ if value[:type] == :structure then
1706
+ unless value[:ref] then
1707
+ raise RuntimeError.new("Model Problem: encountered structure without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1708
+ end
1709
+ if prefix then
1710
+ new_prefix = "#{prefix}/#{key}"
1711
+ else
1712
+ new_prefix = key
1713
+ end
1714
+ validate_model(value[:ref], new_prefix)
1715
+ elsif value[:type] == :field then
1716
+ unless value[:datatype] then
1717
+ raise RuntimeError.new("Model Problem: encountered field without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1718
+ end
1719
+ if value[:datatype] == :enum then
1720
+ unless value[:values] then
1721
+ raise RuntimeError.new("Model Problem: encountered enum field without a :values (key=#{key.inspect}, value=#{value.inspect})")
1722
+ end
1723
+ end
1724
+ elsif value[:type] == :list then
1725
+ unless value[:datatype] then
1726
+ raise RuntimeError.new("Model Problem: encountered list without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1727
+ end
1728
+ if value[:datatype] == :enum then
1729
+ unless value[:values] then
1730
+ raise RuntimeError.new("Model Problem: encountered enum list without a :values (key=#{key.inspect}, value=#{value.inspect})")
1731
+ end
1732
+ end
1733
+ elsif value[:type] == :objlist then
1734
+ unless value[:ref] then
1735
+ raise RuntimeError.new("Model Problem: encountered objlist without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1736
+ end
1737
+ unless value[:model_maxLength] then
1738
+ raise RuntimeError.new("Model Problem: encountered objlist without a :model_maxLength (key=#{key.inspect}, value=#{value.inspect})")
1739
+ end
1740
+ unless value[:model_maxLength].is_a?(Integer) then
1741
+ raise RuntimeError.new("Model Problem: encountered objlist whose :model_maxLength property is not an Integer (key=#{key.inspect}, value=#{value.inspect})")
1742
+ end
1743
+ unless value[:xpath_element] then
1744
+ raise RuntimeError.new("Model Problem: encountered objlist without a :xpath_element property (key=#{key.inspect}, value=#{value.inspect})")
1745
+ end
1746
+ unless value[:xpath_element].is_a?(String) then
1747
+ raise RuntimeError.new("Model Problem: encountered objlist whose :xpath_element property is not a String (key=#{key.inspect}, value=#{value.inspect})")
1748
+ end
1749
+ if prefix then
1750
+ new_prefix = "#{prefix}/#{key}"
1751
+ else
1752
+ new_prefix = key
1753
+ end
1754
+ validate_model(value[:ref], new_prefix)
1755
+ else
1756
+ raise RuntimeError.new("Model Problem: encountered unknown :type #{value[:type].inspect}")
1757
+ end
1758
+ end
1759
+ end
1760
+ end
1761
+
1762
+ def validate_doc_against_schema(struct, doc)
1763
+ root = doc.root
1764
+ sdef = struct[root.name.to_sym]
1765
+ unless sdef[:type] == :structure then
1766
+ puts "ERROR: schema validation failed: root element should be a structure"
1767
+ end
1768
+ # first, check that each doc element is defined for the struct
1769
+ validate_doc_struct_against_schema(sdef[:ref], root, root.name)
1770
+ # second, check that all required elements from the struct are actually present in the doc
1771
+ validate_schema_against_doc_struct(sdef[:ref], root, root.name)
1772
+ end
1773
+
1774
+ def validate_doc_struct_against_schema(struct, doc, prefix = nil)
1775
+ doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
1776
+ fdef = struct[c.name.to_sym]
1777
+ if prefix then
1778
+ xpath = "#{prefix}/#{c.name}"
1779
+ else
1780
+ xpath = c.name
1781
+ end
1782
+ if fdef
1783
+ if fdef[:type] == :structure then
1784
+ return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
1785
+ end
1786
+ else
1787
+ if prefix then
1788
+ xpath = "#{prefix}/#{c.name}"
1789
+ else
1790
+ xpath = c.name
1791
+ end
1792
+ raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
1793
+ end
1794
+ end
1795
+ end
1796
+
1797
+ def validate_schema_against_doc_struct(struct, doc, prefix = nil)
1798
+ struct.each do |key, value|
1799
+ next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1800
+ if prefix then
1801
+ xpath = "#{prefix}/#{key}"
1802
+ else
1803
+ xpath = key
1804
+ end
1805
+ if value[:type] == :structure then
1806
+ node = doc.at_xpath(key.to_s)
1807
+ if node then
1808
+ validate_schema_against_doc_struct(value[:ref], node, xpath)
1809
+ else
1810
+ if value[:optional] == false then
1811
+ raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
1812
+ end
1813
+ end
1814
+ elsif value[:type] == :field then
1815
+ if value[:optional] == false then
1816
+ unless doc.at_xpath(key.to_s) then
1817
+ raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
1818
+ end
1819
+ end
1820
+ elsif value[:type] == :list then
1821
+ if value[:optional] == false then
1822
+ unless doc.at_xpath(key.to_s) then
1823
+ raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
1824
+ end
1825
+ end
1826
+ list = doc.at_xpath(key.to_s)
1827
+ if list then
1828
+ list.children.to_a.each do |c|
1829
+ next unless c.is_a?(Nokogiri::XML::Element)
1830
+ unless c.name == value[:xpath_element] then
1831
+ raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
1832
+ end
1833
+ end
1834
+ end
1835
+ else
1836
+ raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
1837
+ end
1838
+ end
1839
+ end
1840
+
1841
+ def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
1842
+ options = hash[:options]
1843
+ config = hash[:config]
1844
+
1845
+ solver.to_solver(progress, "; --- Document: ---")
1846
+ translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
1847
+ end
1848
+
1849
+ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
1850
+ options = hash[:options]
1851
+ config = hash[:config]
1852
+
1853
+ struct.each do |key, value|
1854
+ next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1855
+ if prefix then
1856
+ xpath = "#{prefix}/#{key.to_s}"
1857
+ else
1858
+ xpath = key.to_s
1859
+ end
1860
+ if value[:type] == :structure then
1861
+ node = doc.at_xpath(xpath)
1862
+ if node then
1863
+ info = {
1864
+ :assertion_type => :doc_structure_exists,
1865
+ :xpath => xpath,
1866
+ }
1867
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1868
+ solver.associateAssertionIDWith(assertionID, info)
1869
+ solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} true) :named #{assertionID}))")
1870
+
1871
+ translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
1872
+ else
1873
+ info = {
1874
+ :assertion_type => :doc_structure_not_exists,
1875
+ :xpath => xpath,
1876
+ }
1877
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1878
+ solver.associateAssertionIDWith(assertionID, info)
1879
+ solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
1880
+ end
1881
+ elsif value[:type] == :field then
1882
+ node = doc.at_xpath(xpath)
1883
+ if node then
1884
+ fvalue = node.text
1885
+ info = {
1886
+ :assertion_type => :doc_field_filled,
1887
+ :xpath => xpath,
1888
+ :value => fvalue
1889
+ }
1890
+ fvalue_str = value_of_type(fvalue, value[:datatype])
1891
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1892
+ solver.associateAssertionIDWith(assertionID, info)
1893
+ solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
1894
+ else
1895
+ info = {
1896
+ :assertion_type => :doc_field_empty,
1897
+ :xpath => xpath,
1898
+ }
1899
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1900
+ solver.associateAssertionIDWith(assertionID, info)
1901
+ solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1902
+ end
1903
+ elsif value[:type] == :list then
1904
+ node = doc.at_xpath(xpath)
1905
+ if node then
1906
+ fvalues = []
1907
+ node.children.to_a.each do |c|
1908
+ next unless c.is_a?(Nokogiri::XML::Element)
1909
+ fvalues << c.text
1910
+ end
1911
+ info = {
1912
+ :assertion_type => :doc_list_filled,
1913
+ :xpath => xpath,
1914
+ :values => fvalues
1915
+ }
1916
+ fvalue_str = value_of_type(fvalues, value[:datatype])
1917
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1918
+ solver.associateAssertionIDWith(assertionID, info)
1919
+ clauses = []
1920
+ fvalues.each_with_index do |fvalue, index|
1921
+ clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
1922
+ end
1923
+ solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
1924
+ else
1925
+ info = {
1926
+ :assertion_type => :doc_field_empty,
1927
+ :xpath => xpath,
1928
+ }
1929
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1930
+ solver.associateAssertionIDWith(assertionID, info)
1931
+ solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1932
+ end
1933
+ else
1934
+ raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
1935
+ end
1936
+ end
1937
+ end
1938
+
1939
+ def validate_doc(progress, struct, doc, options, config)
1940
+ raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
1941
+ options[:solverLog] = "validation.smt2"
1942
+
1943
+ solverFactory = options[:solverFactory]
1944
+ result = solverFactory.query(options) do |solver|
1945
+ set_standard_options(progress, solver)
1946
+ translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
1947
+ translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
1948
+ solver.to_solver(progress, "")
1949
+ end
1950
+ unless result == true then
1951
+ raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
1952
+ end
1953
+
1954
+ return true
1955
+ end
1956
+
1957
+ def generate_unused_logfile_name()
1958
+ number = 0
1959
+ result = "logs/#{number}.log"
1960
+ while File.exist?(result) do
1961
+ number += 1
1962
+ result = "logs/#{number}.log"
1963
+ end
1964
+ result
1965
+ end
1966
+
1967
+ def generate(message, argv, message_config_filename = nil)
1968
+ if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
1969
+ puts USAGE
1970
+ exit(0)
1971
+ end
1972
+
1973
+ options = { :negated_validation_rules => [], :date_now => (Date.today - Time.at(0).to_date).to_i, :timestamp_now => Time.now().to_i }
1974
+
1975
+ opt = argv.shift
1976
+ while opt do
1977
+ if opt == "-negate" then
1978
+ rule = argv.shift
1979
+ options[:negated_validation_rules] << rule
1980
+ elsif opt == "-list-keys" then
1981
+ options[:list_keys] = true
1982
+ elsif opt == "-list" then
1983
+ options[:list] = argv.shift
1984
+ elsif opt == "-list-validation-rules" then
1985
+ options[:list_validation_rules] = true
1986
+ elsif opt == "-generate-docs-from" then
1987
+ options[:combination_source] = argv.shift
1988
+ elsif opt == "-skip" then
1989
+ options[:skip] = Integer(argv.shift)
1990
+ elsif opt == "-generate-docs-for-key" then
1991
+ options[:docs_for_key] = argv.shift
1992
+ elsif opt == "-count-docs-for-key" then
1993
+ options[:count_docs_for_key] = argv.shift
1994
+ elsif opt == "-continue" then
1995
+ options[:continue] = true
1996
+ elsif opt == "-max-num-docs" then
1997
+ options[:max_num_docs] = Integer(argv.shift)
1998
+ puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
1999
+ elsif opt == "-validate" then
2000
+ if options.has_key?(:validate_files) then
2001
+ options[:validate_files] << argv.shift
2002
+ else
2003
+ options[:validate_files] = [argv.shift]
2004
+ end
2005
+ else
2006
+ puts "FATAL: unknown option #{opt.inspect}."
2007
+ puts USAGE
2008
+ raise FatalError.new("")
2009
+ end
2010
+ opt = argv.shift
2011
+ end
2012
+
2013
+ ProgressBar.logging(1, generate_unused_logfile_name()) do |progress|
2014
+ unless options[:negated_validation_rules].empty? then
2015
+ progress.print_line "negating rules: #{options[:negated_validation_rules].join(", ")}"
2016
+
2017
+ rules = collect_validation_rule_names(message)
2018
+ unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
2019
+ unless unknown_negated_rules.empty? then
2020
+ throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
2021
+ end
2022
+
2023
+ end
2024
+
2025
+ if File.exist?(CONFIG_FILE) then
2026
+ config = YAML.load_file(CONFIG_FILE)
2027
+ else
2028
+ config = DEFAULT_CONFIG
2029
+ File.open(CONFIG_FILE, "w") do |f|
2030
+ f.puts(config.to_yaml)
2031
+ end
2032
+ progress.print_line "No config file found. Default config written to #{CONFIG_FILE}"
2033
+ end
2034
+ if message_config_filename then
2035
+ message_config = YAML.load_file(message_config_filename)
2036
+ config = config.merge(message_config)
2037
+ end
2038
+
2039
+ introduce_parent_links(message)
2040
+ z3path = config[:z3path] || DEFAULT_Z3_PATH
2041
+ options[:solverFactory] = SolverFactory.new(progress, z3path)
2042
+
2043
+ if options[:validate_files] then
2044
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2045
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2046
+ options[:validate_files].each do |filename|
2047
+ progress.print_line "validating file #{filename}"
2048
+ doc = Nokogiri::XML(File.read(filename))
2049
+ validate_doc_against_schema(message, doc)
2050
+ validate_doc(progress, message, doc, options, config)
2051
+ end
2052
+ elsif options[:count_docs_for_key] then
2053
+ progress.print_line "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
2054
+ elsif options[:docs_for_key] then
2055
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2056
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2057
+ validate_model(message)
2058
+ CoreEnumerationAlgo.new().generate_docs_for_key(progress, message, options[:docs_for_key], OUTPUT_DIR, config, options)
2059
+ elsif options[:list_keys] then
2060
+ list_keys(message)
2061
+ elsif options[:list] then
2062
+ list_fields_and_structures(message, options[:list])
2063
+ elsif options[:list_validation_rules] then
2064
+ list_validation_rules(message)
2065
+ else
2066
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2067
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2068
+ validate_model(message)
2069
+ generate_doc(progress, message, OUTPUT_FILE, config, options)
2070
+ end
2071
+ end
2072
+ end
2073
+