mbt-gen 0.0.3 → 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.
- checksums.yaml +4 -4
- data/lib/mbt-gen.rb +2058 -1958
- data/lib/progress.rb +53 -5
- data/lib/solver-lib.rb +572 -433
- metadata +6 -3
data/lib/mbt-gen.rb
CHANGED
|
@@ -1,1958 +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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"(
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
xpath
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
end
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
.gsub(/
|
|
638
|
-
unless m =~ /
|
|
639
|
-
raise RuntimeError.new("could not parse
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
end
|
|
659
|
-
.gsub(/
|
|
660
|
-
unless m =~ /
|
|
661
|
-
raise RuntimeError.new("could not parse
|
|
662
|
-
end
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
"
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
end
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
else
|
|
786
|
-
|
|
787
|
-
end
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
end
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
if
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
end
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
end
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
struct.each do |key, value|
|
|
1077
|
-
next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
|
|
1078
|
-
if
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
end
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
end
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
end
|
|
1235
|
-
|
|
1236
|
-
def
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
end
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
split
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
end
|
|
1311
|
-
|
|
1312
|
-
def self.
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
if value[:
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
unless
|
|
1711
|
-
raise
|
|
1712
|
-
end
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
end
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
end
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
:
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
end
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
options
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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
|
+
|