mbt-gen 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mbt-gen.rb +2058 -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,2058 @@
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
+ end
459
+ end
460
+ return result
461
+ end
462
+
463
+ def resolve_field_def(path, relative_to_struct, prefix)
464
+ current = relative_to_struct
465
+ result = nil
466
+ path.split("/").each do |segment|
467
+ unless current then
468
+ 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.")
469
+ end
470
+ field_def = current[segment.to_sym]
471
+ unless field_def then
472
+ 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}.")
473
+ end
474
+ if field_def[:type] == :structure then
475
+ unless field_def[:ref] then
476
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
477
+ end
478
+ current = field_def[:ref]
479
+ if prefix then
480
+ prefix = "#{prefix}/#{segment}"
481
+ else
482
+ prefix = segment
483
+ end
484
+ elsif field_def[:type] == :field then
485
+ current = nil
486
+ result = field_def
487
+ elsif field_def[:type] == :list then
488
+ current = nil
489
+ result = field_def
490
+ end
491
+ end
492
+ return result
493
+ end
494
+
495
+ def resolve_struct_path(path, relative_to_struct, prefix)
496
+ current = relative_to_struct
497
+ result = nil
498
+ path.split("/").each do |segment|
499
+ unless current then
500
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
501
+ end
502
+ if segment == "." then
503
+ result = current[:struct_xpath_prefix]
504
+ elsif segment == ".." then
505
+ current = current[:parent_link]
506
+ if prefix then
507
+ prefix_segs = prefix.split("/")
508
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
509
+ else
510
+ prefix = current[:struct_xpath_prefix]
511
+ end
512
+ else
513
+ field_def = current[segment.to_sym]
514
+ unless field_def then
515
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
516
+ end
517
+ if field_def[:type] == :structure then
518
+ current = field_def[:ref]
519
+ if prefix then
520
+ prefix = "#{prefix}/#{segment}"
521
+ else
522
+ prefix = segment
523
+ end
524
+ result = prefix
525
+ end
526
+ end
527
+ end
528
+ return result
529
+ end
530
+
531
+ def resolve_struct_def(path, relative_to_struct, prefix, context_desc = "")
532
+ current = relative_to_struct
533
+ result = nil
534
+ path.split("/").each do |segment|
535
+ unless current then
536
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
537
+ end
538
+ if segment == "." then
539
+ result = current
540
+ elsif segment == ".." then
541
+ current = current[:parent_link]
542
+ if prefix then
543
+ prefix_segs = prefix.split("/")
544
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
545
+ else
546
+ prefix = current[:struct_xpath_prefix]
547
+ end
548
+ else
549
+ field_def = current[segment.to_sym]
550
+ unless field_def then
551
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
552
+ end
553
+ if field_def[:type] == :structure then
554
+ current = field_def[:ref]
555
+ if prefix then
556
+ prefix = "#{prefix}/#{segment}"
557
+ else
558
+ prefix = segment
559
+ end
560
+ result = current
561
+ end
562
+ end
563
+ end
564
+ return result
565
+ end
566
+
567
+ def translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
568
+ solver.to_solver(progress, "; --- Validation Rules ---")
569
+ translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
570
+ struct.each_pair do |key, value|
571
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
572
+ if value[:type] == :structure then
573
+ if prefix then
574
+ new_prefix = "#{prefix.to_s}/#{key}"
575
+ else
576
+ new_prefix = key.to_s
577
+ end
578
+ translate_validation_rules_to_SMTLIB(progress, solver, value[:ref], new_prefix, config, options)
579
+ end
580
+ end
581
+ end
582
+
583
+ def translate_validation_rule_to_SMTLIB(rule, context_struct, prefix, config, options, rule_name)
584
+ date_now = options[:date_now]
585
+ timestamp_now = options[:timestamp_now]
586
+ return rule.gsub(/FILLED\[[^\]]*\]/) do |m|
587
+ unless m =~ /FILLED\[([^\]]*)\]/ then
588
+ raise RuntimeError.new("could not parse FILLED[...]: #{m.inspect} in validation rule #{rule_name}")
589
+ end
590
+ field = resolve_field_path($1, context_struct, prefix)
591
+ filled_var_for_field(field)
592
+ end
593
+ .gsub(/VALUE\[[^\]]*\]/) do |m|
594
+ unless m =~ /VALUE\[([^\]]*)\]/ then
595
+ raise RuntimeError.new("could not parse VALUE[...]: #{m.inspect} in validation rule #{rule_name}")
596
+ end
597
+ field = resolve_field_path($1, context_struct, prefix)
598
+ value_var_for_field(field)
599
+ end
600
+ .gsub(/EXIST\[[^\]]*\]/) do |m|
601
+ unless m =~ /EXIST\[([^\]]*)\]/ then
602
+ raise RuntimeError.new("could not parse EXIST[...]: #{m.inspect} in validation rule #{rule_name}")
603
+ end
604
+ s = resolve_struct_path($1, context_struct, prefix)
605
+ exists_var_for_structure(s)
606
+ end
607
+ .gsub(/SIZE\[[^\]]*\]/) do |m|
608
+ unless m =~ /SIZE\[([^\]]*)\]/ then
609
+ raise RuntimeError.new("could not parse SIZE[...]: #{m.inspect} in validation rule #{rule_name}")
610
+ end
611
+ l = resolve_field_path($1, context_struct, prefix)
612
+ size_var_for_list(l)
613
+ end
614
+ .gsub(/LIST_CONTAINS\[[^\]]*,[^\]]*\]/) do |m|
615
+ unless m =~ /LIST_CONTAINS\[([^\]]*),([^\]]*)\]/ then
616
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
617
+ end
618
+ l = resolve_field_path($1, context_struct, prefix)
619
+ l_def = resolve_field_def($1, context_struct, prefix)
620
+ value = $2
621
+ terms = []
622
+ l_def[:model_maxLength].times do |i|
623
+ terms << "(and (< #{i} #{size_var_for_list(l)}) (= #{value_var_for_list(l, i)} #{value}))"
624
+ end
625
+ "(or #{terms.join(" ")})"
626
+ end
627
+ .gsub(/LIST_VALUE_FIRST_ELEMENT\[[^\]]*\]/) do |m|
628
+ unless m =~ /LIST_VALUE_FIRST_ELEMENT\[([^\]]*)\]/ then
629
+ raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
630
+ end
631
+ xpath = $1
632
+ l = resolve_field_path(xpath, context_struct, prefix)
633
+ value_var_for_list(l, 0)
634
+ end
635
+ .gsub("[Date.now]", date_now.to_s)
636
+ .gsub("[Timestamp.now]", timestamp_now.to_s)
637
+ .gsub(/PREDICATE\[[^\]]*,[^\]]*\]/) do |m|
638
+ unless m =~ /PREDICATE\[([^\]]*),([^\]]*)\]/ then
639
+ raise RuntimeError.new("could not parse PREDICATE[...]: #{m.inspect} in validation rule #{rule_name}")
640
+ end
641
+ path = $1
642
+ key = $2.strip().to_sym
643
+ sdef = resolve_struct_def(path, context_struct, prefix, "in validation rule #{rule_name}")
644
+ unless sdef
645
+ raise RuntimeError.new("could not resolve struct path #{path.inspect} in validation rule #{rule_name}")
646
+ end
647
+ preds = sdef[:predicates]
648
+ mdk = preds[key]
649
+ if mdk then
650
+ if mdk[:smtlib] then
651
+ translate_validation_rule_to_SMTLIB(mdk[:smtlib], sdef, sdef[:struct_xpath_prefix], config, options, "#{rule_name}.predicate.#{key}[#{sdef[:struct_xpath_prefix]}]" )
652
+ else
653
+ 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}")
654
+ end
655
+ else
656
+ raise RuntimeError.new("No predicate #{key.inspect} defined for structure #{sdef[:struct_xpath_prefix]}, but one referenced in validation rule #{rule_name}")
657
+ end
658
+ end
659
+ .gsub(/CONFIG\[[^\]]*\]/) do |m|
660
+ unless m =~ /CONFIG\[([^\]]*)\]/ then
661
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
662
+ end
663
+ key = $1.to_sym
664
+ if config.has_key?(key) then
665
+ "\"#{config[key]}\""
666
+ else
667
+ raise RuntimeError.new("unknown CONFIG-key #{key.inspect} encountered in validation rule #{rule_name}" )
668
+ end
669
+ end
670
+ .gsub(/FIELD_IN_QUERY\[[^\]]*\]/) do |m|
671
+ unless m =~ /FIELD_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
672
+ raise RuntimeError.new("could not parse FIELD_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
673
+ end
674
+ xpath = $1
675
+ query_filename = $2
676
+ fpath = resolve_field_path(xpath, context_struct, prefix)
677
+ fdef = resolve_field_def(xpath, context_struct, prefix)
678
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
679
+ if fdef[:datatype] == :string then
680
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
681
+ "(= #{value_var_for_field(fpath)} \"#{val}\")"
682
+ end
683
+ elsif fdef[:datatype] == :int then
684
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
685
+ "(= #{value_var_for_field(fpath)} #{Integer(val)})"
686
+ end
687
+ else
688
+ 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.")
689
+ end
690
+ "(or #{terms.join(" ")})"
691
+ end
692
+ .gsub(/FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[[^\]]*\]/) do |m|
693
+ unless m =~ /FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[([^\]]*),([^\]]*),([^\]]*)\]/ then
694
+ raise RuntimeError.new("could not parse FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[..,..,..]: #{m.inspect} in validation rule #{rule_name}")
695
+ end
696
+ field_xpath = $1
697
+ param_smtlib = $2
698
+ query_filename = $3
699
+ fpath = resolve_field_path(field_xpath.strip(), context_struct, prefix)
700
+ fdef = resolve_field_def(field_xpath.strip(), context_struct, prefix)
701
+ query_result = YAML.load_file("#{PARAMETERIZED_QUERY_DIR}/#{query_filename.strip()}")
702
+ terms = []
703
+ if fdef[:datatype] == :string then
704
+ terms << "(or #{query_result.keys.map { |key| "(= #{param_smtlib} \"#{key}\")" }.join(" ")})"
705
+ query_result.each_pair do |key, values|
706
+ terms << "(=> (= #{param_smtlib} \"#{key}\") (or #{values.map{ |v| "(= #{value_var_for_field(fpath)} \"#{v}\")" }.join(" ")}))"
707
+ end
708
+ else
709
+ 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.")
710
+ end
711
+ "(and #{terms.join(" ")})"
712
+ end
713
+ .gsub(/CONFIG_IN_QUERY\[[^\]]*\]/) do |m|
714
+ unless m =~ /CONFIG_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
715
+ raise RuntimeError.new("could not parse CONFIG_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
716
+ end
717
+ key = $1.to_sym
718
+ unless config.has_key?(key) then
719
+ raise RuntimeError.new("unknown CONFIG key #{key.inspect} encountered in validation rule #{rule_name}")
720
+ end
721
+ query_filename = $2
722
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
723
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
724
+ "(= \"#{config[key]}\" \"#{val}\")"
725
+ end
726
+ "(or #{terms.join(" ")})"
727
+ end
728
+ end
729
+
730
+ def translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
731
+ return unless struct[:validation_rules]
732
+
733
+ solver.to_solver(progress, "; --- Validation Rules: ---")
734
+ negated_rules = options[:negated_validation_rules] || []
735
+ solver.to_solver(progress, "; negated rules: #{negated_rules.empty? ? "<none>" : negated_rules.join(", ")}")
736
+
737
+ rules = struct[:validation_rules]
738
+ rules.each do |rule_name, rule|
739
+ rule = rule[:smtlib] # ignore the text representation
740
+ next unless rule # warning about this omission is the job of validate_model()
741
+ rule_SMTLIB = translate_validation_rule_to_SMTLIB(rule, struct, prefix, config, options, rule_name)
742
+ if negated_rules.include?(rule_name) then
743
+ rule_SMTLIB = "(not #{rule_SMTLIB})"
744
+ end
745
+ solver.to_solver(progress, "(assert (! #{rule_SMTLIB} :named validation_rule_#{rule_name}_instance_#{prefix.gsub("/", "_")}))")
746
+ end
747
+ end
748
+
749
+ def parse_field_value(fielddef, value)
750
+ value = value.gsub("\n", "")
751
+ datatype = fielddef[:datatype]
752
+ case datatype
753
+ when :int
754
+ return Integer(value)
755
+ when :string
756
+ return value
757
+ when :enum
758
+ return value
759
+ when :date
760
+ return Date.parse(value)
761
+ when :timestamp
762
+ return Time.new(value)
763
+ end
764
+ end
765
+
766
+ def solver_value_to_string(solver_value)
767
+ if solver_value =~ /\"(.*)\"/ then
768
+ return $1
769
+ else
770
+ return solver_value
771
+ end
772
+ end
773
+
774
+ def build_document_from_model(progress, struct, model)
775
+ doc = Nokogiri::XML(XML_TEMPLATE)
776
+ add_model_struct_to_doc(progress, struct, doc, model)
777
+ return doc
778
+ end
779
+
780
+ def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
781
+ struct.each do |key, value|
782
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
783
+ if prefix then
784
+ xpath = "#{prefix}/#{key}"
785
+ else
786
+ xpath = key.to_s
787
+ end
788
+ model_value = model[xpath]
789
+ if value[:type] == :field then
790
+ unless model_value then
791
+ raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
792
+ end
793
+ if model_value[:filled] == true || model_value[:filled] == "true" then
794
+ doc.add_child("<#{key}>#{solver_value_to_string(model_value[:value])}</#{key}>")
795
+ end
796
+ elsif value[:type] == :list then
797
+ node = doc.add_child("<#{key}></#{key}>")
798
+ if node.is_a?(Nokogiri::XML::NodeSet) then
799
+ node = node.first
800
+ end
801
+ xpath_element = value[:xpath_element]
802
+ if Integer(model_value[:size]) > 0 then
803
+ model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
804
+ node.add_child("<#{xpath_element}>#{solver_value_to_string(mvalue)}</#{xpath_element}>")
805
+ end
806
+ end
807
+ elsif value[:type] == :objlist then
808
+ node = doc.add_child("<#{key}></#{key}>")
809
+ if node.is_a?(Nokogiri::XML::NodeSet) then
810
+ node = node.first
811
+ end
812
+ xpath_element = value[:xpath_element]
813
+ if Integer(model_value[:size]) > 0 then
814
+ model_value[:size].times do |index|
815
+ cnode = node.add_child("<#{xpath_element}></#{xpath_element}>")
816
+ if cnode.is_a?(Nokogiri::XML::NodeSet) then
817
+ cnode = cnode.first
818
+ end
819
+ add_model_struct_to_doc(progress, value[:ref], cnode, model, "#{xpath}/#{index}")
820
+ end
821
+ end
822
+ elsif value[:type] == :structure then
823
+ unless value[:ref] then
824
+ raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
825
+ end
826
+ if model_value[:exists] == true || model_value[:exists] == "true" then
827
+ node = doc.add_child("<#{key}></#{key}>")
828
+ if node.is_a?(Nokogiri::XML::NodeSet) then
829
+ node = node.first
830
+ end
831
+ add_model_struct_to_doc(progress, value[:ref], node, model, xpath)
832
+ end
833
+ else
834
+ raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
835
+ end
836
+ end
837
+ end
838
+
839
+ def collect_validation_rule_names(struct)
840
+ result = []
841
+ struct.each do |key, value|
842
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
843
+ if key == :validation_rules
844
+ value.each do |key, value|
845
+ result << key
846
+ end
847
+ else
848
+ if value[:type] == :structure
849
+ result += collect_validation_rule_names(value[:ref])
850
+ end
851
+ end
852
+ end
853
+ return result
854
+ end
855
+
856
+ def set_standard_options(progress, solver)
857
+ solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
858
+ solver.to_solver(progress, "(set-option :smt.core.minimize true) ; ensure that unsat cores are minimal")
859
+ solver.to_solver(progress, "")
860
+ end
861
+
862
+ def generate_doc(progress, message, output_filename, config, options)
863
+ raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
864
+ log = options[:log] || nil
865
+ log.puts "generating doc #{output_filename}" if log
866
+ options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
867
+
868
+ solverFactory = options[:solverFactory]
869
+ model = solverFactory.query_model(options) do |solver|
870
+ set_standard_options(progress, solver)
871
+ log.puts "translating structure" if log
872
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
873
+ solver.to_solver(progress, "")
874
+ end
875
+
876
+ if model then
877
+ result_doc = build_document_from_model(progress, message, model)
878
+ dir = File.dirname(output_filename)
879
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
880
+ File.open(output_filename, "w") do |f|
881
+ f.puts result_doc.to_xml()
882
+ end
883
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
884
+ return true
885
+ else
886
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
887
+ return false
888
+ end
889
+ end
890
+
891
+ def z3ValueToRubyValue(value)
892
+ if value =~ /\"(.*)\"/ then # string
893
+ return $1
894
+ elsif value =~ /\d+/ then # int
895
+ return Integer(value)
896
+ elsif value == "true"
897
+ return true
898
+ elsif value == "false" then
899
+ return false
900
+ else
901
+ raise RuntimeError.new("enountered unknown type of value in z3 model: #{value.inspect}")
902
+ end
903
+ end
904
+
905
+ def extract_key_field_values_from_model(key_fields, model)
906
+ result = {}
907
+ key_fields.each do |xpath, fieldDesc|
908
+ fieldInfo = model[xpath]
909
+ if fieldDesc[:type] == :field then
910
+ if fieldInfo[:filled] then
911
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:value])
912
+ else
913
+ result[xpath] = nil
914
+ end
915
+ elsif fieldDesc[:type] == :struct || fieldDesc[:type] == :structure then
916
+ if fieldInfo.has_key?(:exists) then
917
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:exists])
918
+ else
919
+ result[xpath] = false
920
+ end
921
+ else
922
+ raise RuntimeError.new("encountered unknown :type in solver model: #{fieldDesc[:type].inspect}")
923
+ end
924
+ end
925
+ return result
926
+ end
927
+
928
+ def all_values_for_field(field)
929
+ unless field[:type] == :field then
930
+ raise RuntimeError.new("something that is not a field definition was passed to all_values_for_field(): #{field.inspect}")
931
+ end
932
+ unless field[:datatype] == :enum then
933
+ raise RuntimeError.new("all_values_for_field() is only applicable to fields of datatype enum! not #{field[:datatype].inspect}.")
934
+ end
935
+ values = field[:values].dup
936
+ if field[:optional] == true then
937
+ values << nil
938
+ end
939
+ return values
940
+ end
941
+
942
+ def calc_combinations_for_key_fields(key_fields)
943
+ count = 1
944
+ key_fields.each do |field|
945
+ if field[1][:type] == :field then
946
+ if field[1][:datatype] == :enum then
947
+ num_values = field[1][:values].size
948
+ num_values += 1 if field[1][:optional]
949
+ elsif field[1][:datatype] == :int then
950
+ if field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
951
+ if field[1][:optional] then
952
+ num_values = 2 + field[1][:max] - field[1][:min]
953
+ else
954
+ num_values = 1 + field[1][:max] - field[1][:min]
955
+ end
956
+ else
957
+ return "potentially infinite"
958
+ end
959
+ elsif field[1][:datatype] == :string then
960
+ return "potentially infinite"
961
+ end
962
+ elsif field[1][:type] == :structure then
963
+ num_values = 2
964
+ end
965
+ count *= num_values
966
+ end
967
+ return count
968
+ end
969
+
970
+ def calc_combinations(message, key)
971
+ key_fields = collect_key_fields(key, message)
972
+ if key_fields.empty?
973
+ raise FatalError.new("unknown key: #{key}")
974
+ end
975
+ return calc_combinations_for_key_fields(key_fields)
976
+ end
977
+
978
+ def read_docs_from_log_file(log_filename)
979
+ result = []
980
+ if File.exist?(log_filename) then
981
+ File.open(log_filename, "r") do |f|
982
+ f.each_line do |line|
983
+ doc = {}
984
+ if line =~ /(.*)\ <-\ \{(.*)\}/ then
985
+ filename = $1
986
+ combination = $2
987
+ combination.split(", ").each do |assignment|
988
+ if assignment =~ /(.*):\ (.*)/ then
989
+ key = $1
990
+ value = $2
991
+ if value =~ /\"(.*)\"/ then
992
+ value = $1
993
+ elsif value == "nil" then
994
+ value = nil
995
+ end
996
+ doc[key] = value
997
+ else
998
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid assignment #{assignment.inspect}")
999
+ end
1000
+ end
1001
+ else
1002
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid line #{line.inspect}")
1003
+ end
1004
+ result << doc
1005
+ end
1006
+ end
1007
+ end
1008
+ return result
1009
+ end
1010
+
1011
+ def prepare_output_doc_filename(number, output_dir)
1012
+ chunk_number = number / 100000
1013
+ dirname = "#{output_dir}/#{chunk_number}"
1014
+ unless Dir.exist?(dirname) then
1015
+ FileUtils.mkdir_p(dirname)
1016
+ end
1017
+ return "#{dirname}/doc#{number}.xml"
1018
+ end
1019
+
1020
+ def values_for_key_field(field)
1021
+ if field[:type] == :field then
1022
+ if field[:datatype] == :enum then
1023
+ if field[:optional] then
1024
+ return field[:values] + [nil]
1025
+ else
1026
+ return field[:values]
1027
+ end
1028
+ elsif field[:datatype] == :int then
1029
+ if field.has_key?(:min) and field.has_key?(:max) and field[:min] <= field[:max] then
1030
+ return (field[:min]..field[:max]).to_a
1031
+ else
1032
+ raise RuntimeError.new("cannot determine values for unbounded :int field")
1033
+ end
1034
+ else
1035
+ raise RuntimeError.new("cannot determine values for datatype #{field[:datatype].inspect}")
1036
+ end
1037
+ elsif field[:type] == :structure then
1038
+ if field[:optional] then
1039
+ return [true, false]
1040
+ else
1041
+ return [true]
1042
+ end
1043
+ else
1044
+ raise RuntimeError.new("Encountered unknown :type in model: #{field[:type].inspect} - keys can only mark fields or structures.")
1045
+ end
1046
+ end
1047
+
1048
+ def collect_key_fields(fkey, struct, prefix = nil)
1049
+ result = {}
1050
+ struct.each do |key, value|
1051
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1052
+ if value[:type] == :structure
1053
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1054
+ xpath = key.to_s
1055
+ xpath = "#{prefix}/#{xpath}" if prefix
1056
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values].include?(k)}
1057
+ end
1058
+ if prefix then
1059
+ new_prefix = "#{prefix}/#{key}"
1060
+ else
1061
+ new_prefix = key
1062
+ end
1063
+ result = result.merge(collect_key_fields(fkey, value[:ref], new_prefix))
1064
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1065
+ xpath = key.to_s
1066
+ xpath = "#{prefix}/#{xpath}" if prefix
1067
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values, :min, :max].include?(k)}
1068
+ end
1069
+ end
1070
+ return result
1071
+ end
1072
+
1073
+ def collect_key_fields_and_strctures(fkey, struct, prefix = nil)
1074
+ fields = []
1075
+ structures = []
1076
+ struct.each do |key, value|
1077
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1078
+ if value[:type] == :structure
1079
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1080
+ xpath = key.to_s
1081
+ xpath = "#{prefix}/#{xpath}" if prefix
1082
+ structures << xpath
1083
+ end
1084
+ if prefix then
1085
+ new_prefix = "#{prefix}/#{key}"
1086
+ else
1087
+ new_prefix = key
1088
+ end
1089
+ result_fields, result_structures = collect_key_fields_and_strctures(fkey, value[:ref], new_prefix)
1090
+ fields += result_fields
1091
+ structures += result_structures
1092
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1093
+ xpath = key.to_s
1094
+ xpath = "#{prefix}/#{xpath}" if prefix
1095
+ fields << xpath
1096
+ end
1097
+ end
1098
+ return fields, structures
1099
+ end
1100
+
1101
+ def collect_keys(struct)
1102
+ result = []
1103
+ struct.each do |key, value|
1104
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1105
+ if value[:type] == :structure
1106
+ if value.has_key?(:keys) then
1107
+ result += value[:keys]
1108
+ end
1109
+ if value[:ref] then
1110
+ result += collect_keys(value[:ref])
1111
+ else
1112
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1113
+ end
1114
+ elsif value[:type] == :field and value.has_key?(:keys) then
1115
+ result += value[:keys]
1116
+ end
1117
+ end
1118
+ result.uniq
1119
+ end
1120
+
1121
+ def collect_validation_rules(struct)
1122
+ result = []
1123
+ struct.each do |key, value|
1124
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1125
+ if key == :validation_rules then
1126
+ result += value.keys
1127
+ else
1128
+ if value[:type] == :structure
1129
+ if value[:ref] then
1130
+ result += collect_validation_rules(value[:ref])
1131
+ else
1132
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1133
+ end
1134
+ end
1135
+ end
1136
+ end
1137
+ result.uniq
1138
+ end
1139
+
1140
+ def list_keys(message)
1141
+ keys = collect_keys(message)
1142
+ puts "keys: #{keys.join(", ")}"
1143
+ end
1144
+
1145
+ def list_fields_and_structures(message, fkey)
1146
+ fields, structures = collect_key_fields_and_strctures(fkey, message)
1147
+ puts "fields for key #{fkey}:"
1148
+ fields.each { |f| puts " #{f}" }
1149
+ puts "structures for key #{fkey}:"
1150
+ structures.each { |s| puts " #{s}" }
1151
+ end
1152
+
1153
+ def list_validation_rules(message)
1154
+ rules = collect_validation_rules(message)
1155
+ puts "Validation Rules:"
1156
+ rules.each { |r| puts " #{r}" }
1157
+ end
1158
+
1159
+ def value_of_type(value, type)
1160
+ if type == :string or type == :enum then
1161
+ "\"#{value}\""
1162
+ elsif type == :int then
1163
+ value.to_s
1164
+ elsif type == :date then
1165
+ (Date.parse(value) - Time.at(0).to_date).to_i.to_s
1166
+ elsif type == :timestamp then
1167
+ (Time.new(value) - Time.at(0)).to_i.to_s
1168
+ else
1169
+ raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
1170
+ end
1171
+ end
1172
+
1173
+ class CoreEnumerationAlgo
1174
+
1175
+ def initialize()
1176
+ @num_tries = 0
1177
+ @num_docs = 0
1178
+ @on_new_doc = nil
1179
+ @on_unsat = nil
1180
+ end
1181
+
1182
+
1183
+ # fix values of all variables left from switched
1184
+ def enumerate_fixing_clause(assignment, vars, switched)
1185
+ clauses = []
1186
+ switched.times do |index|
1187
+ var_xpath = vars[index][0]
1188
+ var_def = vars[index][1]
1189
+ if var_def[:type] == :field then
1190
+ if assignment[var_xpath] == nil then
1191
+ clauses << "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named fixing_clause_#{index}))"
1192
+ else
1193
+ 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}))"
1194
+ end
1195
+ elsif var_def[:type] == :structure then
1196
+ clauses << "(assert (! (= #{exists_var_for_structure(var_xpath)} #{assignment[var_xpath].inspect}) :named fixing_clause_#{index}))"
1197
+ end
1198
+ end
1199
+ "; fixing clauses:\n#{clauses.join("\n")}"
1200
+ end
1201
+
1202
+ def summarize_fixing_clause(assignment, vars, switched)
1203
+ fixed = switched.times.to_a.map do |index|
1204
+ var_xpath = vars[index][0]
1205
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1206
+ end
1207
+ "fixed(#{fixed.join(", ")})"
1208
+ end
1209
+
1210
+ # prevent the var from taking values that already have been enumerated exhaustively
1211
+ def enumerate_blocking_clause(var_xpath, var_def, blocked)
1212
+ clauses = []
1213
+ num = 0
1214
+ if var_def[:type] == :field then
1215
+ blocked.each do |value|
1216
+ if value == nil then
1217
+ clauses << "(assert (! (not (= #{filled_var_for_field(var_xpath)} false)) :named blocking_clause_#{num}))"
1218
+ else
1219
+ 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}))"
1220
+ end
1221
+ num += 1
1222
+ end
1223
+ elsif var_def[:type] == :structure then
1224
+ blocked.each do |value|
1225
+ clauses << "(assert (! (not (= #{exists_var_for_structure(var_xpath)} #{value.inspect})) :named blocking_clause_#{num}))"
1226
+ num += 1
1227
+ end
1228
+ end
1229
+ "; blocking clauses:\n#{clauses.join("\n")}"
1230
+ end
1231
+
1232
+ def summarize_blocking_clause(var_xpath, blocked)
1233
+ "blocked(#{var_xpath})=[#{blocked.map{|v| v.inspect}.join(", ")}]"
1234
+ end
1235
+
1236
+ def generate_sparse_doc(progress, message, key, config, options, code, output_doc_filename)
1237
+ additional_options = {}
1238
+ if code then
1239
+ additional_options = { :add_SMTLIB => code }
1240
+ end
1241
+ return generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options.merge(additional_options))
1242
+ end
1243
+
1244
+ def log_filename_for_output_filename(output_filename)
1245
+ "#{output_filename}.smt2"
1246
+ end
1247
+
1248
+ def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
1249
+ raise RuntimeError.new("option :solverFactory is required.") unless options[:solverFactory]
1250
+ log = options[:log] || nil
1251
+ log.puts "generating doc #{output_filename}" if log
1252
+ options[:solverLog] = log_filename_for_output_filename(output_filename)
1253
+
1254
+ key_fields = collect_key_fields(key, message)
1255
+
1256
+ solverFactory = options[:solverFactory]
1257
+ model = solverFactory.query_model(options) do |solver|
1258
+ set_standard_options(progress, solver)
1259
+ log.puts "translating structure" if log
1260
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
1261
+ solver.to_solver(progress, "")
1262
+ end
1263
+
1264
+ if model then
1265
+ result_doc = build_document_from_model(progress, message, model)
1266
+ dir = File.dirname(output_filename)
1267
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1268
+ File.open(output_filename, "w") do |f|
1269
+ f.puts result_doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
1270
+ end
1271
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
1272
+ return extract_key_field_values_from_model(key_fields, model)
1273
+ else
1274
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
1275
+ return nil
1276
+ end
1277
+ end
1278
+
1279
+ def store_continuation_point(doc_number, state, assignment, new_assignment, filename, options, time)
1280
+ File.open(filename, "w") do |f|
1281
+ state_str = "state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")})"
1282
+ assignment_str = "assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1283
+ new_assignment_str = "new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1284
+ f.puts("#{doc_number};#{@num_docs};#{state_str};#{assignment_str};#{new_assignment_str};#{time}")
1285
+ end
1286
+ end
1287
+
1288
+ def self.parse_state(state_str)
1289
+ unless state_str =~ /state\((.*)\)/
1290
+ return nil
1291
+ end
1292
+ split = $1.split("#")
1293
+ split.map do |e|
1294
+ if e == "nil" then
1295
+ nil
1296
+ elsif e =~ /\[(.*)\]/ then
1297
+ $1.split(",").map do |i|
1298
+ if i.strip == "nil" then
1299
+ nil
1300
+ elsif i.strip == "true" then
1301
+ true
1302
+ elsif i.strip == "false" then
1303
+ false
1304
+ elsif i.strip =~ /\"(.*)\"/ then
1305
+ $1
1306
+ end
1307
+ end
1308
+ end
1309
+ end
1310
+ end
1311
+
1312
+ def self.parse_assignment(str)
1313
+ unless str =~ /assignment\((.*)\)/
1314
+ return nil
1315
+ end
1316
+ split = $1.split(",")
1317
+ result = {}
1318
+ split.each do |e|
1319
+ s2 = e.split(":")
1320
+ key = s2[0]
1321
+ value = s2[1]
1322
+ if value == "nil" then
1323
+ result[key] = nil
1324
+ elsif value == "true" then
1325
+ result[key] = true
1326
+ elsif value == "false" then
1327
+ result[key] = false
1328
+ elsif value =~ /\"(.*)\"/
1329
+ result[key] = $1
1330
+ end
1331
+ end
1332
+ result
1333
+ end
1334
+
1335
+ def self.parse_new_assignment(str)
1336
+ unless str =~ /new_assignment\((.*)\)/
1337
+ return nil
1338
+ end
1339
+ split = $1.split(",")
1340
+ result = {}
1341
+ split.each do |e|
1342
+ s2 = e.split(":")
1343
+ key = s2[0]
1344
+ value = s2[1]
1345
+ if value == "nil" then
1346
+ result[key] = nil
1347
+ elsif value == "true" then
1348
+ result[key] = true
1349
+ elsif value == "false" then
1350
+ result[key] = false
1351
+ elsif value =~ /\"(.*)\"/
1352
+ result[key] = $1
1353
+ end
1354
+ end
1355
+ result
1356
+ end
1357
+
1358
+ def self.parse_continuation_point(line)
1359
+ split = line.split(";")
1360
+ doc_number = Integer(split[0])
1361
+ num_docs = Integer(split[1])
1362
+ state = CoreEnumerationAlgo.parse_state(split[2])
1363
+ assignment = CoreEnumerationAlgo.parse_assignment(split[3])
1364
+ new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
1365
+ processessing_time = Integer(split[5])
1366
+ return [doc_number, num_docs, state, assignment, new_assignment, processessing_time]
1367
+ end
1368
+
1369
+ # TODO: logging
1370
+
1371
+ def display_combination_space(assignment, vars, switched, blocked)
1372
+ # enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1373
+
1374
+ # => all vars left of switched are fixed to their assigned values (when the value is nil, then they are empty)
1375
+ # => the switched var is prevented to have any of the blocked values
1376
+ res = switched.times.to_a.map do |index|
1377
+ var_xpath = vars[index][0]
1378
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1379
+ end + ["#{vars[switched][0]}\\{#{blocked.map{|v| v.inspect}.join(",")}}"]
1380
+ "{#{res.join(", ")}}"
1381
+ end
1382
+
1383
+
1384
+ # core algo
1385
+ def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size(), start_time, prior_processing_time)
1386
+ enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time) if switched < vars.size()-1
1387
+ is_finite_state = keys_are_finite_state(vars)
1388
+ if state[switched] == nil then
1389
+ blocked = [assignment[vars[switched][0]]]
1390
+ state[switched] = blocked
1391
+ else
1392
+ blocked = state[switched] + [assignment[vars[switched][0]]]
1393
+ state[switched] = blocked
1394
+ end
1395
+ found = true
1396
+ while found do
1397
+ found = false
1398
+ # - fix all vars left of switched to their values in assignment
1399
+ # - block switched from obtaining the values in blocked
1400
+ # progress.print_line "DEBUG: #{summarize_fixing_clause(assignment, vars, switched)}, #{summarize_blocking_clause(vars[switched][0], blocked)}"
1401
+ clauses = enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1402
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1403
+ new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
1404
+ generated_doc_number = @num_tries
1405
+ time = prior_processing_time + (Time.now().to_i - options[:timestamp_now])
1406
+ store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}", options, time)
1407
+ progress.print_line "STORING continuation point"
1408
+ progress.print_line "|time=#{time}"
1409
+ progress.print_line "|doc_number=#{generated_doc_number}"
1410
+ progress.print_line "|num_docs=#{@num_docs}"
1411
+ progress.print_line "|state=#{state.inspect}"
1412
+ progress.print_line "|assignment=#{assignment.inspect}"
1413
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1414
+
1415
+ if is_finite_state then
1416
+ progress.report_progress(determine_progress(state, vars), start_time, prior_processing_time, @num_docs)
1417
+ else
1418
+ progress.report_progress(nil, start_time, prior_processing_time, @num_docs)
1419
+ end
1420
+ @num_tries += 1
1421
+ if new_assignment then # sat
1422
+ @num_docs += 1
1423
+ @on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
1424
+ if switched < vars.size()-1 then
1425
+ enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1426
+ end
1427
+ blocked << new_assignment[vars[switched][0]]
1428
+ state[switched] = blocked
1429
+ found = true
1430
+ else # unsat
1431
+ @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
1432
+ found = false
1433
+ end
1434
+ end
1435
+ state[switched] = nil
1436
+ end
1437
+
1438
+ # NOTE: can only be called when all key-fields are finite-state
1439
+ def is_final_state(state, new_assignment, key_fields_list)
1440
+ return state[1,state.size()] == [nil] * (state.size()-1) &&
1441
+ state[0].to_set == values_for_key_field(key_fields_list[0][1]).to_set &&
1442
+ new_assignment.empty?
1443
+ end
1444
+
1445
+ # NOTE: can only be called when all key-fields are finite-state
1446
+ def determine_progress(state, vars)
1447
+ combinations_per_value = [nil] * vars.size()
1448
+ comb = 1
1449
+ vars.size().times.to_a.reverse_each do |i|
1450
+ combinations_per_value[i] = comb
1451
+ comb = values_for_key_field(vars[i][1]).size() * comb
1452
+ end
1453
+
1454
+ blocked_combinations = 0
1455
+ state.size().times do |i|
1456
+ if state[i] then
1457
+ blocked_combinations += state[i].size() * combinations_per_value[i]
1458
+ end
1459
+ end
1460
+ blocked_combinations
1461
+ end
1462
+
1463
+ def num_combinations(key_def)
1464
+ if key_def[:type] == :field then
1465
+ if key_def[:datatype] == :enum then
1466
+ key_def[:values].size() + (key_def[:optional] ? 1 : 0)
1467
+ elsif key_def[:datatype] == :int then
1468
+ if key_def.has_key?(:min) and key_def.has_key?(:max) and key_def[:min] <= key_def[:max] then
1469
+ 1 + key_def[:max] - key_def[:min] + (key_def[:optional] ? 1 : 0)
1470
+ else
1471
+ nil
1472
+ end
1473
+ else
1474
+ nil
1475
+ end
1476
+ elsif key_def[:type] == :structure then
1477
+ 2
1478
+ end
1479
+ end
1480
+
1481
+ def keys_are_finite_state(list)
1482
+ list.each do |field|
1483
+ name = field[0]
1484
+ if field[1][:type] == :field then
1485
+ if field[1][:datatype] == :enum then
1486
+ # enums are finite-state
1487
+ 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
1488
+ # ints are finite-state when both bounds are given and :min <= :max
1489
+ else
1490
+ return false
1491
+ end
1492
+ end
1493
+ end
1494
+ return true
1495
+ end
1496
+
1497
+ def generate_docs_for_key(progress, message, key, output_dir, config, options)
1498
+
1499
+ prior_processing_time = 0
1500
+ start_time = options[:timestamp_now]
1501
+ number_of_combinations = calc_combinations(message, key)
1502
+ if number_of_combinations.is_a?(String) then
1503
+ progress.set_max(100000000000000)
1504
+ else
1505
+ progress.set_max(number_of_combinations)
1506
+ end
1507
+ progress.report_progress(0.0, start_time, prior_processing_time, 0)
1508
+ progress.print_line "generating sparse documents efficiently..."
1509
+ progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
1510
+ progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
1511
+
1512
+ progress.print_line "preparing output dir #{output_dir}..."
1513
+ if options[:continue] then
1514
+ unless Dir.exist?(output_dir) then
1515
+ raise FatalError.new("FATAL: directory #{output_dir} not found. There seems to be nothing to continue from...")
1516
+ end
1517
+ log_mode = "a"
1518
+ else
1519
+ unless Dir.exist?(output_dir) then
1520
+ FileUtils.mkdir_p(output_dir)
1521
+ end
1522
+ log_mode = "w"
1523
+ end
1524
+ log_filename = "#{output_dir}/#{COMBINATION_LOG}"
1525
+
1526
+ key_fields = collect_key_fields(key, message)
1527
+ key_fields_list = key_fields.each_pair.to_a
1528
+ key_fields_list.sort! {|a,b| num_combinations(b[1]) <=> num_combinations(a[1]) }
1529
+
1530
+ if options[:continue] then
1531
+ stored_key_fields_str = File.read("#{output_dir}/#{VARS_LOG}")
1532
+ unless key_fields_list.to_yaml == stored_key_fields_str then
1533
+ raise FatalError.new("vars are differing! please specify the same key in order to use -continue!")
1534
+ end
1535
+ progress.print_line "continuing enumerating documents for key #{key}!"
1536
+ else
1537
+ # store vars
1538
+ File.open("#{output_dir}/#{VARS_LOG}", "w") do |f|
1539
+ f.puts(key_fields_list.to_yaml)
1540
+ end
1541
+ end
1542
+
1543
+ cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
1544
+ if options[:continue] then
1545
+ unless File.exist?(cont_filename) then
1546
+ raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
1547
+ end
1548
+ doc_number, num_docs, state, assignment, new_assignment, prior_processing_time = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
1549
+ if is_final_state(state, new_assignment, key_fields_list) then
1550
+ progress.report_progress(number_of_combinations, start_time, prior_processing_time, num_docs)
1551
+ raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
1552
+ end
1553
+ progress.print_line "RESUMING enumeration from the following continuation point:"
1554
+ progress.print_line "|time=#{prior_processing_time}"
1555
+ progress.print_line "|doc_number=#{doc_number}"
1556
+ progress.print_line "|num_docs=#{num_docs}"
1557
+ progress.print_line "|state=#{state.inspect}"
1558
+ progress.print_line "|assignment=#{assignment.inspect}"
1559
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1560
+ if keys_are_finite_state(key_fields_list) then
1561
+ progress.report_progress(determine_progress(state, key_fields_list), start_time, prior_processing_time, num_docs)
1562
+ else
1563
+ progress.report_progress(nil, start_time, prior_processing_time, num_docs)
1564
+ end
1565
+
1566
+ if new_assignment && !new_assignment.empty? then
1567
+ assignment = new_assignment
1568
+ end
1569
+
1570
+ # restore state at continuation point
1571
+ @num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
1572
+ number_of_combinations += 1 # account for this in the safety mechanism
1573
+ @num_docs = num_docs
1574
+ File.open(log_filename, log_mode) do |log|
1575
+
1576
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1577
+ # combination logging
1578
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1579
+
1580
+ if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1581
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1582
+ end
1583
+
1584
+ # safety mechanism
1585
+ if @num_docs > number_of_combinations then
1586
+ 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.")
1587
+ end
1588
+ }
1589
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1590
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1591
+ progress.print_line "doc#{num_doc} NOT generated"
1592
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1593
+ }
1594
+
1595
+ # core algo - resume from state
1596
+ enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1597
+ end
1598
+ else
1599
+ # init safety mechanism
1600
+ @num_tries = 0
1601
+ @num_docs = 0
1602
+ # first try - a freely generated document
1603
+ File.open(log_filename, log_mode) do |log|
1604
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1605
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1606
+ # combination logging
1607
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1608
+
1609
+ if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1610
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1611
+ end
1612
+
1613
+ # safety mechanism
1614
+ unless number_of_combinations.is_a?(String) then
1615
+ if @num_docs > number_of_combinations then
1616
+ 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.")
1617
+ end
1618
+ end
1619
+ }
1620
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1621
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1622
+ progress.print_line "doc#{num_doc} NOT generated"
1623
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1624
+ }
1625
+ key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
1626
+ @num_tries += 1
1627
+ if key_values then
1628
+ @num_docs += 1
1629
+ @on_new_doc.call(@num_tries, output_doc_filename, key_values)
1630
+ else
1631
+ # NOTE: this only occurs when the model is inconsistent and has no valid documents
1632
+ progress.print_line("ERROR: Model inconsistent - no valid documents could be found.")
1633
+ @on_unsat.call(@num_tries, log_filename_for_output_filename(output_doc_filename), "{}", 0)
1634
+ end
1635
+
1636
+ # core algo - start enumeration
1637
+ enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options, start_time, prior_processing_time)
1638
+ end
1639
+ end
1640
+ progress.print_line "No more valid documents could be found. -> Exiting"
1641
+ progress.report_progress(number_of_combinations, start_time, prior_processing_time, @num_docs)
1642
+ end
1643
+ end
1644
+
1645
+ def introduce_parent_links(message, parent = nil, prefix = nil)
1646
+ if parent != nil then
1647
+ message[:parent_link] = parent
1648
+ end
1649
+ if prefix != nil then
1650
+ message[:struct_xpath_prefix] = prefix
1651
+ end
1652
+ message.each_pair do |key, value|
1653
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1654
+ if value[:type] == :structure then
1655
+ if prefix then
1656
+ new_prefix = "#{prefix}/#{key}"
1657
+ else
1658
+ new_prefix = key
1659
+ end
1660
+ introduce_parent_links(value[:ref], message, new_prefix)
1661
+ end
1662
+ end
1663
+ end
1664
+
1665
+ def validate_model(message, prefix=nil)
1666
+
1667
+ message.each_pair do |key, value|
1668
+ if key == :validation_rules then
1669
+ value.each_pair do |vr_key, vr_value|
1670
+ unless vr_value[:text] && vr_value[:text] != "" then
1671
+ puts "INFO: Model Problem: validation rule #{vr_key} does not have a textual representation."
1672
+ end
1673
+ unless vr_value[:smtlib] && vr_value[:smtlib] != "" then
1674
+ puts "WARN: Model Problem: validation rule #{vr_key} does not have an SMTLIB representation. It will hence be omitted!"
1675
+ end
1676
+ end
1677
+ elsif key == :predicates then
1678
+ value.each_pair do |mkey, mvalue|
1679
+ next unless mvalue
1680
+ unless mvalue[:text] && mvalue[:text] != "" then
1681
+ puts "INFO: Model Problem: structure #{prefix}'s predicate #{mkey} does not have a textual representation."
1682
+ end
1683
+ unless mvalue[:smtlib] && mvalue[:smtlib] != "" then
1684
+ puts "WARN: Model Problem: structure #{prefix}'s predicate #{mkey} does not have an SMTLIB representation. It will be regarded as always true."
1685
+ end
1686
+ end
1687
+ elsif key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1688
+ next
1689
+ else
1690
+ if value[:type] == :structure then
1691
+ unless value[:ref] then
1692
+ raise RuntimeError.new("Model Problem: encountered structure without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1693
+ end
1694
+ if prefix then
1695
+ new_prefix = "#{prefix}/#{key}"
1696
+ else
1697
+ new_prefix = key
1698
+ end
1699
+ validate_model(value[:ref], new_prefix)
1700
+ elsif value[:type] == :field then
1701
+ unless value[:datatype] then
1702
+ raise RuntimeError.new("Model Problem: encountered field without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1703
+ end
1704
+ if value[:datatype] == :enum then
1705
+ unless value[:values] then
1706
+ raise RuntimeError.new("Model Problem: encountered enum field without a :values (key=#{key.inspect}, value=#{value.inspect})")
1707
+ end
1708
+ end
1709
+ elsif value[:type] == :list then
1710
+ unless value[:datatype] then
1711
+ raise RuntimeError.new("Model Problem: encountered list without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1712
+ end
1713
+ if value[:datatype] == :enum then
1714
+ unless value[:values] then
1715
+ raise RuntimeError.new("Model Problem: encountered enum list without a :values (key=#{key.inspect}, value=#{value.inspect})")
1716
+ end
1717
+ end
1718
+ elsif value[:type] == :objlist then
1719
+ unless value[:ref] then
1720
+ raise RuntimeError.new("Model Problem: encountered objlist without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1721
+ end
1722
+ unless value[:model_maxLength] then
1723
+ raise RuntimeError.new("Model Problem: encountered objlist without a :model_maxLength (key=#{key.inspect}, value=#{value.inspect})")
1724
+ end
1725
+ unless value[:model_maxLength].is_a?(Integer) then
1726
+ raise RuntimeError.new("Model Problem: encountered objlist whose :model_maxLength property is not an Integer (key=#{key.inspect}, value=#{value.inspect})")
1727
+ end
1728
+ unless value[:xpath_element] then
1729
+ raise RuntimeError.new("Model Problem: encountered objlist without a :xpath_element property (key=#{key.inspect}, value=#{value.inspect})")
1730
+ end
1731
+ unless value[:xpath_element].is_a?(String) then
1732
+ raise RuntimeError.new("Model Problem: encountered objlist whose :xpath_element property is not a String (key=#{key.inspect}, value=#{value.inspect})")
1733
+ end
1734
+ if prefix then
1735
+ new_prefix = "#{prefix}/#{key}"
1736
+ else
1737
+ new_prefix = key
1738
+ end
1739
+ validate_model(value[:ref], new_prefix)
1740
+ else
1741
+ raise RuntimeError.new("Model Problem: encountered unknown :type #{value[:type].inspect}")
1742
+ end
1743
+ end
1744
+ end
1745
+ end
1746
+
1747
+ def validate_doc_against_schema(struct, doc)
1748
+ root = doc.root
1749
+ sdef = struct[root.name.to_sym]
1750
+ unless sdef[:type] == :structure then
1751
+ puts "ERROR: schema validation failed: root element should be a structure"
1752
+ end
1753
+ # first, check that each doc element is defined for the struct
1754
+ validate_doc_struct_against_schema(sdef[:ref], root, root.name)
1755
+ # second, check that all required elements from the struct are actually present in the doc
1756
+ validate_schema_against_doc_struct(sdef[:ref], root, root.name)
1757
+ end
1758
+
1759
+ def validate_doc_struct_against_schema(struct, doc, prefix = nil)
1760
+ doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
1761
+ fdef = struct[c.name.to_sym]
1762
+ if prefix then
1763
+ xpath = "#{prefix}/#{c.name}"
1764
+ else
1765
+ xpath = c.name
1766
+ end
1767
+ if fdef
1768
+ if fdef[:type] == :structure then
1769
+ return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
1770
+ end
1771
+ else
1772
+ if prefix then
1773
+ xpath = "#{prefix}/#{c.name}"
1774
+ else
1775
+ xpath = c.name
1776
+ end
1777
+ raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
1778
+ end
1779
+ end
1780
+ end
1781
+
1782
+ def validate_schema_against_doc_struct(struct, doc, prefix = nil)
1783
+ struct.each do |key, value|
1784
+ next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1785
+ if prefix then
1786
+ xpath = "#{prefix}/#{key}"
1787
+ else
1788
+ xpath = key
1789
+ end
1790
+ if value[:type] == :structure then
1791
+ node = doc.at_xpath(key.to_s)
1792
+ if node then
1793
+ validate_schema_against_doc_struct(value[:ref], node, xpath)
1794
+ else
1795
+ if value[:optional] == false then
1796
+ raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
1797
+ end
1798
+ end
1799
+ elsif value[:type] == :field then
1800
+ if value[:optional] == false then
1801
+ unless doc.at_xpath(key.to_s) then
1802
+ raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
1803
+ end
1804
+ end
1805
+ elsif value[:type] == :list then
1806
+ if value[:optional] == false then
1807
+ unless doc.at_xpath(key.to_s) then
1808
+ raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
1809
+ end
1810
+ end
1811
+ list = doc.at_xpath(key.to_s)
1812
+ if list then
1813
+ list.children.to_a.each do |c|
1814
+ next unless c.is_a?(Nokogiri::XML::Element)
1815
+ unless c.name == value[:xpath_element] then
1816
+ raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
1817
+ end
1818
+ end
1819
+ end
1820
+ else
1821
+ raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
1822
+ end
1823
+ end
1824
+ end
1825
+
1826
+ def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
1827
+ options = hash[:options]
1828
+ config = hash[:config]
1829
+
1830
+ solver.to_solver(progress, "; --- Document: ---")
1831
+ translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
1832
+ end
1833
+
1834
+ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
1835
+ options = hash[:options]
1836
+ config = hash[:config]
1837
+
1838
+ struct.each do |key, value|
1839
+ next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1840
+ if prefix then
1841
+ xpath = "#{prefix}/#{key.to_s}"
1842
+ else
1843
+ xpath = key.to_s
1844
+ end
1845
+ if value[:type] == :structure then
1846
+ node = doc.at_xpath(xpath)
1847
+ if node then
1848
+ info = {
1849
+ :assertion_type => :doc_structure_exists,
1850
+ :xpath => xpath,
1851
+ }
1852
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1853
+ solver.associateAssertionIDWith(assertionID, info)
1854
+ solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} true) :named #{assertionID}))")
1855
+
1856
+ translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
1857
+ else
1858
+ info = {
1859
+ :assertion_type => :doc_structure_not_exists,
1860
+ :xpath => xpath,
1861
+ }
1862
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1863
+ solver.associateAssertionIDWith(assertionID, info)
1864
+ solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
1865
+ end
1866
+ elsif value[:type] == :field then
1867
+ node = doc.at_xpath(xpath)
1868
+ if node then
1869
+ fvalue = node.text
1870
+ info = {
1871
+ :assertion_type => :doc_field_filled,
1872
+ :xpath => xpath,
1873
+ :value => fvalue
1874
+ }
1875
+ fvalue_str = value_of_type(fvalue, value[:datatype])
1876
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1877
+ solver.associateAssertionIDWith(assertionID, info)
1878
+ solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
1879
+ else
1880
+ info = {
1881
+ :assertion_type => :doc_field_empty,
1882
+ :xpath => xpath,
1883
+ }
1884
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1885
+ solver.associateAssertionIDWith(assertionID, info)
1886
+ solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1887
+ end
1888
+ elsif value[:type] == :list then
1889
+ node = doc.at_xpath(xpath)
1890
+ if node then
1891
+ fvalues = []
1892
+ node.children.to_a.each do |c|
1893
+ next unless c.is_a?(Nokogiri::XML::Element)
1894
+ fvalues << c.text
1895
+ end
1896
+ info = {
1897
+ :assertion_type => :doc_list_filled,
1898
+ :xpath => xpath,
1899
+ :values => fvalues
1900
+ }
1901
+ fvalue_str = value_of_type(fvalues, value[:datatype])
1902
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1903
+ solver.associateAssertionIDWith(assertionID, info)
1904
+ clauses = []
1905
+ fvalues.each_with_index do |fvalue, index|
1906
+ clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
1907
+ end
1908
+ solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
1909
+ else
1910
+ info = {
1911
+ :assertion_type => :doc_field_empty,
1912
+ :xpath => xpath,
1913
+ }
1914
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1915
+ solver.associateAssertionIDWith(assertionID, info)
1916
+ solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1917
+ end
1918
+ else
1919
+ raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
1920
+ end
1921
+ end
1922
+ end
1923
+
1924
+ def validate_doc(progress, struct, doc, options, config)
1925
+ raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
1926
+ options[:solverLog] = "validation.smt2"
1927
+
1928
+ solverFactory = options[:solverFactory]
1929
+ result = solverFactory.query(options) do |solver|
1930
+ set_standard_options(progress, solver)
1931
+ translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
1932
+ translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
1933
+ solver.to_solver(progress, "")
1934
+ end
1935
+ unless result == true then
1936
+ raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
1937
+ end
1938
+
1939
+ return true
1940
+ end
1941
+
1942
+ def generate_unused_logfile_name()
1943
+ number = 0
1944
+ result = "logs/#{number}.log"
1945
+ while File.exist?(result) do
1946
+ number += 1
1947
+ result = "logs/#{number}.log"
1948
+ end
1949
+ result
1950
+ end
1951
+
1952
+ def generate(message, argv, message_config_filename = nil)
1953
+ if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
1954
+ puts USAGE
1955
+ exit(0)
1956
+ end
1957
+
1958
+ options = { :negated_validation_rules => [], :date_now => (Date.today - Time.at(0).to_date).to_i, :timestamp_now => Time.now().to_i }
1959
+
1960
+ opt = argv.shift
1961
+ while opt do
1962
+ if opt == "-negate" then
1963
+ rule = argv.shift
1964
+ options[:negated_validation_rules] << rule
1965
+ elsif opt == "-list-keys" then
1966
+ options[:list_keys] = true
1967
+ elsif opt == "-list" then
1968
+ options[:list] = argv.shift
1969
+ elsif opt == "-list-validation-rules" then
1970
+ options[:list_validation_rules] = true
1971
+ elsif opt == "-generate-docs-from" then
1972
+ options[:combination_source] = argv.shift
1973
+ elsif opt == "-skip" then
1974
+ options[:skip] = Integer(argv.shift)
1975
+ elsif opt == "-generate-docs-for-key" then
1976
+ options[:docs_for_key] = argv.shift
1977
+ elsif opt == "-count-docs-for-key" then
1978
+ options[:count_docs_for_key] = argv.shift
1979
+ elsif opt == "-continue" then
1980
+ options[:continue] = true
1981
+ elsif opt == "-max-num-docs" then
1982
+ options[:max_num_docs] = Integer(argv.shift)
1983
+ puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
1984
+ elsif opt == "-validate" then
1985
+ if options.has_key?(:validate_files) then
1986
+ options[:validate_files] << argv.shift
1987
+ else
1988
+ options[:validate_files] = [argv.shift]
1989
+ end
1990
+ else
1991
+ puts "FATAL: unknown option #{opt.inspect}."
1992
+ puts USAGE
1993
+ raise FatalError.new("")
1994
+ end
1995
+ opt = argv.shift
1996
+ end
1997
+
1998
+ ProgressBar.logging(1, generate_unused_logfile_name()) do |progress|
1999
+ unless options[:negated_validation_rules].empty? then
2000
+ progress.print_line "negating rules: #{options[:negated_validation_rules].join(", ")}"
2001
+
2002
+ rules = collect_validation_rule_names(message)
2003
+ unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
2004
+ unless unknown_negated_rules.empty? then
2005
+ throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
2006
+ end
2007
+
2008
+ end
2009
+
2010
+ if File.exist?(CONFIG_FILE) then
2011
+ config = YAML.load_file(CONFIG_FILE)
2012
+ else
2013
+ config = DEFAULT_CONFIG
2014
+ File.open(CONFIG_FILE, "w") do |f|
2015
+ f.puts(config.to_yaml)
2016
+ end
2017
+ progress.print_line "No config file found. Default config written to #{CONFIG_FILE}"
2018
+ end
2019
+ if message_config_filename then
2020
+ message_config = YAML.load_file(message_config_filename)
2021
+ config = config.merge(message_config)
2022
+ end
2023
+
2024
+ introduce_parent_links(message)
2025
+ z3path = config[:z3path] || DEFAULT_Z3_PATH
2026
+ options[:solverFactory] = SolverFactory.new(progress, z3path)
2027
+
2028
+ if options[:validate_files] then
2029
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2030
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2031
+ options[:validate_files].each do |filename|
2032
+ progress.print_line "validating file #{filename}"
2033
+ doc = Nokogiri::XML(File.read(filename))
2034
+ validate_doc_against_schema(message, doc)
2035
+ validate_doc(progress, message, doc, options, config)
2036
+ end
2037
+ elsif options[:count_docs_for_key] then
2038
+ progress.print_line "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
2039
+ elsif options[:docs_for_key] then
2040
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2041
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2042
+ validate_model(message)
2043
+ CoreEnumerationAlgo.new().generate_docs_for_key(progress, message, options[:docs_for_key], OUTPUT_DIR, config, options)
2044
+ elsif options[:list_keys] then
2045
+ list_keys(message)
2046
+ elsif options[:list] then
2047
+ list_fields_and_structures(message, options[:list])
2048
+ elsif options[:list_validation_rules] then
2049
+ list_validation_rules(message)
2050
+ else
2051
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2052
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2053
+ validate_model(message)
2054
+ generate_doc(progress, message, OUTPUT_FILE, config, options)
2055
+ end
2056
+ end
2057
+ end
2058
+