spec_object 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/spec_object.rb +497 -0
- metadata +44 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 233128ffeb95159a61665a26c25a91d818f22790
|
4
|
+
data.tar.gz: 2ea4ee16d2813cbb9ca0d6df92cc83f7189dfe84
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b073b71e8e620bd0114a383e58fe4059418561be3e296bc099e283799dfe209c6e1ab61a9f029ceefb096df82c6c4139d400df352853991a6a07c46401629706
|
7
|
+
data.tar.gz: 3fee798020d069a31a8cbbd77ce7976e63671610f1accf83f3ec0fc3eeadf77bfeb04a87ca0b5c0bb4953888f254c6fd19726ff10ebf746ff68a23bbe2ddfaeb
|
data/lib/spec_object.rb
ADDED
@@ -0,0 +1,497 @@
|
|
1
|
+
class Object
|
2
|
+
def to_so_expr
|
3
|
+
So::Const.new(self)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module So
|
8
|
+
class Expr
|
9
|
+
def to_so_expr
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def <(other)
|
14
|
+
Lt.lt(self, other)
|
15
|
+
end
|
16
|
+
|
17
|
+
def >(other)
|
18
|
+
Lt.lt(other, self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def !
|
22
|
+
Not.not_(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
Eq.eq(self, other)
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
Index.index(self, key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_time
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Const < Expr
|
41
|
+
def initialize(value)
|
42
|
+
@value = value
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :value
|
46
|
+
|
47
|
+
def pp(n)
|
48
|
+
"#{' '*n}#{@value.inspect}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def substitute(v, e)
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def evaluate(calls)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Time < Expr
|
61
|
+
def initialize(n)
|
62
|
+
@n = n
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :n
|
66
|
+
|
67
|
+
def pp(n)
|
68
|
+
"#{' '*n}t#{@n}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def substitute(v, e)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def evaluate(calls)
|
76
|
+
self
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Variable < Expr
|
81
|
+
def initialize
|
82
|
+
@is_time = false
|
83
|
+
@is_value = false
|
84
|
+
end
|
85
|
+
|
86
|
+
def time?
|
87
|
+
@is_time
|
88
|
+
end
|
89
|
+
|
90
|
+
def value?
|
91
|
+
@is_value
|
92
|
+
end
|
93
|
+
|
94
|
+
def pp(n)
|
95
|
+
"#{' '*n}v#{object_id}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def substitute(v, e)
|
99
|
+
if v.object_id == self.object_id
|
100
|
+
e
|
101
|
+
else
|
102
|
+
self
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def evaluate(calls)
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def assert_value
|
111
|
+
@is_value = true
|
112
|
+
raise "variable used as both value and time" if @is_time
|
113
|
+
end
|
114
|
+
|
115
|
+
def assert_time
|
116
|
+
@is_time = true
|
117
|
+
raise "variable used as both value and time" if @is_value
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Lt < Expr
|
122
|
+
def initialize(a, b)
|
123
|
+
@a = a
|
124
|
+
@b = b
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.lt(a, b)
|
128
|
+
a = a.to_so_expr
|
129
|
+
b = b.to_so_expr
|
130
|
+
|
131
|
+
if a.kind_of?(Const) && b.kind_of?(Const)
|
132
|
+
a.value < b.value
|
133
|
+
elsif a.kind_of?(Time) && b.kind_of?(Time)
|
134
|
+
a.n < b.n
|
135
|
+
else
|
136
|
+
new(a, b)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def pp(n)
|
141
|
+
"#{' '*n}(<\n#{@a.pp(n+2)}\n#{@b.pp(n+2)})"
|
142
|
+
end
|
143
|
+
|
144
|
+
def substitute(v, e)
|
145
|
+
Lt.lt(@a.substitute(v, e), @b.substitute(v, e))
|
146
|
+
end
|
147
|
+
|
148
|
+
def evaluate(calls)
|
149
|
+
Lt.lt(@a.evaluate(calls), @b.evaluate(calls))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Eq < Expr
|
154
|
+
def initialize(a, b)
|
155
|
+
@a = a
|
156
|
+
@b = b
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.eq(a, b)
|
160
|
+
a = a.to_so_expr
|
161
|
+
b = b.to_so_expr
|
162
|
+
|
163
|
+
if a.kind_of?(Const) && b.kind_of?(Const)
|
164
|
+
(a.value == b.value).to_so_expr
|
165
|
+
else
|
166
|
+
new(a, b)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def pp(n)
|
171
|
+
"#{' '*n}(==\n#{@a.pp(n+2)}\n#{@b.pp(n+2)})"
|
172
|
+
end
|
173
|
+
|
174
|
+
def substitute(v, e)
|
175
|
+
Eq.eq(@a.substitute(v, e), @b.substitute(v, e))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class Index < Expr
|
180
|
+
def initialize(x, index)
|
181
|
+
@x = x
|
182
|
+
@index = index
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.index(x, index)
|
186
|
+
x = x.to_so_expr
|
187
|
+
index = index.to_so_expr
|
188
|
+
|
189
|
+
if x.kind_of?(Const) && index.kind_of?(Const)
|
190
|
+
(x.value[index.value]).to_so_expr
|
191
|
+
else
|
192
|
+
new(x, index)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def pp(n)
|
197
|
+
"#{@x.pp(n)}[#{@index.pp(0)}]"
|
198
|
+
end
|
199
|
+
|
200
|
+
def substitute(v, e)
|
201
|
+
Index.index(@x.substitute(v, e), @index.substitute(v, e))
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class And < Expr
|
206
|
+
def initialize(*args)
|
207
|
+
@args = args
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.and_(*args)
|
211
|
+
args.map! do |arg|
|
212
|
+
arg.to_so_expr
|
213
|
+
end
|
214
|
+
|
215
|
+
args1 =
|
216
|
+
args.select do |arg|
|
217
|
+
if arg.kind_of?(Const)
|
218
|
+
if arg.value == false
|
219
|
+
return arg
|
220
|
+
elsif arg.value == true
|
221
|
+
false
|
222
|
+
else
|
223
|
+
true
|
224
|
+
end
|
225
|
+
else
|
226
|
+
true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
if args1.size == 1
|
231
|
+
args1[0]
|
232
|
+
elsif args1.size == 0
|
233
|
+
true.to_so_expr
|
234
|
+
else
|
235
|
+
new(*args1)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def pp(n)
|
240
|
+
s = @args.map do |arg| arg.pp(n+2) end.join("\n")
|
241
|
+
"#{' '*n}(and\n#{s})"
|
242
|
+
end
|
243
|
+
|
244
|
+
def substitute(v, e)
|
245
|
+
And.and_(*@args.map do |arg| arg.substitute(v, e) end)
|
246
|
+
end
|
247
|
+
|
248
|
+
def evaluate(calls)
|
249
|
+
And.and_(*@args.map do |arg| arg.evaluate(calls) end)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class Not < Expr
|
254
|
+
def initialize(x)
|
255
|
+
@x = x
|
256
|
+
end
|
257
|
+
|
258
|
+
attr_reader :x
|
259
|
+
|
260
|
+
def self.not_(x)
|
261
|
+
x = x.to_so_expr
|
262
|
+
|
263
|
+
if x.kind_of?(Const)
|
264
|
+
(!(x.value)).to_so_expr
|
265
|
+
elsif x.kind_of?(Not)
|
266
|
+
x.x
|
267
|
+
else
|
268
|
+
new(x)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def pp(n)
|
273
|
+
"#{' '*n}(not\n#{@x.pp(n+2)})"
|
274
|
+
end
|
275
|
+
|
276
|
+
def substitute(v, e)
|
277
|
+
Not.not_(@x.substitute(v, e))
|
278
|
+
end
|
279
|
+
|
280
|
+
def evaluate(calls)
|
281
|
+
Not.not_(@x.evaluate(calls))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class Exists < Expr
|
286
|
+
def initialize(variable, expr)
|
287
|
+
raise "expected variable" unless variable.is_a?(Variable)
|
288
|
+
|
289
|
+
@variable = variable
|
290
|
+
@expr = expr.to_so_expr
|
291
|
+
end
|
292
|
+
|
293
|
+
def pp(n)
|
294
|
+
"#{' '*n}(exists #{@variable.pp(0)}\n#{@expr.pp(n+2)})"
|
295
|
+
end
|
296
|
+
|
297
|
+
def substitute(v, e)
|
298
|
+
raise "bad thing happ(n)ened" if v.object_id == @variable.object_id
|
299
|
+
Exists.new(@variable, @expr.substitute(v, e))
|
300
|
+
end
|
301
|
+
|
302
|
+
def evaluate(calls)
|
303
|
+
if @variable.time?
|
304
|
+
posibilities =
|
305
|
+
(0...calls.size).map do |t|
|
306
|
+
v = @expr.substitute(@variable, Time.new(t)).evaluate(calls)
|
307
|
+
end
|
308
|
+
|
309
|
+
t =
|
310
|
+
posibilities.any? do |v|
|
311
|
+
v.kind_of?(Const) && v.value
|
312
|
+
end
|
313
|
+
|
314
|
+
if t
|
315
|
+
true.to_so_expr
|
316
|
+
else
|
317
|
+
f =
|
318
|
+
posibilities.all? do |v|
|
319
|
+
v.kind_of?(Const) && !(v.value)
|
320
|
+
end
|
321
|
+
|
322
|
+
if f
|
323
|
+
false.to_so_expr
|
324
|
+
else
|
325
|
+
self
|
326
|
+
end
|
327
|
+
end
|
328
|
+
elsif @variable.value?
|
329
|
+
self
|
330
|
+
else
|
331
|
+
raise "cannot infer the type of #{@variable.pp(0)}"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
class Received < Expr
|
337
|
+
def initialize(method, time=nil, args=nil)
|
338
|
+
raise "expected method name" unless method.is_a?(Symbol)
|
339
|
+
|
340
|
+
@method = method
|
341
|
+
@time = time
|
342
|
+
@args = args
|
343
|
+
end
|
344
|
+
|
345
|
+
def at(time)
|
346
|
+
time = time.to_so_expr
|
347
|
+
time.assert_time
|
348
|
+
r = Received.new(@method, time, @args)
|
349
|
+
r
|
350
|
+
end
|
351
|
+
|
352
|
+
def with(*args)
|
353
|
+
args.map! do |arg|
|
354
|
+
arg.assert_value
|
355
|
+
arg.to_so_expr
|
356
|
+
end
|
357
|
+
Received.new(@method, @time, args)
|
358
|
+
end
|
359
|
+
|
360
|
+
def pp(n)
|
361
|
+
s =
|
362
|
+
if !(@args.nil?)
|
363
|
+
@args.map do |arg| arg.pp(n+4) end.join("\n")
|
364
|
+
else
|
365
|
+
"#{' '*(n+2)}"
|
366
|
+
end
|
367
|
+
t_pp =
|
368
|
+
if !(@time.nil?)
|
369
|
+
@time.pp(n+2)
|
370
|
+
else
|
371
|
+
"#{' '*(n+2)}nil"
|
372
|
+
end
|
373
|
+
"#{' '*n}(received #{@method.inspect}\n#{t_pp}\n#{' '*(n+2)}(\n#{s}))"
|
374
|
+
end
|
375
|
+
|
376
|
+
def substitute(v, e)
|
377
|
+
time = @time.substitute(v, e)
|
378
|
+
args = @args.map do |arg| arg.substitute(v, e) end
|
379
|
+
|
380
|
+
Received.new(@method, time, args)
|
381
|
+
end
|
382
|
+
|
383
|
+
def evaluate(calls)
|
384
|
+
t = @time.evaluate(calls)
|
385
|
+
if !(t.kind_of?(Time))
|
386
|
+
return self
|
387
|
+
end
|
388
|
+
|
389
|
+
method, args, output = calls[t.n]
|
390
|
+
if method != @method
|
391
|
+
return false.to_so_expr
|
392
|
+
end
|
393
|
+
|
394
|
+
if args.size != @args.size
|
395
|
+
return false.to_so_expr
|
396
|
+
end
|
397
|
+
|
398
|
+
exprs =
|
399
|
+
args.zip(@args).map do |(value, expr)|
|
400
|
+
value.to_so_expr == expr
|
401
|
+
end
|
402
|
+
|
403
|
+
And.and_(*exprs)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class DSL
|
408
|
+
def exist(&blk)
|
409
|
+
v = Variable.new
|
410
|
+
Exists.new(v, blk.call(v))
|
411
|
+
end
|
412
|
+
|
413
|
+
def received(method)
|
414
|
+
Received.new(method)
|
415
|
+
end
|
416
|
+
|
417
|
+
def both(a, b)
|
418
|
+
And.and_(a, b)
|
419
|
+
end
|
420
|
+
|
421
|
+
def all(*args)
|
422
|
+
And.and_(*args)
|
423
|
+
end
|
424
|
+
|
425
|
+
def either(a, b)
|
426
|
+
a = a.to_so_expr
|
427
|
+
b = b.to_so_expr
|
428
|
+
|
429
|
+
!both(!a, !b)
|
430
|
+
end
|
431
|
+
|
432
|
+
def ite(c, t, f)
|
433
|
+
c = c.to_so_expr
|
434
|
+
|
435
|
+
either(both(c, t), both(!c, f))
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
module SpecObject
|
440
|
+
def self.extended(mod)
|
441
|
+
mod.send(:define_singleton_method, :behaviours) do
|
442
|
+
@behaviours ||= {}
|
443
|
+
end
|
444
|
+
|
445
|
+
mod.send(:define_method, :initialize) do |wrapped|
|
446
|
+
@wrapped = wrapped
|
447
|
+
@calls = []
|
448
|
+
end
|
449
|
+
|
450
|
+
mod.send(:define_method, :method_missing) do |name, *args|
|
451
|
+
output = @wrapped.send(name, *args)
|
452
|
+
|
453
|
+
behaviour = mod.behaviours[name]
|
454
|
+
if behaviour
|
455
|
+
v_args, v_output, expr = behaviour.values_at(:args, :output, :expr)
|
456
|
+
|
457
|
+
expr =
|
458
|
+
expr
|
459
|
+
.substitute(v_output, output.to_so_expr)
|
460
|
+
.substitute(v_args, args.to_so_expr)
|
461
|
+
|
462
|
+
v = expr.evaluate(@calls)
|
463
|
+
unless v.kind_of?(Const) && v.value
|
464
|
+
puts v.pp(0)
|
465
|
+
raise "Problem"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
@calls.push([name, args, output])
|
470
|
+
|
471
|
+
output
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def specs(cl)
|
476
|
+
spec = self
|
477
|
+
|
478
|
+
old_new = cl.method(:new)
|
479
|
+
cl.send(:define_singleton_method, :new) do |*args|
|
480
|
+
spec.new(old_new.call(*args))
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def behaviour(name, &blk)
|
485
|
+
args = Variable.new
|
486
|
+
output = Variable.new
|
487
|
+
|
488
|
+
expr = DSL.new.instance_exec(args, output, &blk).to_so_expr
|
489
|
+
|
490
|
+
behaviours[name] = {
|
491
|
+
args: args,
|
492
|
+
output: output,
|
493
|
+
expr: expr
|
494
|
+
}
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
metadata
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spec_object
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Waterworth
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: da.waterworth@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/spec_object.rb
|
20
|
+
homepage: http://github.com/DanielWaterworth/spec_object
|
21
|
+
licenses:
|
22
|
+
- MIT
|
23
|
+
metadata: {}
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
requirements: []
|
39
|
+
rubyforge_project:
|
40
|
+
rubygems_version: 2.4.8
|
41
|
+
signing_key:
|
42
|
+
specification_version: 4
|
43
|
+
summary: Describe your objects' behaviour with temporal logic
|
44
|
+
test_files: []
|