speculation 0.4.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -76,6 +76,13 @@ module Speculation
76
76
  end
77
77
  end
78
78
 
79
+ # @private
80
+ def self.fmap(gen)
81
+ ->(rantly) do
82
+ yield gen.call(rantly)
83
+ end
84
+ end
85
+
79
86
  # @private
80
87
  GEN_BUILTINS = {
81
88
  Integer => ->(r) { r.integer },
@@ -101,6 +108,10 @@ module Speculation
101
108
  gen = Gen.gen_for_pred(Array)
102
109
  Set.new(gen.call(r))
103
110
  end,
111
+ SortedSet => ->(r) do
112
+ gen = Gen.gen_for_pred(Array)
113
+ SortedSet.new(gen.call(r))
114
+ end,
104
115
  Hash => ->(r) do
105
116
  kgen = Gen.gen_for_pred(r.choose(Integer, String, Float, Symbol, Date, Time))
106
117
  vgen = Gen.gen_for_pred(r.choose(Integer, String, Float, Symbol, Date, Time, Set[true, false]))
@@ -13,17 +13,19 @@ module Speculation
13
13
  namespace = name_or_namespace
14
14
  else
15
15
  name = name_or_namespace
16
- namespace = is_a?(Module) ? self.name : self.class.name
16
+ namespace = is_a?(Module) ? self : self.class
17
17
  end
18
18
 
19
19
  NamespacedSymbols.symbol(namespace, name)
20
20
  end
21
21
 
22
22
  def self.symbol(ns, name)
23
+ ns = ns.name if ns.is_a?(Module)
24
+
23
25
  :"#{ns}/#{name}"
24
26
  end
25
27
 
26
- def self.name(sym)
28
+ def self.namespaced_name(sym)
27
29
  sym.to_s.split("/").last
28
30
  end
29
31
 
@@ -0,0 +1,48 @@
1
+ module Speculation
2
+ # Collection of predicate methods used within Speculation.
3
+ # These may appear as the value for the `:pred` key in the return value of
4
+ # `Speculation.explain_data`.
5
+ module Predicates
6
+ def self.hash?(x)
7
+ x.respond_to?(:store) && x.respond_to?(:key?) && x.respond_to?(:[])
8
+ end
9
+
10
+ def self.array?(x)
11
+ x.respond_to?(:at) && x.respond_to?(:[])
12
+ end
13
+
14
+ def self.collection?(xs)
15
+ xs.respond_to?(:each)
16
+ end
17
+
18
+ def self.distinct?(xs)
19
+ seen = Set[]
20
+
21
+ xs.each do |x|
22
+ if seen.include?(x)
23
+ return false
24
+ else
25
+ seen << x
26
+ end
27
+ end
28
+
29
+ true
30
+ end
31
+
32
+ def self.count_eq?(count, coll)
33
+ coll.count == count
34
+ end
35
+
36
+ def self.count_between?(min_count, max_count, coll)
37
+ coll.count.between?(min_count, max_count)
38
+ end
39
+
40
+ def self.key?(hash, key)
41
+ hash.key?(key)
42
+ end
43
+
44
+ def self.empty?(coll)
45
+ coll.empty?
46
+ end
47
+ end
48
+ end
@@ -10,6 +10,10 @@ module Speculation
10
10
  raise NotImplementedError
11
11
  end
12
12
 
13
+ def unform(_x)
14
+ raise NotImplementedError
15
+ end
16
+
13
17
  def explain(_path, _via, _inn, _value)
14
18
  raise NotImplementedError
15
19
  end
@@ -22,12 +22,16 @@ module Speculation
22
22
  @specs.value!.each do |spec|
23
23
  value = spec.conform(value)
24
24
 
25
- return S::INVALID if S.invalid?(value)
25
+ return :"Speculation/invalid" if S.invalid?(value)
26
26
  end
27
27
 
28
28
  value
29
29
  end
30
30
 
31
+ def unform(value)
32
+ @preds.reverse.reduce(value) { |val, pred| S.unform(pred, val) }
33
+ end
34
+
31
35
  def explain(path, via, inn, value)
32
36
  S.explain_pred_list(@preds, path, via, inn, value)
33
37
  end
@@ -38,7 +42,7 @@ module Speculation
38
42
 
39
43
  def gen(overrides, path, rmap)
40
44
  if @gen
41
- @gen
45
+ @gen.call
42
46
  else
43
47
  S.gensub(@preds.first, overrides, path, rmap)
44
48
  end
@@ -28,21 +28,27 @@ module Speculation
28
28
  end)
29
29
  end
30
30
 
31
+ if options[:distinct]
32
+ collection_predicates.push(->(coll) do
33
+ coll.empty? || Predicates.distinct?(coll)
34
+ end)
35
+ end
36
+
31
37
  @collection_predicate = ->(coll) { collection_predicates.all? { |f| f.respond_to?(:call) ? f.call(coll) : f === coll } }
32
38
  @delayed_spec = Concurrent::Delay.new { S.send(:specize, predicate) }
33
- @kfn = options.fetch(ns(S, :kfn), ->(i, _v) { i })
39
+ @kfn = options.fetch(:kfn, ->(i, _v) { i })
34
40
  @conform_keys, @conform_all, @kind, @gen_into, @gen_max, @distinct, @count, @min_count, @max_count =
35
- options.values_at(:conform_keys, ns(S, :conform_all), :kind, :into, :gen_max, :distinct, :count, :min_count, :max_count)
41
+ options.values_at(:conform_keys, :conform_all, :kind, :into, :gen_max, :distinct, :count, :min_count, :max_count)
36
42
  @gen_max ||= 20
37
43
  @conform_into = @gen_into
38
44
 
39
45
  # returns a tuple of [init add complete] fns
40
46
  @cfns = ->(x) do
41
- if Utils.array?(x) && (!@conform_into || Utils.array?(@conform_into))
42
- [Utils.method(:itself),
47
+ if Predicates.array?(x) && (!@conform_into || Predicates.array?(@conform_into))
48
+ [->(init) { init.dup },
43
49
  ->(ret, i, v, cv) { v.equal?(cv) ? ret : ret.tap { |r| r[i] = cv } },
44
50
  Utils.method(:itself)]
45
- elsif Utils.hash?(x) && ((@kind && !@conform_into) || Utils.hash?(@conform_into))
51
+ elsif Predicates.hash?(x) && ((@kind && !@conform_into) || Predicates.hash?(@conform_into))
46
52
  [@conform_keys ? Utils.method(:empty) : Utils.method(:itself),
47
53
  ->(ret, _i, v, cv) {
48
54
  if v.equal?(cv) && !@conform_keys
@@ -61,7 +67,7 @@ module Speculation
61
67
  end
62
68
 
63
69
  def conform(value)
64
- return S::INVALID unless @collection_predicate.call(value)
70
+ return :"Speculation/invalid" unless @collection_predicate.call(value)
65
71
 
66
72
  spec = @delayed_spec.value!
67
73
 
@@ -74,7 +80,7 @@ module Speculation
74
80
  conformed_value = spec.conform(val)
75
81
 
76
82
  if S.invalid?(conformed_value)
77
- return S::INVALID
83
+ return :"Speculation/invalid"
78
84
  else
79
85
  return_value = add.call(return_value, index, val, conformed_value)
80
86
  end
@@ -87,13 +93,30 @@ module Speculation
87
93
 
88
94
  value.each_with_index do |item, index|
89
95
  return value if index == limit
90
- return S::INVALID unless S.valid?(spec, item)
96
+ return :"Speculation/invalid" unless S.valid?(spec, item)
91
97
  end
92
98
 
93
99
  value
94
100
  end
95
101
  end
96
102
 
103
+ def unform(value)
104
+ if @conform_all
105
+ spec = @delayed_spec.value!
106
+ init, add, complete = @cfns.call(value)
107
+
108
+ ret = value.
109
+ each_with_index.
110
+ reduce(init.call(value)) { |memo, (val, index)|
111
+ add.call(memo, index, val, spec.unform(val))
112
+ }
113
+
114
+ complete.call(ret)
115
+ else
116
+ value
117
+ end
118
+ end
119
+
97
120
  def explain(path, via, inn, value)
98
121
  probs = collection_problems(value, @kind, @distinct, @count, @min_count, @max_count, path, via, inn)
99
122
  return probs if probs
@@ -117,7 +140,7 @@ module Speculation
117
140
  end
118
141
 
119
142
  def gen(overrides, path, rmap)
120
- return @gen if @gen
143
+ return @gen.call if @gen
121
144
 
122
145
  pgen = S.gensub(@predicate, overrides, path, rmap)
123
146
 
@@ -132,13 +155,13 @@ module Speculation
132
155
 
133
156
  val = if @distinct
134
157
  if @count
135
- rantly.array(@count, &pgen).tap { |arr| rantly.guard(Utils.distinct?(arr)) }
158
+ rantly.array(@count, &pgen).tap { |arr| rantly.guard(Predicates.distinct?(arr)) }
136
159
  else
137
160
  min = @min_count || 0
138
161
  max = @max_count || [@gen_max, 2 * min].max
139
162
  count = rantly.range(min, max)
140
163
 
141
- rantly.array(count, &pgen).tap { |arr| rantly.guard(Utils.distinct?(arr)) }
164
+ rantly.array(count, &pgen).tap { |arr| rantly.guard(Predicates.distinct?(arr)) }
142
165
  end
143
166
  elsif @count
144
167
  rantly.array(@count, &pgen)
@@ -160,26 +183,26 @@ module Speculation
160
183
  private
161
184
 
162
185
  def collection_problems(x, kfn, distinct, count, min_count, max_count, path, via, inn)
163
- pred = kfn || Utils.method(:collection?)
186
+ pred = kfn || Predicates.method(:collection?)
164
187
 
165
188
  unless S.pvalid?(pred, x)
166
189
  return S.explain1(pred, path, via, inn, x)
167
190
  end
168
191
 
169
- if count && !Utils.count_eq?(x, count)
170
- return [{ :path => path, :pred => [Utils.method(:count_eq?), [x, count]], :val => x, :via => via, :in => inn }]
192
+ if count && !Predicates.count_eq?(count, x)
193
+ return [{ :path => path, :pred => [Predicates.method(:count_eq?), [count, x]], :val => x, :via => via, :in => inn }]
171
194
  end
172
195
 
173
196
  if min_count || max_count
174
197
  min_count ||= 0
175
198
  max_count ||= Float::INFINITY
176
- unless Utils.count_between?(x, min_count, max_count)
177
- return [{ :path => path, :pred => [Utils.method(:count_between?), [x, min_count, max_count]], :val => x, :via => via, :in => inn }]
199
+ unless Predicates.count_between?(min_count, max_count, x)
200
+ return [{ :path => path, :pred => [Predicates.method(:count_between?), [min_count, max_count, x]], :val => x, :via => via, :in => inn }]
178
201
  end
179
202
  end
180
203
 
181
- if distinct && !x.empty? && !Utils.distinct?(x)
182
- [{ :path => path, :pred => [Utils.method(:distinct?), [x]], :val => x, :via => via, :in => inn }]
204
+ if distinct && !x.empty? && !Predicates.distinct?(x)
205
+ [{ :path => path, :pred => [Predicates.method(:distinct?), [x]], :val => x, :via => via, :in => inn }]
183
206
  end
184
207
  end
185
208
  end
@@ -14,7 +14,7 @@ module Speculation
14
14
 
15
15
  def initialize(args: nil, ret: nil, fn: nil, block: nil, gen: nil)
16
16
  @args = args
17
- @ret = ret
17
+ @ret = ret || :"Speculation/any"
18
18
  @fn = fn
19
19
  @block = block
20
20
  @gen = gen
@@ -23,17 +23,21 @@ module Speculation
23
23
  def conform(f)
24
24
  raise "Can't conform fspec without args spec: #{inspect}" unless @args
25
25
 
26
- return S::INVALID unless f.is_a?(Proc) || f.is_a?(Method)
26
+ return :"Speculation/invalid" unless f.is_a?(Proc) || f.is_a?(Method)
27
27
 
28
28
  specs = { :args => @args, :ret => @ret, :fn => @fn, :block => @block }
29
29
 
30
30
  if f.equal?(FSpec.validate_fn(f, specs, S.fspec_iterations))
31
31
  f
32
32
  else
33
- S::INVALID
33
+ :"Speculation/invalid"
34
34
  end
35
35
  end
36
36
 
37
+ def unform(f)
38
+ f
39
+ end
40
+
37
41
  def explain(path, via, inn, f)
38
42
  unless f.respond_to?(:call)
39
43
  return [{ :path => path, :pred => [f.method(:respond_to?), [:call]], :val => f, :via => via, :in => inn }]
@@ -66,7 +70,7 @@ module Speculation
66
70
  end
67
71
 
68
72
  def gen(overrides, _path, _rmap)
69
- return @gen if @gen
73
+ return @gen.call if @gen
70
74
 
71
75
  ->(_rantly) do
72
76
  ->(*args, &block) do
@@ -31,7 +31,7 @@ module Speculation
31
31
  req_specs = req_keys + req_un_specs
32
32
  req_keys += req_un_specs.map(&method(:unqualify_key))
33
33
 
34
- pred_exprs = [Utils.method(:hash?)]
34
+ pred_exprs = [Predicates.method(:hash?)]
35
35
  pred_exprs.push(->(v) { parse_req(req, v, Utils.method(:itself)).empty? }) if req.any?
36
36
  pred_exprs.push(->(v) { parse_req(req_un, v, method(:unqualify_key)).empty? }) if req_un.any?
37
37
 
@@ -44,43 +44,61 @@ module Speculation
44
44
  end
45
45
 
46
46
  def conform(value)
47
- return S::INVALID unless @keys_pred.call(value)
47
+ return :"Speculation/invalid" unless @keys_pred.call(value)
48
48
 
49
49
  reg = S.registry
50
- ret = value
51
50
 
52
- value.each do |key, v|
53
- spec_name = @key_to_spec_map.fetch(key, key)
51
+ value.reduce(value) do |ret, (key, v)|
52
+ spec_name = key_to_spec_name(key)
54
53
  spec = reg[spec_name]
55
54
 
56
- next unless spec
55
+ if spec
56
+ conformed_value = S.conform(spec, v)
57
57
 
58
- conformed_value = S.conform(spec, v)
59
-
60
- if S.invalid?(conformed_value)
61
- return S::INVALID
62
- else
63
- unless conformed_value.equal?(v)
64
- ret = ret.merge(key => conformed_value)
58
+ if S.invalid?(conformed_value)
59
+ break :"Speculation/invalid"
60
+ elsif conformed_value.equal?(v)
61
+ ret
62
+ else
63
+ ret.merge(key => conformed_value)
65
64
  end
65
+ else
66
+ ret
66
67
  end
67
68
  end
69
+ end
68
70
 
69
- ret
71
+ def unform(value)
72
+ reg = S.registry
73
+
74
+ value.reduce(value) do |ret, (key, conformed_value)|
75
+ if reg.key?(key_to_spec_name(key))
76
+ unformed_value = S.unform(key_to_spec_name(key), conformed_value)
77
+
78
+ if conformed_value.equal?(unformed_value)
79
+ ret
80
+ else
81
+ ret.merge(key => unformed_value)
82
+ end
83
+ else
84
+ ret
85
+ end
86
+ end
70
87
  end
71
88
 
72
89
  def explain(path, via, inn, value)
73
- unless Utils.hash?(value)
74
- return [{ :path => path, :pred => [Utils.method(:hash?), [value]], :val => value, :via => via, :in => inn }]
90
+ unless Predicates.hash?(value)
91
+ return [{ :path => path, :pred => [Predicates.method(:hash?), [value]], :val => value, :via => via, :in => inn }]
75
92
  end
76
93
 
94
+ reg = S.registry
77
95
  problems = []
78
96
 
79
97
  if @req.any?
80
98
  failures = parse_req(@req, value, Utils.method(:itself))
81
99
 
82
100
  failures.each do |failure_sexp|
83
- pred = [Utils.method(:key?), [failure_sexp]]
101
+ pred = [Predicates.method(:key?), [failure_sexp]]
84
102
  problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
85
103
  end
86
104
  end
@@ -89,17 +107,16 @@ module Speculation
89
107
  failures = parse_req(@req_un, value, method(:unqualify_key))
90
108
 
91
109
  failures.each do |failure_sexp|
92
- pred = [Utils.method(:key?), [failure_sexp]]
110
+ pred = [Predicates.method(:key?), [failure_sexp]]
93
111
  problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
94
112
  end
95
113
  end
96
114
 
97
115
  problems += value.flat_map { |(k, v)|
98
- next unless S.registry.key?(@key_to_spec_map[k])
116
+ next unless reg.key?(key_to_spec_name(k))
117
+ next if S.pvalid?(key_to_spec_name(k), v)
99
118
 
100
- unless S.pvalid?(@key_to_spec_map.fetch(k), v)
101
- S.explain1(@key_to_spec_map.fetch(k), Utils.conj(path, k), via, Utils.conj(inn, k), v)
102
- end
119
+ S.explain1(key_to_spec_name(k), Utils.conj(path, k), via, Utils.conj(inn, k), v)
103
120
  }
104
121
 
105
122
  problems.compact
@@ -110,7 +127,7 @@ module Speculation
110
127
  end
111
128
 
112
129
  def gen(overrides, path, rmap)
113
- return @gen if @gen
130
+ return @gen.call if @gen
114
131
 
115
132
  rmap = S.inck(rmap, @id)
116
133
 
@@ -149,7 +166,7 @@ module Speculation
149
166
  end
150
167
 
151
168
  def unqualify_key(x)
152
- NamespacedSymbols.name(x).to_sym
169
+ NamespacedSymbols.namespaced_name(x).to_sym
153
170
  end
154
171
 
155
172
  def parse_req(ks, v, f)
@@ -158,20 +175,20 @@ module Speculation
158
175
  ret = if key.is_a?(Array)
159
176
  op, *kks = key
160
177
  case op
161
- when ns(S, :or)
178
+ when :"Speculation/or"
162
179
  if kks.any? { |k| parse_req([k], v, f).empty? }
163
180
  []
164
181
  else
165
182
  transform_keys([key], f)
166
183
  end
167
- when ns(S, :and)
184
+ when :"Speculation/and"
168
185
  if kks.all? { |k| parse_req([k], v, f).empty? }
169
186
  []
170
187
  else
171
188
  transform_keys([key], f)
172
189
  end
173
190
  else
174
- raise "Expected or, and, got #{op}"
191
+ raise "Expected Speculation/or, Speculation/and, got #{op}"
175
192
  end
176
193
  elsif v.key?(f.call(key))
177
194
  []
@@ -190,10 +207,14 @@ module Speculation
190
207
  keys.map { |key|
191
208
  case key
192
209
  when Array then transform_keys(key, f)
193
- when ns(S, :and), ns(S, :or) then key
210
+ when :"Speculation/and", :"Speculation/or" then key
194
211
  else f.call(key)
195
212
  end
196
213
  }
197
214
  end
215
+
216
+ def key_to_spec_name(k)
217
+ @key_to_spec_map.fetch(k, k)
218
+ end
198
219
  end
199
220
  end