adsl 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +17 -0
- data/LICENSE +165 -0
- data/README.md +45 -0
- data/bin/adsl-verify +111 -0
- data/lib/ds/data_store_spec.rb +292 -0
- data/lib/fol/first_order_logic.rb +261 -0
- data/lib/parser/adsl_ast.rb +780 -0
- data/lib/parser/adsl_parser.racc +151 -0
- data/lib/parser/adsl_parser.rex +48 -0
- data/lib/parser/adsl_parser.rex.rb +196 -0
- data/lib/parser/adsl_parser.tab.rb +976 -0
- data/lib/parser/dsdl_parser.rex.rb +196 -0
- data/lib/parser/dsdl_parser.tab.rb +976 -0
- data/lib/spass/bin.rb +164 -0
- data/lib/spass/ruby_extensions.rb +25 -0
- data/lib/spass/spass_ds_extensions.rb +870 -0
- data/lib/spass/spass_translator.rb +378 -0
- data/lib/spass/util.rb +11 -0
- data/lib/util/csv_hash_formatter.rb +39 -0
- data/lib/util/test_helper.rb +33 -0
- data/lib/util/util.rb +110 -0
- metadata +185 -0
@@ -0,0 +1,378 @@
|
|
1
|
+
require 'spass/ruby_extensions'
|
2
|
+
require 'fol/first_order_logic'
|
3
|
+
|
4
|
+
module SpassTranslator
|
5
|
+
def replace_conjecture(input, conjecture)
|
6
|
+
input.gsub(/list_of_formulae\s*\(\s*conjectures\s*\)\s*\..*?end_of_list\./m, <<-SPASS)
|
7
|
+
list_of_formulae(conjectures).
|
8
|
+
formula(#{conjecture.resolve_spass}).
|
9
|
+
end_of_list.
|
10
|
+
SPASS
|
11
|
+
end
|
12
|
+
|
13
|
+
class Predicate
|
14
|
+
attr_accessor :name, :arity
|
15
|
+
|
16
|
+
include FOL
|
17
|
+
|
18
|
+
def initialize(name, arity)
|
19
|
+
@name = name
|
20
|
+
@arity = arity
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](*args)
|
24
|
+
args = args.flatten
|
25
|
+
return "#{@name}(#{ (1..@arity).map{ |i| "${#{i}}"}.join(", ") })".resolve_params(*args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ContextCommon
|
30
|
+
attr_accessor :parent, :level
|
31
|
+
|
32
|
+
def type_pred(*args)
|
33
|
+
return @level == 0 ? 'true' : @type_pred[*args.flatten]
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(translation, name, parent)
|
37
|
+
@level = parent.nil? ? 0 : parent.level + 1
|
38
|
+
@translation = translation
|
39
|
+
@parent = parent
|
40
|
+
|
41
|
+
unless parent.nil?
|
42
|
+
@type_pred = translation.create_predicate(name, @level)
|
43
|
+
|
44
|
+
translation.reserve_names @parent.p_names do |ps|
|
45
|
+
translation.create_formula FOL::ForAll.new(ps, :c, FOL::Implies.new(
|
46
|
+
type_pred(ps, :c), @parent.type_pred(ps)
|
47
|
+
))
|
48
|
+
end
|
49
|
+
translation.reserve_names @parent.p_names, @parent.p_names, :c do |p1s, p2s, c|
|
50
|
+
translation.create_formula FOL::ForAll.new(p1s, p2s, :c, FOL::Implies.new(
|
51
|
+
FOL::And.new(type_pred(p1s, c), type_pred(p2s, c)),
|
52
|
+
FOL::PairwiseEqual.new(p1s, p2s)
|
53
|
+
))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def same_level_before_formula(parents, c1, c2)
|
59
|
+
raise 'To be implemented'
|
60
|
+
end
|
61
|
+
|
62
|
+
def p_names(num = level)
|
63
|
+
num.times.map{ |i| "p#{i+1}".to_sym }
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get_common_context(c1, c2)
|
67
|
+
while c1.level > c2.level
|
68
|
+
c1 = c1.parent
|
69
|
+
end
|
70
|
+
while c2.level > c1.level
|
71
|
+
c2 = c2.parent
|
72
|
+
end
|
73
|
+
while c1 != c2
|
74
|
+
c1 = c1.parent
|
75
|
+
c2 = c2.parent
|
76
|
+
end
|
77
|
+
return c1
|
78
|
+
end
|
79
|
+
|
80
|
+
def before(c2, c1var, c2var, executed_before)
|
81
|
+
c1 = self
|
82
|
+
@translation.reserve_names((1..c1.level-1).map{|i| "parent_a#{i}"}) do |context1_names|
|
83
|
+
@translation.reserve_names((1..c2.level-1).map{|i| "parent_b#{i}"}) do |context2_names|
|
84
|
+
context1_names << c1var
|
85
|
+
context2_names << c2var
|
86
|
+
common_context = ContextCommon.get_common_context c1, c2
|
87
|
+
prereq_formulae = FOL::And.new(c1.type_pred(context1_names), c2.type_pred(context2_names))
|
88
|
+
|
89
|
+
solution = executed_before
|
90
|
+
parent_args = context1_names.first(common_context.level)
|
91
|
+
parent_args.pop
|
92
|
+
while common_context.parent
|
93
|
+
c1_name = context1_names[common_context.level-1]
|
94
|
+
c2_name = context2_names[common_context.level-1]
|
95
|
+
solution = FOL::And.new(
|
96
|
+
FOL::Implies.new(common_context.same_level_before_formula(parent_args, c1_name, c2_name), true),
|
97
|
+
FOL::Implies.new(common_context.same_level_before_formula(parent_args, c2_name, c1_name), false),
|
98
|
+
FOL::Implies.new(
|
99
|
+
FOL::Not.new(
|
100
|
+
common_context.same_level_before_formula(parent_args, c1_name, c2_name),
|
101
|
+
common_context.same_level_before_formula(parent_args, c2_name, c1_name)
|
102
|
+
),
|
103
|
+
solution
|
104
|
+
)
|
105
|
+
)
|
106
|
+
common_context = common_context.parent
|
107
|
+
parent_args.pop
|
108
|
+
end
|
109
|
+
solution = FOL::Implies.new(FOL::And.new(prereq_formulae), solution)
|
110
|
+
if context1_names.length > 1 or context2_names.length > 1
|
111
|
+
solution = FOL::ForAll.new([context1_names[0..-2], context2_names[0..-2]], solution)
|
112
|
+
end
|
113
|
+
return solution
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class FlatContext < ContextCommon
|
120
|
+
def initialize(translation, name, parent)
|
121
|
+
super
|
122
|
+
end
|
123
|
+
|
124
|
+
def same_level_before_formula(ps, c1, c2)
|
125
|
+
false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class ChainedContext < ContextCommon
|
130
|
+
attr_accessor :before_pred, :just_before, :first, :last
|
131
|
+
include FOL
|
132
|
+
|
133
|
+
def initialize(translation, name, parent)
|
134
|
+
super
|
135
|
+
|
136
|
+
@before_pred = translation.create_predicate "#{@type_pred.name}_before", @type_pred.arity + 1
|
137
|
+
@just_before = translation.create_predicate "#{@type_pred.name}_just_before", @type_pred.arity + 1
|
138
|
+
@first = translation.create_predicate "#{@type_pred.name}_first", @type_pred.arity
|
139
|
+
@last = translation.create_predicate "#{@type_pred.name}_last", @type_pred.arity
|
140
|
+
|
141
|
+
ps = []
|
142
|
+
(@type_pred.arity-1).times{ |i| ps << "p#{i}" }
|
143
|
+
translation.create_formula _for_all(ps, :c, _not(@before_pred[ps, :c, :c]))
|
144
|
+
translation.create_formula _for_all(ps, :c1, :c2, _implies(@before_pred[ps, :c1, :c2], _and(
|
145
|
+
@type_pred[ps, :c1],
|
146
|
+
@type_pred[ps, :c2],
|
147
|
+
_not(@before_pred[ps, :c2, :c1]),
|
148
|
+
_implies(
|
149
|
+
_and(@type_pred[ps, :c1], @type_pred[ps, :c2]),
|
150
|
+
_or(_equal(:c1, :c2), @before_pred[ps, :c1, :c2], @before_pred[ps, :c2, :c1])
|
151
|
+
)
|
152
|
+
)))
|
153
|
+
translation.create_formula _for_all(ps, :c1, :c2, :c3, _implies(
|
154
|
+
_and(@before_pred[ps, :c1, :c2], @before_pred[ps, :c2, :c3]),
|
155
|
+
@before_pred[ps, :c1, :c3]
|
156
|
+
))
|
157
|
+
translation.create_formula _for_all(ps, :c1, :c2, _equiv(
|
158
|
+
@just_before[ps, :c1, :c2],
|
159
|
+
_and(
|
160
|
+
@before_pred[ps, :c1, :c2],
|
161
|
+
_not(_exists(:mid, _and(@before_pred[ps, :c1, :mid], @before_pred[ps, :mid, :c2])))
|
162
|
+
)
|
163
|
+
))
|
164
|
+
translation.create_formula _for_all(ps, _and(
|
165
|
+
_equiv(
|
166
|
+
_exists(:c, @type_pred[ps, :c]),
|
167
|
+
_exists(:c, @first[ps, :c]),
|
168
|
+
_exists(:c, @last[ps, :c])
|
169
|
+
),
|
170
|
+
_for_all(ps, :c, _implies(
|
171
|
+
@type_pred[ps, :c],
|
172
|
+
_one_of(@last[ps, :c], _exists(:next, @just_before[ps, :c, :next]))
|
173
|
+
)),
|
174
|
+
_for_all(ps, :c, _equiv(@first[ps, :c],
|
175
|
+
_and(@type_pred[ps, :c], _not(_exists(:pre, @before_pred[ps, :pre, :c])))
|
176
|
+
)),
|
177
|
+
_for_all(ps, :c, _equiv(@last[ps, :c],
|
178
|
+
_and(@type_pred[ps, :c], _not(_exists(:post, @before_pred[ps, :c, :post])))
|
179
|
+
))
|
180
|
+
))
|
181
|
+
end
|
182
|
+
|
183
|
+
def same_level_before_formula(ps, c1, c2)
|
184
|
+
@before_pred[ps, c1, c2]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Translation
|
189
|
+
attr_accessor :context, :prev_state, :invariant_state
|
190
|
+
attr_reader :existed_initially, :exists_finally, :root_context
|
191
|
+
attr_reader :is_object, :is_tuple, :is_either_resolution, :resolved_as_true
|
192
|
+
attr_reader :create_obj_stmts, :delete_obj_stmts, :all_contexts, :classes
|
193
|
+
attr_reader :conjectures
|
194
|
+
|
195
|
+
include FOL
|
196
|
+
|
197
|
+
def initialize
|
198
|
+
@classes = []
|
199
|
+
@temp_vars = []
|
200
|
+
@functions = []
|
201
|
+
@predicates = []
|
202
|
+
@formulae = [[]]
|
203
|
+
@conjectures = []
|
204
|
+
@all_contexts = []
|
205
|
+
@existed_initially = create_predicate :existed_initially, 1
|
206
|
+
@exists_finally = create_predicate :exists_finally, 1
|
207
|
+
@is_object = create_predicate :is_object, 1
|
208
|
+
@is_tuple = create_predicate :is_tuple, 1
|
209
|
+
@is_either_resolution = create_predicate :is_either_resolution, 1
|
210
|
+
@root_context = create_context 'root_context', true, nil
|
211
|
+
@context = @root_context
|
212
|
+
# {class => [[before_stmt, context], [after_stmt, context]]}
|
213
|
+
@create_obj_stmts = Hash.new{ |hash, klass| hash[klass] = [] }
|
214
|
+
@delete_obj_stmts = Hash.new{ |hash, klass| hash[klass] = [] }
|
215
|
+
@prev_state = create_state :init_state
|
216
|
+
|
217
|
+
@invariant_state = nil
|
218
|
+
end
|
219
|
+
|
220
|
+
def create_state name
|
221
|
+
state = create_predicate name, @context.level + 1
|
222
|
+
reserve_names([:c_1] * @context.level, :o) do |cs, o|
|
223
|
+
create_formula FOL::ForAll.new(cs, o, FOL::Implies.new(
|
224
|
+
state[cs, o],
|
225
|
+
FOL::And.new(@context.type_pred(cs), FOL::Or.new(@is_object[o], @is_tuple[o]))
|
226
|
+
))
|
227
|
+
end
|
228
|
+
state
|
229
|
+
end
|
230
|
+
|
231
|
+
def create_context(name, flat, parent)
|
232
|
+
context = nil
|
233
|
+
if flat
|
234
|
+
context = FlatContext.new self, name, parent
|
235
|
+
else
|
236
|
+
context = ChainedContext.new self, name, parent
|
237
|
+
end
|
238
|
+
@all_contexts << context
|
239
|
+
context
|
240
|
+
end
|
241
|
+
|
242
|
+
def push_formula_frame
|
243
|
+
@formulae.push []
|
244
|
+
end
|
245
|
+
|
246
|
+
def pop_formula_frame
|
247
|
+
@formulae.pop
|
248
|
+
end
|
249
|
+
|
250
|
+
def create_formula(formula)
|
251
|
+
raise ArgumentError, 'Formula not resolveable to Spass' unless formula.class.method_defined? :resolve_spass
|
252
|
+
@formulae.last.push formula
|
253
|
+
end
|
254
|
+
|
255
|
+
def create_conjecture(formula)
|
256
|
+
raise ArgumentError, 'Formula not resolveable to Spass' unless formula.class.method_defined? :resolve_spass
|
257
|
+
@conjectures.push formula
|
258
|
+
end
|
259
|
+
|
260
|
+
def create_function(name, arity)
|
261
|
+
function = Predicate.new get_pred_name(name.to_s), arity
|
262
|
+
@functions << function
|
263
|
+
function
|
264
|
+
end
|
265
|
+
|
266
|
+
def create_predicate(name, arity)
|
267
|
+
pred = Predicate.new get_pred_name(name.to_s), arity
|
268
|
+
@predicates << pred
|
269
|
+
pred
|
270
|
+
end
|
271
|
+
|
272
|
+
def get_pred_name prefix
|
273
|
+
prefix_of_prefix = prefix
|
274
|
+
prefix_of_prefix = prefix.scan(/^(.+)_\d+$/).first.first if prefix =~ /^.+_\d+$/
|
275
|
+
|
276
|
+
already_existing = (@functions + @predicates).select{ |a| a.name =~ /^#{Regexp.escape prefix_of_prefix}(_\d+)?$/ }.max_by{ |a| a.name }
|
277
|
+
return prefix if already_existing.nil?
|
278
|
+
return already_existing.name.increment_suffix
|
279
|
+
end
|
280
|
+
|
281
|
+
def _reserve_names(*names)
|
282
|
+
result = []
|
283
|
+
names.each do |name|
|
284
|
+
if name.is_a? Array
|
285
|
+
result << _reserve_names(*name)
|
286
|
+
else
|
287
|
+
while @temp_vars.include? name
|
288
|
+
name = name.to_s.increment_suffix.to_sym
|
289
|
+
end
|
290
|
+
@temp_vars.push name
|
291
|
+
result << name
|
292
|
+
end
|
293
|
+
end
|
294
|
+
result
|
295
|
+
end
|
296
|
+
|
297
|
+
def reserve_names(*names)
|
298
|
+
result = _reserve_names(*names)
|
299
|
+
yield *result
|
300
|
+
ensure
|
301
|
+
names.flatten.length.times do
|
302
|
+
@temp_vars.pop
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def gen_formula_for_unique_arg(pred, *args)
|
307
|
+
individuals = []
|
308
|
+
args.each do |arg|
|
309
|
+
arg = arg.is_a?(Range) ? arg.to_a : [arg].flatten
|
310
|
+
next if arg.empty?
|
311
|
+
vars1 = (1..pred.arity).map{ |i| "e#{i}" }
|
312
|
+
vars2 = vars1.dup
|
313
|
+
as = []
|
314
|
+
bs = []
|
315
|
+
arg.each do |index|
|
316
|
+
a = "a#{index+1}".to_sym
|
317
|
+
vars1[index] = a
|
318
|
+
b = "b#{index+1}".to_sym
|
319
|
+
vars2[index] = b
|
320
|
+
as << a
|
321
|
+
bs << b
|
322
|
+
end
|
323
|
+
reserve_names (vars1 | vars2) do
|
324
|
+
individuals << _for_all(vars1 | vars2, _implies(_and(pred[vars1], pred[vars2]), _pairwise_equal(as, bs)))
|
325
|
+
end
|
326
|
+
end
|
327
|
+
return true if individuals.empty?
|
328
|
+
formula = _and(individuals)
|
329
|
+
create_formula formula
|
330
|
+
return formula
|
331
|
+
end
|
332
|
+
|
333
|
+
def spass_wrap(with, what)
|
334
|
+
return "" if what.length == 0
|
335
|
+
return with % what
|
336
|
+
end
|
337
|
+
|
338
|
+
def spass_list_of(what, *content)
|
339
|
+
spass_wrap "list_of_#{what.to_s}.%s\nend_of_list.", content.flatten.map{ |c| "\n " + c.to_s }.join("")
|
340
|
+
end
|
341
|
+
|
342
|
+
def to_spass_string
|
343
|
+
functions = @functions.map{ |f| "(#{f.name}, #{f.arity})" }.join(", ")
|
344
|
+
predicates = @predicates.map{ |p| "(#{p.name}, #{p.arity})" }.join(", ")
|
345
|
+
formulae = @formulae.first.map do |f|
|
346
|
+
begin
|
347
|
+
next "formula(#{f.resolve_spass})."
|
348
|
+
rescue => e
|
349
|
+
pp f
|
350
|
+
raise e
|
351
|
+
end
|
352
|
+
end
|
353
|
+
conjectures = @conjectures.map{ |f| "formula(#{f.resolve_spass})." }
|
354
|
+
<<-SPASS
|
355
|
+
begin_problem(Blahblah).
|
356
|
+
list_of_descriptions.
|
357
|
+
name({* *}).
|
358
|
+
author({* *}).
|
359
|
+
status(satisfiable).
|
360
|
+
description( {* *} ).
|
361
|
+
end_of_list.
|
362
|
+
#{spass_list_of( :symbols,
|
363
|
+
spass_wrap("functions[%s].", functions),
|
364
|
+
spass_wrap("predicates[%s].", predicates)
|
365
|
+
)}
|
366
|
+
#{spass_list_of( "formulae(axioms)",
|
367
|
+
formulae
|
368
|
+
)}
|
369
|
+
#{spass_list_of( "formulae(conjectures)",
|
370
|
+
conjectures
|
371
|
+
)}
|
372
|
+
end_problem.
|
373
|
+
SPASS
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
data/lib/spass/util.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Spass
|
2
|
+
module Util
|
3
|
+
def replace_conjecture(input, conjecture)
|
4
|
+
input.gsub(/list_of_formulae\s*\(\s*conjectures\s*\)\s*\..*?end_of_list\./m, <<-SPASS)
|
5
|
+
list_of_formulae(conjectures).
|
6
|
+
formula(#{conjecture.resolve_spass}).
|
7
|
+
end_of_list.
|
8
|
+
SPASS
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# A writer into CSV format that takes lines in hash format
|
2
|
+
# row = {:column1 => value1, :column2 => value2, ... }
|
3
|
+
# All rows are buffered together and a csv file is output with
|
4
|
+
# the union of individual column sets
|
5
|
+
# if the order of columns matter, supply an OrderedHash
|
6
|
+
# instance for each row
|
7
|
+
|
8
|
+
require 'set'
|
9
|
+
|
10
|
+
module Util
|
11
|
+
class CSVHashFormatter
|
12
|
+
def escape(obj)
|
13
|
+
"\"#{obj.to_s.gsub('"', '""')}\""
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@row_hashes = []
|
18
|
+
@columns = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_row(row)
|
22
|
+
@row_hashes << row
|
23
|
+
row.keys.each do |key|
|
24
|
+
@columns << key unless @columns.include? key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method :<<, :add_row
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
return '' if @columns.empty?
|
32
|
+
output = @columns.map{ |c| escape(c) }.join(',') + "\n"
|
33
|
+
@row_hashes.each do |row|
|
34
|
+
output = output + @columns.map{ |c| escape(row[c] || '') }.join(',') + "\n"
|
35
|
+
end
|
36
|
+
output
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'parser/adsl_parser.tab'
|
3
|
+
require 'spass/bin'
|
4
|
+
require 'spass/spass_ds_extensions'
|
5
|
+
require 'spass/util'
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
include Spass::Bin
|
9
|
+
include Spass::Util
|
10
|
+
|
11
|
+
SPASS_TIMEOUT = 5
|
12
|
+
|
13
|
+
def adsl_assert(expected_result, input, options={})
|
14
|
+
ds_spec = ADSL::ADSLParser.new.parse input
|
15
|
+
raise "Exactly one action required in ADSL" if ds_spec.actions.length != 1
|
16
|
+
action_name = ds_spec.actions.first.name
|
17
|
+
spass = ds_spec.translate_action(action_name)
|
18
|
+
spass = replace_conjecture spass, options[:conjecture] if options.include? :conjecture
|
19
|
+
result = exec_spass(spass, options[:timeout] || SPASS_TIMEOUT)
|
20
|
+
if result == :inconclusive
|
21
|
+
puts "inconclusive result on testcase #{self.class.name}.#{method_name}"
|
22
|
+
else
|
23
|
+
assert_equal expected_result, result
|
24
|
+
end
|
25
|
+
rescue Exception => e
|
26
|
+
puts spass unless spass.nil?
|
27
|
+
raise e
|
28
|
+
end
|
29
|
+
|
30
|
+
def spass_assert(expected_result, input, timeout = SPASS_TIMEOUT)
|
31
|
+
adsl_assert expected_result, input, :timeout => timeout
|
32
|
+
end
|
33
|
+
end
|