mbt-gen 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/mbt-gen.rb ADDED
@@ -0,0 +1,1774 @@
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
+ EOF
77
+
78
+ class FatalError < RuntimeError
79
+ end
80
+
81
+ class NormalProgramTermination < RuntimeError
82
+ end
83
+
84
+ def raw_field(xpath)
85
+ path_str = "base"
86
+ path_str = xpath.gsub("/", "-") if xpath
87
+ "field-#{path_str}"
88
+ end
89
+
90
+ def filled_var_for_field(xpath)
91
+ "#{raw_field(xpath)}-filled"
92
+ end
93
+
94
+ def value_var_for_field(xpath)
95
+ "#{raw_field(xpath)}-value"
96
+ end
97
+
98
+ def exists_var_for_structure(xpath)
99
+ struct_str = "base"
100
+ struct_str = xpath.gsub("/", "-") if xpath
101
+ "struct-#{struct_str}-exists"
102
+ end
103
+
104
+ def size_var_for_list(xpath)
105
+ "#{raw_field(xpath)}-size"
106
+ end
107
+
108
+ def value_var_for_list(xpath, index)
109
+ "#{raw_field(xpath)}-index-#{index}-value"
110
+ end
111
+
112
+ def translate_datatype_to_SMTLIB(datatype)
113
+ case datatype
114
+ when :string
115
+ return "String"
116
+ when :int
117
+ return "Int"
118
+ when :bool
119
+ return "Bool"
120
+ when :date
121
+ return "Int"
122
+ when :timestamp
123
+ return "Int"
124
+ when :enum
125
+ return "String"
126
+ end
127
+ end
128
+
129
+ def translate_value_to_SMTLIB(value)
130
+ if value.is_a?(Integer) then
131
+ return value.to_s
132
+ elsif value.is_a?(String) then
133
+ return value.inspect
134
+ elsif value.is_a?(Date) then
135
+ return (value - (Time.at(0).to_date)).to_i # days since 1970.01.01
136
+ elsif value.is_a?(Time) then
137
+ return value.to_i
138
+ else
139
+ raise RuntimeError.new("cannot translate value to SMTLIB: #{value.inspect}")
140
+ end
141
+ end
142
+
143
+ def min_constraint_for_expr(expr, lower_bound)
144
+ "(>= #{expr} #{lower_bound})"
145
+ end
146
+
147
+ def max_constraint_for_expr(expr, upper_bound)
148
+ "(<= #{expr} #{upper_bound})"
149
+ end
150
+
151
+ def regex_constraint_for_expr(expr, regex)
152
+ "(str.in.re #{expr} #{Regexp_to_SMTLIB.translate_regexp_to_SMTLIB(regex)})"
153
+ end
154
+
155
+ def maxLength_contraint_for_expr(expr, maxLength)
156
+ "(<= (str.len #{expr}) #{maxLength})"
157
+ end
158
+
159
+ def oneOf_contraint_for_expr(expr, values)
160
+ list = values.map do |val|
161
+ "(= #{expr} \"#{val}\")"
162
+ end
163
+ "(or #{list.join(" ")})"
164
+ end
165
+
166
+ def enum_constraint_for_expr(expr, values)
167
+ "(or #{values.map{|x| "(= #{expr} #{translate_value_to_SMTLIB(x)})"}.join(" ")})"
168
+ end
169
+
170
+ def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef, varname)
171
+ if fielddef[:optional] == nil || fielddef[:optional] == false then
172
+ # field is required
173
+ solver.to_solver(progress, "(assert (! #{varname}-filled :named required-#{varname}))")
174
+ end
175
+ datatype = fielddef[:datatype]
176
+ case datatype
177
+ when :int
178
+ if fielddef[:min] then
179
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{min_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named min-#{varname}))")
180
+ end
181
+ if fielddef[:max] then
182
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{max_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named max-#{varname}))")
183
+ end
184
+ when :string
185
+ if fielddef[:regex] then
186
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{regex_constraint_for_expr("#{varname}-value", fielddef[:regex])}) :named regex-#{varname}))")
187
+ end
188
+ # TODO: minLength?
189
+ if fielddef[:maxLength] then
190
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{maxLength_contraint_for_expr("#{varname}-value", fielddef[:maxLength])}) :named maxLength-#{varname}))")
191
+ end
192
+ if fielddef[:from_list] then
193
+ list_filename = "#{LISTS_DIR}/#{fielddef[:from_list]}"
194
+ unless File.exist?(list_filename)
195
+ raise RuntimeError.new("could not find list file #{list_filename}.")
196
+ end
197
+ values = File.read(list_filename).split("\n").map{|v| v.chomp()}
198
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{oneOf_contraint_for_expr("#{varname}-value", values)}) :named oneOf-#{varname}))")
199
+ end
200
+ when :enum
201
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{enum_constraint_for_expr("#{varname}-value", fielddef[:values])}) :named enum-#{varname}))")
202
+ end
203
+ end
204
+
205
+ def translate_list_def_to_SMTLIB_constraints(progress, solver, fielddef, varname)
206
+ datatype = fielddef[:datatype]
207
+ case datatype
208
+ when :int
209
+ if fielddef[:min] then
210
+ fielddef[:model_maxLength].times do |i|
211
+ 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}))")
212
+ end
213
+ end
214
+ if fielddef[:max] then
215
+ fielddef[:model_maxLength].times do |i|
216
+ 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}))")
217
+ end
218
+ end
219
+ when :string
220
+ if fielddef[:regex] then
221
+ fielddef[:model_maxLength].times do |i|
222
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{regex_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:regex])}) :named list-regex-#{varname}-index-#{i}))")
223
+ end
224
+ end
225
+ # TODO: minLength?
226
+ if fielddef[:maxLength] then
227
+ fielddef[:model_maxLength].times do |i|
228
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{maxLength_contraint_for_expr("#{varname}-index-#{i}-value", fielddef[:maxLength])}) :named list-maxLength-#{varname}-index-#{i}))")
229
+ end
230
+ end
231
+ when :enum
232
+ fielddef[:model_maxLength].times do |i|
233
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{enum_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:values])}) :named list-enum-#{varname}-index-#{i}))")
234
+ end
235
+ end
236
+ end
237
+
238
+ def default_value_for_fielddef(fielddef)
239
+ datatype = fielddef[:datatype]
240
+ case datatype
241
+ when :int
242
+ return "0"
243
+ when :string
244
+ return "".inspect
245
+ when :date
246
+ return "0"
247
+ when :timestamp
248
+ return "0"
249
+ when :enum
250
+ return "".inspect
251
+ else
252
+ raise RuntimeError.new("unknown datatype: #{datatype.inspect}")
253
+ end
254
+ end
255
+
256
+ def translate_structure_to_SMTLIB(progress, solver, struct, hash = {})
257
+ prefix = hash[:prefix]
258
+ config = hash[:config]
259
+ options = hash[:options]
260
+ translate_substructure_to_SMTLIB(progress, solver, struct, hash)
261
+ translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
262
+ if options[:add_SMTLIB] then
263
+ solver.to_solver(progress, "; --- additional SMTLIB Code: ---")
264
+ solver.to_solver(progress, options[:add_SMTLIB])
265
+ end
266
+ end
267
+
268
+ def translate_substructure_to_SMTLIB(progress, solver, struct, hash = {})
269
+ prefix = hash[:prefix]
270
+ struct_varname = hash[:struct_varname]
271
+ config = hash[:config]
272
+ options = hash[:options]
273
+ solver.to_solver(progress, "; --- Structure: ---")
274
+ struct.each do |field, definition|
275
+ if field == :additional_smtlib then
276
+ solver.to_solver(progress, "; additional SMTLIB of structure #{prefix}")
277
+ solver.to_solver(progress, translate_validation_rule_to_SMTLIB(definition, struct, prefix, config, options, "<additional SMTLIB of structure #{prefix}>"))
278
+ elsif field == :validation_rules || field == :predicates || field == :parent_link || field == :struct_xpath_prefix
279
+ next
280
+ else
281
+ solver.to_solver(progress, "; #{field.to_s}")
282
+ if definition[:type] == :field then
283
+ display_prefix = ""
284
+ display_prefix = "#{prefix}/" if prefix
285
+ xpath = field.to_s
286
+ xpath = "#{prefix}/#{xpath}" if prefix
287
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
288
+ solver.declare_const(progress, value_var_for_field(xpath), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :datatype => definition[:datatype]})
289
+ 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)}))")
290
+ if (struct_varname)
291
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
292
+ end
293
+ translate_field_def_to_SMTLIB_constraints(progress, solver, definition, raw_field(xpath))
294
+ elsif definition[:type] == :list then
295
+ display_prefix = ""
296
+ display_prefix = "#{prefix}/" if prefix
297
+ xpath = field.to_s
298
+ xpath = "#{prefix}/#{xpath}" if prefix
299
+ solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
300
+ definition[:model_maxLength].times do |i|
301
+ 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]})
302
+ 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}))")
303
+ end
304
+ if (struct_varname)
305
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (= #{size_var_for_list(xpath)} 0)) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
306
+ end
307
+ translate_list_def_to_SMTLIB_constraints(progress, solver, definition, raw_field(xpath))
308
+ elsif definition[:type] == :structure then
309
+ xpath = field.to_s
310
+ xpath = "#{prefix}/#{xpath}" if prefix
311
+ solver.declare_const(progress, exists_var_for_structure(xpath), "Bool", {:xpath => xpath})
312
+ if (struct_varname)
313
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{exists_var_for_structure(xpath)})) :named empty-struct-has-no-substructure-#{raw_field(xpath)}))")
314
+ end
315
+ translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => xpath, :struct_varname => exists_var_for_structure(xpath), :options => options, :config => config)
316
+ else
317
+ next
318
+ end
319
+ end
320
+ end
321
+ end
322
+
323
+ def resolve_field_path(path, relative_to_struct, prefix)
324
+ current = relative_to_struct
325
+ result = nil
326
+ path.split("/").each do |segment|
327
+ unless current then
328
+ 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.")
329
+ end
330
+ field_def = current[segment.to_sym]
331
+ unless field_def then
332
+ 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}.")
333
+ end
334
+ if field_def[:type] == :structure then
335
+ unless field_def[:ref] then
336
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
337
+ end
338
+ current = field_def[:ref]
339
+ if prefix then
340
+ prefix = "#{prefix}/#{segment}"
341
+ else
342
+ prefix = segment
343
+ end
344
+ elsif field_def[:type] == :field then
345
+ current = nil
346
+ result = segment
347
+ if prefix
348
+ result = "#{prefix}/#{result}"
349
+ end
350
+ elsif field_def[:type] == :list then
351
+ current = nil
352
+ result = segment
353
+ if prefix
354
+ result = "#{prefix}/#{result}"
355
+ end
356
+ end
357
+ end
358
+ return result
359
+ end
360
+
361
+ def resolve_field_def(path, relative_to_struct, prefix)
362
+ current = relative_to_struct
363
+ result = nil
364
+ path.split("/").each do |segment|
365
+ unless current then
366
+ 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.")
367
+ end
368
+ field_def = current[segment.to_sym]
369
+ unless field_def then
370
+ 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}.")
371
+ end
372
+ if field_def[:type] == :structure then
373
+ unless field_def[:ref] then
374
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
375
+ end
376
+ current = field_def[:ref]
377
+ if prefix then
378
+ prefix = "#{prefix}/#{segment}"
379
+ else
380
+ prefix = segment
381
+ end
382
+ elsif field_def[:type] == :field then
383
+ current = nil
384
+ result = field_def
385
+ elsif field_def[:type] == :list then
386
+ current = nil
387
+ result = field_def
388
+ end
389
+ end
390
+ return result
391
+ end
392
+
393
+ def resolve_struct_path(path, relative_to_struct, prefix)
394
+ current = relative_to_struct
395
+ result = nil
396
+ path.split("/").each do |segment|
397
+ unless current then
398
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
399
+ end
400
+ if segment == "." then
401
+ result = current[:struct_xpath_prefix]
402
+ elsif segment == ".." then
403
+ current = current[:parent_link]
404
+ if prefix then
405
+ prefix_segs = prefix.split("/")
406
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
407
+ else
408
+ prefix = current[:struct_xpath_prefix]
409
+ end
410
+ else
411
+ field_def = current[segment.to_sym]
412
+ unless field_def then
413
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
414
+ end
415
+ if field_def[:type] == :structure then
416
+ current = field_def[:ref]
417
+ if prefix then
418
+ prefix = "#{prefix}/#{segment}"
419
+ else
420
+ prefix = segment
421
+ end
422
+ result = prefix
423
+ end
424
+ end
425
+ end
426
+ return result
427
+ end
428
+
429
+ def resolve_struct_def(path, relative_to_struct, prefix, context_desc = "")
430
+ current = relative_to_struct
431
+ result = nil
432
+ path.split("/").each do |segment|
433
+ unless current then
434
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
435
+ end
436
+ if segment == "." then
437
+ result = current
438
+ elsif segment == ".." then
439
+ current = current[:parent_link]
440
+ if prefix then
441
+ prefix_segs = prefix.split("/")
442
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
443
+ else
444
+ prefix = current[:struct_xpath_prefix]
445
+ end
446
+ else
447
+ field_def = current[segment.to_sym]
448
+ unless field_def then
449
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
450
+ end
451
+ if field_def[:type] == :structure then
452
+ current = field_def[:ref]
453
+ if prefix then
454
+ prefix = "#{prefix}/#{segment}"
455
+ else
456
+ prefix = segment
457
+ end
458
+ result = current
459
+ end
460
+ end
461
+ end
462
+ return result
463
+ end
464
+
465
+ def translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
466
+ solver.to_solver(progress, "; --- Validation Rules ---")
467
+ translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
468
+ struct.each_pair do |key, value|
469
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
470
+ if value[:type] == :structure then
471
+ if prefix then
472
+ new_prefix = "#{prefix.to_s}/#{key}"
473
+ else
474
+ new_prefix = key.to_s
475
+ end
476
+ translate_validation_rules_to_SMTLIB(progress, solver, value[:ref], new_prefix, config, options)
477
+ end
478
+ end
479
+ end
480
+
481
+ def translate_validation_rule_to_SMTLIB(rule, context_struct, prefix, config, options, rule_name)
482
+ date_now = options[:date_now]
483
+ timestamp_now = options[:timestamp_now]
484
+ return rule.gsub(/FILLED\[[^\]]*\]/) do |m|
485
+ unless m =~ /FILLED\[([^\]]*)\]/ then
486
+ raise RuntimeError.new("could not parse FILLED[...]: #{m.inspect} in validation rule #{rule_name}")
487
+ end
488
+ field = resolve_field_path($1, context_struct, prefix)
489
+ filled_var_for_field(field)
490
+ end
491
+ .gsub(/VALUE\[[^\]]*\]/) do |m|
492
+ unless m =~ /VALUE\[([^\]]*)\]/ then
493
+ raise RuntimeError.new("could not parse VALUE[...]: #{m.inspect} in validation rule #{rule_name}")
494
+ end
495
+ field = resolve_field_path($1, context_struct, prefix)
496
+ value_var_for_field(field)
497
+ end
498
+ .gsub(/EXIST\[[^\]]*\]/) do |m|
499
+ unless m =~ /EXIST\[([^\]]*)\]/ then
500
+ raise RuntimeError.new("could not parse EXIST[...]: #{m.inspect} in validation rule #{rule_name}")
501
+ end
502
+ s = resolve_struct_path($1, context_struct, prefix)
503
+ exists_var_for_structure(s)
504
+ end
505
+ .gsub(/SIZE\[[^\]]*\]/) do |m|
506
+ unless m =~ /SIZE\[([^\]]*)\]/ then
507
+ raise RuntimeError.new("could not parse SIZE[...]: #{m.inspect} in validation rule #{rule_name}")
508
+ end
509
+ l = resolve_field_path($1, context_struct, prefix)
510
+ size_var_for_list(l)
511
+ end
512
+ .gsub(/LIST_CONTAINS\[[^\]]*,[^\]]*\]/) do |m|
513
+ unless m =~ /LIST_CONTAINS\[([^\]]*),([^\]]*)\]/ then
514
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
515
+ end
516
+ l = resolve_field_path($1, context_struct, prefix)
517
+ l_def = resolve_field_def($1, context_struct, prefix)
518
+ value = $2
519
+ terms = []
520
+ l_def[:model_maxLength].times do |i|
521
+ terms << "(and (< #{i} #{size_var_for_list(l)}) (= #{value_var_for_list(l, i)} #{value}))"
522
+ end
523
+ "(or #{terms.join(" ")})"
524
+ end
525
+ .gsub(/LIST_VALUE_FIRST_ELEMENT\[[^\]]*\]/) do |m|
526
+ unless m =~ /LIST_VALUE_FIRST_ELEMENT\[([^\]]*)\]/ then
527
+ raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
528
+ end
529
+ xpath = $1
530
+ l = resolve_field_path(xpath, context_struct, prefix)
531
+ value_var_for_list(l, 0)
532
+ end
533
+ .gsub("[Date.now]", date_now.to_s)
534
+ .gsub("[Timestamp.now]", timestamp_now.to_s)
535
+ .gsub(/PREDICATE\[[^\]]*,[^\]]*\]/) do |m|
536
+ unless m =~ /PREDICATE\[([^\]]*),([^\]]*)\]/ then
537
+ raise RuntimeError.new("could not parse PREDICATE[...]: #{m.inspect} in validation rule #{rule_name}")
538
+ end
539
+ path = $1
540
+ key = $2.strip().to_sym
541
+ sdef = resolve_struct_def(path, context_struct, prefix, "in validation rule #{rule_name}")
542
+ unless sdef
543
+ raise RuntimeError.new("could not resolve struct path #{path.inspect} in validation rule #{rule_name}")
544
+ end
545
+ preds = sdef[:predicates]
546
+ mdk = preds[key]
547
+ if mdk then
548
+ if mdk[:smtlib] then
549
+ translate_validation_rule_to_SMTLIB(mdk[:smtlib], sdef, sdef[:struct_xpath_prefix], config, options, "#{rule_name}.predicate.#{key}[#{sdef[:struct_xpath_prefix]}]" )
550
+ else
551
+ 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}")
552
+ end
553
+ else
554
+ raise RuntimeError.new("No predicate #{key.inspect} defined for structure #{sdef[:struct_xpath_prefix]}, but one referenced in validation rule #{rule_name}")
555
+ end
556
+ end
557
+ .gsub(/CONFIG\[[^\]]*\]/) do |m|
558
+ unless m =~ /CONFIG\[([^\]]*)\]/ then
559
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
560
+ end
561
+ key = $1.to_sym
562
+ if config.has_key?(key) then
563
+ "\"#{config[key]}\""
564
+ else
565
+ raise RuntimeError.new("unknown CONFIG-key #{key.inspect} encountered in validation rule #{rule_name}" )
566
+ end
567
+ end
568
+ .gsub(/FIELD_IN_QUERY\[[^\]]*\]/) do |m|
569
+ unless m =~ /FIELD_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
570
+ raise RuntimeError.new("could not parse FIELD_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
571
+ end
572
+ xpath = $1
573
+ query_filename = $2
574
+ fpath = resolve_field_path(xpath, context_struct, prefix)
575
+ fdef = resolve_field_def(xpath, context_struct, prefix)
576
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
577
+ if fdef[:datatype] == :string then
578
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
579
+ "(= #{value_var_for_field(fpath)} \"#{val}\")"
580
+ end
581
+ elsif fdef[:datatype] == :int then
582
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
583
+ "(= #{value_var_for_field(fpath)} #{Integer(val)})"
584
+ end
585
+ else
586
+ 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.")
587
+ end
588
+ "(or #{terms.join(" ")})"
589
+ end
590
+ .gsub(/FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[[^\]]*\]/) do |m|
591
+ unless m =~ /FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[([^\]]*),([^\]]*),([^\]]*)\]/ then
592
+ raise RuntimeError.new("could not parse FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[..,..,..]: #{m.inspect} in validation rule #{rule_name}")
593
+ end
594
+ field_xpath = $1
595
+ param_smtlib = $2
596
+ query_filename = $3
597
+ fpath = resolve_field_path(field_xpath.strip(), context_struct, prefix)
598
+ fdef = resolve_field_def(field_xpath.strip(), context_struct, prefix)
599
+ query_result = YAML.load_file("#{PARAMETERIZED_QUERY_DIR}/#{query_filename.strip()}")
600
+ terms = []
601
+ if fdef[:datatype] == :string then
602
+ terms << "(or #{query_result.keys.map { |key| "(= #{param_smtlib} \"#{key}\")" }.join(" ")})"
603
+ query_result.each_pair do |key, values|
604
+ terms << "(=> (= #{param_smtlib} \"#{key}\") (or #{values.map{ |v| "(= #{value_var_for_field(fpath)} \"#{v}\")" }.join(" ")}))"
605
+ end
606
+ else
607
+ 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.")
608
+ end
609
+ "(and #{terms.join(" ")})"
610
+ end
611
+ .gsub(/CONFIG_IN_QUERY\[[^\]]*\]/) do |m|
612
+ unless m =~ /CONFIG_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
613
+ raise RuntimeError.new("could not parse CONFIG_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
614
+ end
615
+ key = $1.to_sym
616
+ unless config.has_key?(key) then
617
+ raise RuntimeError.new("unknown CONFIG key #{key.inspect} encountered in validation rule #{rule_name}")
618
+ end
619
+ query_filename = $2
620
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
621
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
622
+ "(= \"#{config[key]}\" \"#{val}\")"
623
+ end
624
+ "(or #{terms.join(" ")})"
625
+ end
626
+ end
627
+
628
+ def translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
629
+ return unless struct[:validation_rules]
630
+
631
+ solver.to_solver(progress, "; --- Validation Rules: ---")
632
+ negated_rules = options[:negated_validation_rules] || []
633
+ solver.to_solver(progress, "; negated rules: #{negated_rules.empty? ? "<none>" : negated_rules.join(", ")}")
634
+
635
+ rules = struct[:validation_rules]
636
+ rules.each do |rule_name, rule|
637
+ rule = rule[:smtlib] # ignore the text representation
638
+ next unless rule # warning about this omission is the job of validate_model()
639
+ rule_SMTLIB = translate_validation_rule_to_SMTLIB(rule, struct, prefix, config, options, rule_name)
640
+ if negated_rules.include?(rule_name) then
641
+ rule_SMTLIB = "(not #{rule_SMTLIB})"
642
+ end
643
+ solver.to_solver(progress, "(assert (! #{rule_SMTLIB} :named validation_rule_#{rule_name}_instance_#{prefix.gsub("/", "_")}))")
644
+ end
645
+ end
646
+
647
+ def parse_field_value(fielddef, value)
648
+ value = value.gsub("\n", "")
649
+ datatype = fielddef[:datatype]
650
+ case datatype
651
+ when :int
652
+ return Integer(value)
653
+ when :string
654
+ return value
655
+ when :enum
656
+ return value
657
+ when :date
658
+ return Date.parse(value)
659
+ when :timestamp
660
+ return Time.new(value)
661
+ end
662
+ end
663
+
664
+ def solver_value_to_string(solver_value)
665
+ if solver_value =~ /\"(.*)\"/ then
666
+ return $1
667
+ else
668
+ return solver_value
669
+ end
670
+ end
671
+
672
+ def build_document_from_model(progress, struct, model)
673
+ doc = Nokogiri::XML(XML_TEMPLATE)
674
+ add_model_struct_to_doc(progress, struct, doc, model)
675
+ return doc
676
+ end
677
+
678
+ def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
679
+ struct.each do |key, value|
680
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
681
+ if value[:type] == :field then
682
+ if prefix then
683
+ xpath = "#{prefix}/#{key}"
684
+ else
685
+ xpath = key.to_s
686
+ end
687
+ model_value = model[xpath]
688
+ unless model_value then
689
+ raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
690
+ end
691
+ if model_value[:filled] == true || model_value[:filled] == "true" then
692
+ doc.add_child("<#{key}>#{solver_value_to_string(model_value[:value])}</#{key}>")
693
+ end
694
+ elsif value[:type] == :list then
695
+ node = doc.add_child("<#{key}></#{key}>")
696
+ if node.is_a?(Nokogiri::XML::NodeSet) then
697
+ node = node.first
698
+ end
699
+ if prefix then
700
+ xpath = "#{prefix}/#{key}"
701
+ else
702
+ xpath = key.to_s
703
+ end
704
+ model_value = model[xpath]
705
+ xpath_element = value[:xpath_element]
706
+ if Integer(model_value[:size]) > 0 then
707
+ model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
708
+ node.add_child("<#{xpath_element}>#{solver_value_to_string(mvalue)}</#{xpath_element}>")
709
+ end
710
+ end
711
+ elsif value[:type] == :structure then
712
+ node = doc.add_child("<#{key}></#{key}>")
713
+ if node.is_a?(Nokogiri::XML::NodeSet) then
714
+ node = node.first
715
+ end
716
+ unless value[:ref] then
717
+ raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
718
+ end
719
+ if prefix then
720
+ new_prefix = "#{prefix}/#{key}"
721
+ else
722
+ new_prefix = key
723
+ end
724
+ add_model_struct_to_doc(progress, value[:ref], node, model, new_prefix)
725
+ else
726
+ raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
727
+ end
728
+ end
729
+ end
730
+
731
+ def collect_validation_rule_names(struct)
732
+ result = []
733
+ struct.each do |key, value|
734
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
735
+ if key == :validation_rules
736
+ value.each do |key, value|
737
+ result << key
738
+ end
739
+ else
740
+ if value[:type] == :structure
741
+ result += collect_validation_rule_names(value[:ref])
742
+ end
743
+ end
744
+ end
745
+ return result
746
+ end
747
+
748
+ def generate_doc(progress, message, output_filename, config, options)
749
+ log = options[:log] || nil
750
+ log.puts "generating doc #{output_filename}" if log
751
+ options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
752
+
753
+ z3path = config[:z3path] || DEFAULT_Z3_PATH
754
+ z3 = Z3Solver.new(progress, z3path)
755
+ begin
756
+ model = z3.query_model(progress, options) do |solver|
757
+ solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
758
+ solver.to_solver(progress, "")
759
+ log.puts "translating structure" if log
760
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
761
+ solver.to_solver(progress, "")
762
+ end
763
+ ensure
764
+ z3.close()
765
+ end
766
+
767
+ if model then
768
+ result_doc = build_document_from_model(progress, message, model)
769
+ dir = File.dirname(output_filename)
770
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
771
+ File.open(output_filename, "w") do |f|
772
+ f.puts result_doc.to_xml()
773
+ end
774
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
775
+ return true
776
+ else
777
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
778
+ return false
779
+ end
780
+ end
781
+
782
+ def z3ValueToRubyValue(value)
783
+ if value =~ /\"(.*)\"/ then # string
784
+ return $1
785
+ elsif value =~ /\d+/ then # int
786
+ return Integer(value)
787
+ elsif value == "true"
788
+ return true
789
+ elsif value == "false" then
790
+ return false
791
+ else
792
+ raise RuntimeError.new("enountered unknown type of value in z3 model: #{value.inspect}")
793
+ end
794
+ end
795
+
796
+ def extract_key_field_values_from_model(key_fields, model)
797
+ result = {}
798
+ key_fields.each do |xpath, fieldDesc|
799
+ fieldInfo = model[xpath]
800
+ if fieldDesc[:type] == :field then
801
+ if fieldInfo[:filled] then
802
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:value])
803
+ else
804
+ result[xpath] = nil
805
+ end
806
+ elsif fieldDesc[:type] == :struct || fieldDesc[:type] == :structure then
807
+ if fieldInfo.has_key?(:exists) then
808
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:exists])
809
+ else
810
+ result[xpath] = false
811
+ end
812
+ else
813
+ raise RuntimeError.new("encountered unknown :type in solver model: #{fieldDesc[:type].inspect}")
814
+ end
815
+ end
816
+ return result
817
+ end
818
+
819
+ def all_values_for_field(field)
820
+ unless field[:type] == :field then
821
+ raise RuntimeError.new("something that is not a field definition was passed to all_values_for_field(): #{field.inspect}")
822
+ end
823
+ unless field[:datatype] == :enum then
824
+ raise RuntimeError.new("all_values_for_field() is only applicable to fields of datatype enum! not #{field[:datatype].inspect}.")
825
+ end
826
+ values = field[:values].dup
827
+ if field[:optional] == true then
828
+ values << nil
829
+ end
830
+ return values
831
+ end
832
+
833
+ def each_combination(fields, &block)
834
+ if fields.size < 1 then
835
+ return
836
+ end
837
+ if fields.size == 1 then
838
+ field = fields.first
839
+ values = field[1][:values].clone()
840
+ if field[1][:optional] == true then
841
+ values << nil
842
+ end
843
+ values.each do |value|
844
+ block.call({ field[0] => value })
845
+ end
846
+ else
847
+ first = fields.shift
848
+ values = first[1][:values].clone()
849
+ if first[1][:optional] == true then
850
+ values << nil
851
+ end
852
+ each_combination(fields) do |mapping|
853
+ values.each do |value|
854
+ block.call(mapping.merge({ first[0] => value }))
855
+ end
856
+ end
857
+ end
858
+ end
859
+
860
+ def calc_combinations_for_key_fields(key_fields)
861
+ count = 1
862
+ key_fields.each do |field|
863
+ if field[1][:type] == :field then
864
+ if field[1][:datatype] == :enum then
865
+ num_values = field[1][:values].size
866
+ num_values += 1 if field[1][:optional]
867
+ elsif field[1][:datatype] == :int then
868
+ if field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
869
+ if field[1][:optional] then
870
+ num_values = 2 + field[1][:max] - field[1][:min]
871
+ else
872
+ num_values = 1 + field[1][:max] - field[1][:min]
873
+ end
874
+ else
875
+ return "potentially infinite"
876
+ end
877
+ elsif field[1][:datatype] == :string then
878
+ return "potentially infinite"
879
+ end
880
+ elsif field[1][:type] == :structure then
881
+ num_values = 2
882
+ end
883
+ count *= num_values
884
+ end
885
+ return count
886
+ end
887
+
888
+ def calc_combinations(message, key)
889
+ key_fields = collect_key_fields(key, message)
890
+ return calc_combinations_for_key_fields(key_fields)
891
+ end
892
+
893
+ def read_docs_from_log_file(log_filename)
894
+ result = []
895
+ if File.exist?(log_filename) then
896
+ File.open(log_filename, "r") do |f|
897
+ f.each_line do |line|
898
+ doc = {}
899
+ if line =~ /(.*)\ <-\ \{(.*)\}/ then
900
+ filename = $1
901
+ combination = $2
902
+ combination.split(", ").each do |assignment|
903
+ if assignment =~ /(.*):\ (.*)/ then
904
+ key = $1
905
+ value = $2
906
+ if value =~ /\"(.*)\"/ then
907
+ value = $1
908
+ elsif value == "nil" then
909
+ value = nil
910
+ end
911
+ doc[key] = value
912
+ else
913
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid assignment #{assignment.inspect}")
914
+ end
915
+ end
916
+ else
917
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid line #{line.inspect}")
918
+ end
919
+ result << doc
920
+ end
921
+ end
922
+ end
923
+ return result
924
+ end
925
+
926
+ def blocking_clause(docs, vars = docs.map{|e| e.keys}.flatten)
927
+ unless docs.empty?
928
+ codes = docs.map do |key_values|
929
+ value_codes = key_values.filter{|xpath,value| vars.include?(xpath) }.map do |xpath, value|
930
+ if value == nil then
931
+ "(= #{filled_var_for_field(xpath)} false)"
932
+ else
933
+ "(and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} \"#{value}\"))"
934
+ end
935
+ end
936
+ if value_codes.empty? then
937
+ "true"
938
+ else
939
+ "(not (and #{value_codes.join(" ")}))"
940
+ end
941
+ end
942
+ return "" if codes.empty?
943
+ return "(assert (! (and #{codes.join(" ")}) :named blocking-clause))"
944
+ end
945
+ return nil
946
+ end
947
+
948
+ #def fixing_clause(key_values, fixed = key_value.keys)
949
+ # codes = []
950
+ # key_values.each do |xpath, value|
951
+ # if fixed.include?(xpath) then
952
+ # if value then
953
+ # codes << "(and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} \"#{value}\"))"
954
+ # else
955
+ # codes << "(= #{filled_var_for_field(xpath)} false)"
956
+ # end
957
+ # end
958
+ # end
959
+ # if codes.empty? then
960
+ # return ""
961
+ # else
962
+ # return "(assert (! (and #{codes.join(" ")}) :named fixing-clause))"
963
+ # end
964
+ #end
965
+
966
+ def prepare_output_doc_filename(number, output_dir)
967
+ chunk_number = number / 100000
968
+ dirname = "#{output_dir}/#{chunk_number}"
969
+ unless Dir.exist?(dirname) then
970
+ FileUtils.mkdir_p(dirname)
971
+ end
972
+ return "#{dirname}/doc#{number}.xml"
973
+ end
974
+
975
+ def switch_blocking_clause(fixed, current_value, var_xpath, blocked_values)
976
+ clauses = []
977
+ blocked_values.each do |value|
978
+ if value != nil then
979
+ clauses << "(not (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} \"#{value}\")))"
980
+ else
981
+ clauses << "(not (= #{filled_var_for_field(var_xpath)} false))"
982
+ end
983
+ end
984
+ if fixed then
985
+ if current_value != nil then
986
+ result = "; switch fixing clause\n(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} \"#{current_value}\")) :named switch_fixing_clause))"
987
+ else
988
+ result = "; switch fixing clause\n(assert (! (= #{filled_var_for_field(var_xpath)} false) :named switch_fixing_clause))"
989
+ end
990
+ else
991
+ result = ""
992
+ end
993
+ unless clauses.empty? then
994
+ result = result + "\n; switch blocking clause\n(assert (! (and #{clauses.join(" ")}) :named switch_blocking_clause))"
995
+ end
996
+ return result
997
+ end
998
+
999
+ def values_for_key_field(field)
1000
+ if field[:type] == :field then
1001
+ if field[:datatype] == :enum then
1002
+ if field[:optional] then
1003
+ return field[:values] + [nil]
1004
+ else
1005
+ return field[:values]
1006
+ end
1007
+ elsif field[:datatype] == :int then
1008
+ if field.has_key?(:min) and field.has_key?(:max) and field[:min] <= field[:max] then
1009
+ return (field[:min]..field[:max]).to_a
1010
+ else
1011
+ raise RuntimeError.new("cannot determine values for unbounded :int field")
1012
+ end
1013
+ else
1014
+ raise RuntimeError.new("cannot determine values for datatype #{field[:datatype].inspect}")
1015
+ end
1016
+ elsif field[:type] == :structure then
1017
+ if field[:optional] then
1018
+ return [true, false]
1019
+ else
1020
+ return [true]
1021
+ end
1022
+ else
1023
+ raise RuntimeError.new("Encountered unknown :type in model: #{field[:type].inspect} - keys can only mark fields or structures.")
1024
+ end
1025
+ end
1026
+
1027
+ def fixing_clause(xpath, switched_info)
1028
+ if switched_info[:current_value] == nil then
1029
+ return "(assert (! (= #{filled_var_for_field(xpath)} false) :named fixing-clause-#{raw_field(xpath)}))"
1030
+ else
1031
+ return "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} \"#{switched_info[:current_value]}\")) :named fixing-clause-#{raw_field(xpath)}))"
1032
+ end
1033
+ end
1034
+
1035
+ def collect_key_fields(fkey, struct, prefix = nil)
1036
+ result = {}
1037
+ struct.each do |key, value|
1038
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1039
+ if value[:type] == :structure
1040
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1041
+ xpath = key.to_s
1042
+ xpath = "#{prefix}/#{xpath}" if prefix
1043
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values].include?(k)}
1044
+ end
1045
+ if prefix then
1046
+ new_prefix = "#{prefix}/#{key}"
1047
+ else
1048
+ new_prefix = key
1049
+ end
1050
+ result = result.merge(collect_key_fields(fkey, value[:ref], new_prefix))
1051
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1052
+ xpath = key.to_s
1053
+ xpath = "#{prefix}/#{xpath}" if prefix
1054
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values, :min, :max].include?(k)}
1055
+ end
1056
+ end
1057
+ return result
1058
+ end
1059
+
1060
+ def collect_key_fields_and_strctures(fkey, struct, prefix = nil)
1061
+ fields = []
1062
+ structures = []
1063
+ struct.each do |key, value|
1064
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1065
+ if value[:type] == :structure
1066
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1067
+ xpath = key.to_s
1068
+ xpath = "#{prefix}/#{xpath}" if prefix
1069
+ structures << xpath
1070
+ end
1071
+ if prefix then
1072
+ new_prefix = "#{prefix}/#{key}"
1073
+ else
1074
+ new_prefix = key
1075
+ end
1076
+ result_fields, result_structures = collect_key_fields_and_strctures(fkey, value[:ref], new_prefix)
1077
+ fields += result_fields
1078
+ structures += result_structures
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
+ fields << xpath
1083
+ end
1084
+ end
1085
+ return fields, structures
1086
+ end
1087
+
1088
+ def collect_keys(struct)
1089
+ result = []
1090
+ struct.each do |key, value|
1091
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1092
+ if value[:type] == :structure
1093
+ if value.has_key?(:keys) then
1094
+ result += value[:keys]
1095
+ end
1096
+ if value[:ref] then
1097
+ result += collect_keys(value[:ref])
1098
+ else
1099
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1100
+ end
1101
+ elsif value[:type] == :field and value.has_key?(:keys) then
1102
+ result += value[:keys]
1103
+ end
1104
+ end
1105
+ result.uniq
1106
+ end
1107
+
1108
+ def collect_validation_rules(struct)
1109
+ result = []
1110
+ struct.each do |key, value|
1111
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1112
+ if key == :validation_rules then
1113
+ result += value.keys
1114
+ else
1115
+ if value[:type] == :structure
1116
+ if value[:ref] then
1117
+ result += collect_validation_rules(value[:ref])
1118
+ else
1119
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1120
+ end
1121
+ end
1122
+ end
1123
+ end
1124
+ result.uniq
1125
+ end
1126
+
1127
+ def list_keys(message)
1128
+ keys = collect_keys(message)
1129
+ puts "keys: #{keys.join(", ")}"
1130
+ end
1131
+
1132
+ def list_fields_and_structures(message, fkey)
1133
+ fields, structures = collect_key_fields_and_strctures(fkey, message)
1134
+ puts "fields for key #{fkey}:"
1135
+ fields.each { |f| puts " #{f}" }
1136
+ puts "structures for key #{fkey}:"
1137
+ structures.each { |s| puts " #{s}" }
1138
+ end
1139
+
1140
+ def list_validation_rules(message)
1141
+ rules = collect_validation_rules(message)
1142
+ puts "Validation Rules:"
1143
+ rules.each { |r| puts " #{r}" }
1144
+ end
1145
+
1146
+ class CoreEnumerationAlgo
1147
+
1148
+ def initialize()
1149
+ @num_tries = 0
1150
+ @num_docs = 0
1151
+ @on_new_doc = nil
1152
+ @on_unsat = nil
1153
+ end
1154
+
1155
+
1156
+ # fix values of all variables left from switched
1157
+ def enumerate_fixing_clause(assignment, vars, switched)
1158
+ clauses = []
1159
+ switched.times do |index|
1160
+ var_xpath = vars[index][0]
1161
+ var_def = vars[index][1]
1162
+ if var_def[:type] == :field then
1163
+ if assignment[var_xpath] == nil then
1164
+ clauses << "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named fixing_clause_#{index}))"
1165
+ else
1166
+ 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}))"
1167
+ end
1168
+ elsif var_def[:type] == :structure then
1169
+ clauses << "(assert (! (= #{exists_var_for_structure(var_xpath)} #{assignment[var_xpath].inspect}) :named fixing_clause_#{index}))"
1170
+ end
1171
+ end
1172
+ "; fixing clauses:\n#{clauses.join("\n")}"
1173
+ end
1174
+
1175
+ def summarize_fixing_clause(assignment, vars, switched)
1176
+ fixed = switched.times.to_a.map do |index|
1177
+ var_xpath = vars[index][0]
1178
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1179
+ end
1180
+ "fixed(#{fixed.join(", ")})"
1181
+ end
1182
+
1183
+ def value_of_type(value, type)
1184
+ if type == :string or type == :enum then
1185
+ "\"#{value}\""
1186
+ elsif type == :int then
1187
+ value.to_s
1188
+ end
1189
+ end
1190
+
1191
+ # prevent the var from taking values that already have been enumerated exhaustively
1192
+ def enumerate_blocking_clause(var_xpath, var_def, blocked)
1193
+ clauses = []
1194
+ num = 0
1195
+ if var_def[:type] == :field then
1196
+ blocked.each do |value|
1197
+ if value == nil then
1198
+ clauses << "(assert (! (not (= #{filled_var_for_field(var_xpath)} false)) :named blocking_clause_#{num}))"
1199
+ else
1200
+ 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}))"
1201
+ end
1202
+ num += 1
1203
+ end
1204
+ elsif var_def[:type] == :structure then
1205
+ blocked.each do |value|
1206
+ clauses << "(assert (! (not (= #{exists_var_for_structure(var_xpath)} #{value.inspect})) :named blocking_clause_#{num}))"
1207
+ num += 1
1208
+ end
1209
+ end
1210
+ "; blocking clauses:\n#{clauses.join("\n")}"
1211
+ end
1212
+
1213
+ def summarize_blocking_clause(var_xpath, blocked)
1214
+ "blocked(#{var_xpath})=[#{blocked.map{|v| v.inspect}.join(", ")}]"
1215
+ end
1216
+
1217
+ def generate_sparse_doc(progress, message, key, config, options, code, output_doc_filename)
1218
+ additional_options = {}
1219
+ if code then
1220
+ additional_options = { :add_SMTLIB => code }
1221
+ end
1222
+ return generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options.merge(additional_options))
1223
+ end
1224
+
1225
+ def log_filename_for_output_filename(output_filename)
1226
+ "#{output_filename}.smt2"
1227
+ end
1228
+
1229
+ def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
1230
+ log = options[:log] || nil
1231
+ log.puts "generating doc #{output_filename}" if log
1232
+ options[:solverLog] = log_filename_for_output_filename(output_filename)
1233
+
1234
+ key_fields = collect_key_fields(key, message)
1235
+
1236
+ z3path = config[:z3path] || DEFAULT_Z3_PATH
1237
+ z3 = Z3Solver.new(progress, z3path)
1238
+ begin
1239
+ model = z3.query_model(progress, options) do |solver|
1240
+ solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
1241
+ solver.to_solver(progress, "")
1242
+ log.puts "translating structure" if log
1243
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
1244
+ solver.to_solver(progress, "")
1245
+ end
1246
+ ensure
1247
+ z3.close()
1248
+ end
1249
+
1250
+ if model then
1251
+ result_doc = build_document_from_model(progress, message, model)
1252
+ dir = File.dirname(output_filename)
1253
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1254
+ File.open(output_filename, "w") do |f|
1255
+ f.puts result_doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
1256
+ end
1257
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
1258
+ return extract_key_field_values_from_model(key_fields, model)
1259
+ else
1260
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
1261
+ return nil
1262
+ end
1263
+ end
1264
+
1265
+ def store_continuation_point(doc_number, state, assignment, new_assignment, filename)
1266
+ File.open(filename, "w") do |f|
1267
+ f.puts("#{doc_number};#{@num_docs};state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")});assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")});new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})")
1268
+ end
1269
+ end
1270
+
1271
+ def self.parse_state(state_str)
1272
+ unless state_str =~ /state\((.*)\)/
1273
+ return nil
1274
+ end
1275
+ split = $1.split("#")
1276
+ split.map do |e|
1277
+ if e == "nil" then
1278
+ nil
1279
+ elsif e =~ /\[(.*)\]/ then
1280
+ $1.split(",").map do |i|
1281
+ if i.strip == "nil" then
1282
+ nil
1283
+ elsif i.strip == "true" then
1284
+ true
1285
+ elsif i.strip == "false" then
1286
+ false
1287
+ elsif i.strip =~ /\"(.*)\"/ then
1288
+ $1
1289
+ end
1290
+ end
1291
+ end
1292
+ end
1293
+ end
1294
+
1295
+ def self.parse_assignment(str)
1296
+ unless str =~ /assignment\((.*)\)/
1297
+ return nil
1298
+ end
1299
+ split = $1.split(",")
1300
+ result = {}
1301
+ split.each do |e|
1302
+ s2 = e.split(":")
1303
+ key = s2[0]
1304
+ value = s2[1]
1305
+ if value == "nil" then
1306
+ result[key] = nil
1307
+ elsif value == "true" then
1308
+ result[key] = true
1309
+ elsif value == "false" then
1310
+ result[key] = false
1311
+ elsif value =~ /\"(.*)\"/
1312
+ result[key] = $1
1313
+ end
1314
+ end
1315
+ result
1316
+ end
1317
+
1318
+ def self.parse_new_assignment(str)
1319
+ unless str =~ /new_assignment\((.*)\)/
1320
+ return nil
1321
+ end
1322
+ split = $1.split(",")
1323
+ result = {}
1324
+ split.each do |e|
1325
+ s2 = e.split(":")
1326
+ key = s2[0]
1327
+ value = s2[1]
1328
+ if value == "nil" then
1329
+ result[key] = nil
1330
+ elsif value == "true" then
1331
+ result[key] = true
1332
+ elsif value == "false" then
1333
+ result[key] = false
1334
+ elsif value =~ /\"(.*)\"/
1335
+ result[key] = $1
1336
+ end
1337
+ end
1338
+ result
1339
+ end
1340
+
1341
+ def self.parse_continuation_point(line)
1342
+ split = line.split(";")
1343
+ doc_number = Integer(split[0])
1344
+ num_docs = Integer(split[1])
1345
+ state = CoreEnumerationAlgo.parse_state(split[2])
1346
+ assignment = CoreEnumerationAlgo.parse_assignment(split[3])
1347
+ new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
1348
+ return [doc_number, num_docs, state, assignment, new_assignment]
1349
+ end
1350
+
1351
+ # TODO: logging
1352
+
1353
+ def display_combination_space(assignment, vars, switched, blocked)
1354
+ # enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1355
+
1356
+ # => all vars left of switched are fixed to their assigned values (when the value is nil, then they are empty)
1357
+ # => the switched var is prevented to have any of the blocked values
1358
+ res = switched.times.to_a.map do |index|
1359
+ var_xpath = vars[index][0]
1360
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1361
+ end + ["#{vars[switched][0]}|#{blocked.map{|v| v.inspect}.join(",")}"]
1362
+ "{#{res.join(", ")}}"
1363
+ end
1364
+
1365
+ # core algo
1366
+ def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size())
1367
+ enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state) if switched < vars.size()-1
1368
+ is_finite_state = keys_are_finite_state(vars)
1369
+ if state[switched] == nil then
1370
+ blocked = [assignment[vars[switched][0]]]
1371
+ state[switched] = blocked
1372
+ else
1373
+ blocked = state[switched] + [assignment[vars[switched][0]]]
1374
+ state[switched] = blocked
1375
+ end
1376
+ found = true
1377
+ while found do
1378
+ found = false
1379
+ # - fix all vars left of switched to their values in assignment
1380
+ # - block switched from obtaining the values in blocked
1381
+ # progress.print_line "DEBUG: #{summarize_fixing_clause(assignment, vars, switched)}, #{summarize_blocking_clause(vars[switched][0], blocked)}"
1382
+ clauses = enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1383
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1384
+ new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
1385
+ generated_doc_number = @num_tries
1386
+ store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}")
1387
+ if is_finite_state then
1388
+ progress.report_progress(determine_progress(state, vars))
1389
+ else
1390
+ progress.report_progress(nil)
1391
+ end
1392
+ @num_tries += 1
1393
+ if new_assignment then # sat
1394
+ @num_docs += 1
1395
+ @on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
1396
+ if switched < vars.size()-1 then
1397
+ enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state)
1398
+ end
1399
+ blocked << new_assignment[vars[switched][0]]
1400
+ state[switched] = blocked
1401
+ found = true
1402
+ else # unsat
1403
+ @on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked)) if @on_unsat
1404
+ found = false
1405
+ end
1406
+ end
1407
+ state[switched] = nil
1408
+ end
1409
+
1410
+ # NOTE: can only be called when all key-fields are finite-state
1411
+ def is_final_state(state, new_assignment, key_fields_list)
1412
+ return state[1,state.size()] == [nil] * (state.size()-1) &&
1413
+ state[0].to_set == values_for_key_field(key_fields_list[0][1]).to_set &&
1414
+ new_assignment.empty?
1415
+ end
1416
+
1417
+ # NOTE: can only be called when all key-fields are finite-state
1418
+ def determine_progress(state, vars)
1419
+ combinations_per_value = [nil] * vars.size()
1420
+ comb = 1
1421
+ vars.size().times.to_a.reverse_each do |i|
1422
+ combinations_per_value[i] = comb
1423
+ comb = values_for_key_field(vars[i][1]).size() * comb
1424
+ end
1425
+
1426
+ blocked_combinations = 0
1427
+ state.size().times do |i|
1428
+ if state[i] then
1429
+ blocked_combinations += state[i].size() * combinations_per_value[i]
1430
+ end
1431
+ end
1432
+ blocked_combinations
1433
+ end
1434
+
1435
+ def num_combinations(key_def)
1436
+ if key_def[:type] == :field then
1437
+ if key_def[:datatype] == :enum then
1438
+ key_def[:values].size() + (key_def[:optional] ? 1 : 0)
1439
+ elsif key_def[:datatype] == :int then
1440
+ if key_def.has_key?(:min) and key_def.has_key?(:max) and key_def[:min] <= key_def[:max] then
1441
+ 1 + key_def[:max] - key_def[:min] + (key_def[:optional] ? 1 : 0)
1442
+ else
1443
+ nil
1444
+ end
1445
+ else
1446
+ nil
1447
+ end
1448
+ elsif key_def[:type] == :structure then
1449
+ 2
1450
+ end
1451
+ end
1452
+
1453
+ def keys_are_finite_state(list)
1454
+ list.each do |field|
1455
+ name = field[0]
1456
+ if field[1][:type] == :field then
1457
+ if field[1][:datatype] == :enum then
1458
+ # enums are finite-state
1459
+ 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
1460
+ # ints are finite-state when both bounds are given and :min <= :max
1461
+ else
1462
+ return false
1463
+ end
1464
+ end
1465
+ end
1466
+ return true
1467
+ end
1468
+
1469
+ def generate_docs_for_key(message, key, output_dir, config, options)
1470
+
1471
+ number_of_combinations = calc_combinations(message, key)
1472
+ if number_of_combinations.is_a?(String) then
1473
+ progress = ProgressBar.new(100000000000000)
1474
+ else
1475
+ progress = ProgressBar.new(number_of_combinations)
1476
+ end
1477
+ begin
1478
+ ensure
1479
+ end
1480
+ progress.report_progress(0.0)
1481
+ progress.print_line "generating sparse documents efficiently..."
1482
+ progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
1483
+ progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
1484
+
1485
+ progress.print_line "preparing output dir #{output_dir}..."
1486
+ if options[:continue] then
1487
+ unless Dir.exist?(output_dir) then
1488
+ raise FatalError.new("FATAL: directory #{output_dir} not found. There seems to be nothing to continue from...")
1489
+ end
1490
+ log_mode = "a"
1491
+ else
1492
+ unless Dir.exist?(output_dir) then
1493
+ FileUtils.mkdir_p(output_dir)
1494
+ end
1495
+ log_mode = "w"
1496
+ end
1497
+ log_filename = "#{output_dir}/#{COMBINATION_LOG}"
1498
+
1499
+ key_fields = collect_key_fields(key, message)
1500
+ key_fields_list = key_fields.each_pair.to_a
1501
+ key_fields_list.sort! {|a,b| num_combinations(b[1]) <=> num_combinations(a[1]) }
1502
+
1503
+ if options[:continue] then
1504
+ stored_key_fields_str = File.read("#{output_dir}/#{VARS_LOG}")
1505
+ unless key_fields_list.to_yaml == stored_key_fields_str then
1506
+ raise FatalError.new("vars are differing! please specify the same key in order to use -continue!")
1507
+ end
1508
+ progress.print_line "continuing enumerating documents for key #{key}!"
1509
+ else
1510
+ # store vars
1511
+ File.open("#{output_dir}/#{VARS_LOG}", "w") do |f|
1512
+ f.puts(key_fields_list.to_yaml)
1513
+ end
1514
+ end
1515
+
1516
+ #progress.print_line "DEBUG: key_fields_list=#{key_fields_list.inspect}"
1517
+
1518
+ cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
1519
+ if options[:continue] then
1520
+ unless File.exist?(cont_filename) then
1521
+ raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
1522
+ end
1523
+ doc_number, num_docs, state, assignment, new_assignment = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
1524
+ if is_final_state(state, new_assignment, key_fields_list) then
1525
+ progress.report_progress(number_of_combinations)
1526
+ raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
1527
+ end
1528
+ progress.print_line "RESUMING enumeration from the following continuation point:"
1529
+ progress.print_line "|doc_number=#{doc_number}"
1530
+ progress.print_line "|num_docs=#{num_docs}"
1531
+ progress.print_line "|state=#{state.inspect}"
1532
+ progress.print_line "|assignment=#{assignment.inspect}"
1533
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1534
+ if keys_are_finite_state(key_fields_list) then
1535
+ progress.report_progress(determine_progress(state, key_fields_list))
1536
+ else
1537
+ progress.report_progress(nil)
1538
+ end
1539
+
1540
+ if new_assignment && !new_assignment.empty? then
1541
+ assignment = new_assignment
1542
+ end
1543
+
1544
+ # restore state at continuation point
1545
+ @num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
1546
+ number_of_combinations += 1 # account for this in the safety mechanism
1547
+ @num_docs = num_docs
1548
+ doc_counter = 0
1549
+ File.open(log_filename, log_mode) do |log|
1550
+
1551
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1552
+ # combination logging
1553
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}: #{v.inspect}"}.join(", ")}}"
1554
+
1555
+ doc_counter += 1
1556
+ if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
1557
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1558
+ end
1559
+
1560
+ # safety mechanism
1561
+ if @num_docs > number_of_combinations then
1562
+ 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.")
1563
+ end
1564
+ }
1565
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str|
1566
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1567
+ progress.print_line "doc#{num_doc} NOT generated"
1568
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching"
1569
+ }
1570
+
1571
+ # core algo - resume from state
1572
+ enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state)
1573
+ end
1574
+ else
1575
+ # init safety mechanism
1576
+ @num_tries = 0
1577
+ @num_docs = 0
1578
+ doc_counter = 0
1579
+ # first try - a freely generated document
1580
+ File.open(log_filename, log_mode) do |log|
1581
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1582
+ key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
1583
+ log.puts "#{output_doc_filename} <- {#{key_values.map{|k,v| "#{k}: #{v.inspect}"}.join(", ")}}"
1584
+ doc_counter += 1
1585
+ @num_tries += 1
1586
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1587
+ # combination logging
1588
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}: #{v.inspect}"}.join(", ")}}"
1589
+
1590
+ doc_counter += 1
1591
+ if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
1592
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1593
+ end
1594
+
1595
+ # safety mechanism
1596
+ unless number_of_combinations.is_a?(String) then
1597
+ if @num_docs > number_of_combinations then
1598
+ 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.")
1599
+ end
1600
+ end
1601
+ }
1602
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str|
1603
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1604
+ progress.print_line "doc#{num_doc} NOT generated"
1605
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching"
1606
+ }
1607
+
1608
+ # core algo - start enumeration
1609
+ enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options)
1610
+ end
1611
+
1612
+ progress.print_line "No more valid documents could be found. -> Exiting"
1613
+ progress.report_progress(number_of_combinations)
1614
+ end
1615
+ end
1616
+ end
1617
+
1618
+ def introduce_parent_links(message, parent = nil, prefix = nil)
1619
+ if parent != nil then
1620
+ message[:parent_link] = parent
1621
+ end
1622
+ if prefix != nil then
1623
+ message[:struct_xpath_prefix] = prefix
1624
+ end
1625
+ message.each_pair do |key, value|
1626
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1627
+ if value[:type] == :structure then
1628
+ if prefix then
1629
+ new_prefix = "#{prefix}/#{key}"
1630
+ else
1631
+ new_prefix = key
1632
+ end
1633
+ introduce_parent_links(value[:ref], message, new_prefix)
1634
+ end
1635
+ end
1636
+ end
1637
+
1638
+ def validate_model(message, prefix=nil)
1639
+
1640
+ message.each_pair do |key, value|
1641
+ if key == :validation_rules then
1642
+ value.each_pair do |vr_key, vr_value|
1643
+ unless vr_value[:text] && vr_value[:text] != "" then
1644
+ puts "INFO: Model Problem: validation rule #{vr_key} does not have a textual representation."
1645
+ end
1646
+ unless vr_value[:smtlib] && vr_value[:smtlib] != "" then
1647
+ puts "WARN: Model Problem: validation rule #{vr_key} does not have an SMTLIB representation. It will hence be omitted!"
1648
+ end
1649
+ end
1650
+ elsif key == :predicates then
1651
+ value.each_pair do |mkey, mvalue|
1652
+ next unless mvalue
1653
+ unless mvalue[:text] && mvalue[:text] != "" then
1654
+ puts "INFO: Model Problem: structure #{prefix}'s predicate #{mkey} does not have a textual representation."
1655
+ end
1656
+ unless mvalue[:smtlib] && mvalue[:smtlib] != "" then
1657
+ puts "WARN: Model Problem: structure #{prefix}'s predicate #{mkey} does not have an SMTLIB representation. It will be regarded as always true."
1658
+ end
1659
+ end
1660
+ elsif key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1661
+ next
1662
+ else
1663
+ if value[:type] == :structure then
1664
+ unless value[:ref] then
1665
+ raise RuntimeError.new("Model Problem: encountered structure without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1666
+ end
1667
+ if prefix then
1668
+ new_prefix = "#{prefix}/#{key}"
1669
+ else
1670
+ new_prefix = key
1671
+ end
1672
+ validate_model(value[:ref], new_prefix)
1673
+ elsif value[:type] == :field then
1674
+ unless value[:datatype] then
1675
+ raise RuntimeError.new("Model Problem: encountered field without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1676
+ end
1677
+ if value[:datatype] == :enum then
1678
+ unless value[:values] then
1679
+ raise RuntimeError.new("Model Problem: encountered enum field without a :values (key=#{key.inspect}, value=#{value.inspect})")
1680
+ end
1681
+ end
1682
+ end
1683
+ end
1684
+ end
1685
+ end
1686
+
1687
+ def generate(message, argv, message_config_filename = nil)
1688
+ if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
1689
+ puts USAGE
1690
+ exit(0)
1691
+ end
1692
+
1693
+ options = { :negated_validation_rules => [], :date_now => (Date.today - Time.at(0).to_date).to_i, :timestamp_now => Time.now().to_i }
1694
+
1695
+ opt = argv.shift
1696
+ while opt do
1697
+ if opt == "-negate" then
1698
+ rule = argv.shift
1699
+ options[:negated_validation_rules] << rule
1700
+ elsif opt == "-list-keys" then
1701
+ options[:list_keys] = true
1702
+ elsif opt == "-list" then
1703
+ options[:list] = argv.shift
1704
+ elsif opt == "-list-validation-rules" then
1705
+ options[:list_validation_rules] = true
1706
+ elsif opt == "-generate-docs-from" then
1707
+ options[:combination_source] = argv.shift
1708
+ elsif opt == "-skip" then
1709
+ options[:skip] = Integer(argv.shift)
1710
+ elsif opt == "-generate-docs-for-key" then
1711
+ options[:docs_for_key] = argv.shift
1712
+ elsif opt == "-count-docs-for-key" then
1713
+ options[:count_docs_for_key] = argv.shift
1714
+ elsif opt == "-continue" then
1715
+ options[:continue] = true
1716
+ elsif opt == "-max-num-docs" then
1717
+ options[:max_num_docs] = Integer(argv.shift)
1718
+ puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
1719
+ else
1720
+ puts "FATAL: unknown option #{opt.inspect}."
1721
+ puts USAGE
1722
+ raise FatalError.new("")
1723
+ end
1724
+ opt = argv.shift
1725
+ end
1726
+
1727
+ unless options[:negated_validation_rules].empty? then
1728
+ puts "negating rules: #{options[:negated_validation_rules].join(", ")}"
1729
+
1730
+ rules = collect_validation_rule_names(message)
1731
+ unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
1732
+ unless unknown_negated_rules.empty? then
1733
+ throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
1734
+ end
1735
+
1736
+ end
1737
+
1738
+ if File.exist?(CONFIG_FILE) then
1739
+ config = YAML.load_file(CONFIG_FILE)
1740
+ else
1741
+ config = DEFAULT_CONFIG
1742
+ File.open(CONFIG_FILE, "w") do |f|
1743
+ f.puts(config.to_yaml)
1744
+ end
1745
+ puts "No config file found. Default config written to #{CONFIG_FILE}"
1746
+ end
1747
+ if message_config_filename then
1748
+ message_config = YAML.load_file(message_config_filename)
1749
+ config = config.merge(message_config)
1750
+ end
1751
+
1752
+ introduce_parent_links(message)
1753
+
1754
+ if options[:count_docs_for_key] then
1755
+ puts "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
1756
+ elsif options[:docs_for_key] then
1757
+ puts "using #{options[:date_now]} as [Date.now]"
1758
+ puts "using #{options[:timestamp_now]} as [Timestamp.now]"
1759
+ validate_model(message)
1760
+ CoreEnumerationAlgo.new().generate_docs_for_key(message, options[:docs_for_key], OUTPUT_DIR, config, options)
1761
+ elsif options[:list_keys] then
1762
+ list_keys(message)
1763
+ elsif options[:list] then
1764
+ list_fields_and_structures(message, options[:list])
1765
+ elsif options[:list_validation_rules] then
1766
+ list_validation_rules(message)
1767
+ else
1768
+ puts "using #{options[:date_now]} as [Date.now]"
1769
+ puts "using #{options[:timestamp_now]} as [Timestamp.now]"
1770
+ validate_model(message)
1771
+ progress = ProgressBar.new(1)
1772
+ generate_doc(progress, message, OUTPUT_FILE, config, options)
1773
+ end
1774
+ end