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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/spec_object.rb +497 -0
  3. 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
@@ -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: []