speculation 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -0
- data/.travis.yml +2 -4
- data/README.md +22 -12
- data/Rakefile +5 -9
- data/bin/console +54 -7
- data/examples/codebreaker.rb +246 -0
- data/examples/spec_guide.rb +1288 -0
- data/lib/speculation.rb +145 -146
- data/lib/speculation/error.rb +4 -3
- data/lib/speculation/gen.rb +51 -47
- data/lib/speculation/identifier.rb +7 -7
- data/lib/speculation/namespaced_symbols.rb +26 -19
- data/lib/speculation/pmap.rb +9 -10
- data/lib/speculation/spec_impl/and_spec.rb +3 -4
- data/lib/speculation/spec_impl/every_spec.rb +24 -24
- data/lib/speculation/spec_impl/f_spec.rb +32 -35
- data/lib/speculation/spec_impl/hash_spec.rb +33 -41
- data/lib/speculation/spec_impl/merge_spec.rb +2 -3
- data/lib/speculation/spec_impl/nilable_spec.rb +8 -9
- data/lib/speculation/spec_impl/or_spec.rb +5 -7
- data/lib/speculation/spec_impl/regex_spec.rb +2 -3
- data/lib/speculation/spec_impl/spec.rb +3 -5
- data/lib/speculation/spec_impl/tuple_spec.rb +8 -10
- data/lib/speculation/test.rb +126 -101
- data/lib/speculation/utils.rb +31 -5
- data/lib/speculation/version.rb +1 -1
- data/speculation.gemspec +0 -1
- metadata +30 -44
- data/lib/speculation/conj.rb +0 -32
- data/lib/speculation/utils_specs.rb +0 -57
data/lib/speculation.rb
CHANGED
@@ -5,15 +5,13 @@ require "securerandom"
|
|
5
5
|
|
6
6
|
require "speculation/version"
|
7
7
|
require "speculation/namespaced_symbols"
|
8
|
-
require "speculation/conj"
|
9
8
|
require "speculation/identifier"
|
10
9
|
require "speculation/utils"
|
11
10
|
require "speculation/spec_impl"
|
12
11
|
require "speculation/error"
|
13
12
|
|
14
13
|
module Speculation
|
15
|
-
|
16
|
-
using Conj
|
14
|
+
extend NamespacedSymbols
|
17
15
|
|
18
16
|
class << self
|
19
17
|
# Enables or disables spec asserts. Defaults to false.
|
@@ -36,7 +34,7 @@ module Speculation
|
|
36
34
|
attr_accessor :coll_error_limit
|
37
35
|
end
|
38
36
|
|
39
|
-
@check_asserts =
|
37
|
+
@check_asserts = ENV["SPECULATION_CHECK_ASSERTS"] == "true"
|
40
38
|
@recursion_limit = 4
|
41
39
|
@fspec_iterations = 21
|
42
40
|
@coll_check_limit = 101
|
@@ -44,20 +42,24 @@ module Speculation
|
|
44
42
|
|
45
43
|
@registry_ref = Concurrent::Atom.new({})
|
46
44
|
|
47
|
-
# Can be enabled
|
45
|
+
# Can be enabled or disabled at runtime:
|
46
|
+
# - enabled/disabled by setting `check_asserts`.
|
47
|
+
# - enabled by setting environment variable SPECULATION_CHECK_ASSERTS to the
|
48
|
+
# string "true"
|
49
|
+
# Defaults to false if not set.
|
48
50
|
# @param spec [Spec]
|
49
51
|
# @param x value to validate
|
50
52
|
# @return x if x is valid? according to spec
|
51
53
|
# @raise [Error] with explain_data plus :Speculation/failure of :assertion_failed
|
52
54
|
def self.assert(spec, x)
|
53
55
|
return x unless check_asserts
|
54
|
-
return x
|
56
|
+
return x if valid?(spec, x)
|
55
57
|
|
56
|
-
ed =
|
58
|
+
ed = _explain_data(spec, [], [], [], x).merge(ns(:failure) => :assertion_failed)
|
57
59
|
out = StringIO.new
|
58
|
-
|
60
|
+
explain_out(ed, out)
|
59
61
|
|
60
|
-
raise Speculation::Error.new("Spec assertion failed\n#{out.string}",
|
62
|
+
raise Speculation::Error.new("Spec assertion failed\n#{out.string}", ed)
|
61
63
|
end
|
62
64
|
|
63
65
|
# @param infinite [Boolean] whether +/- infinity allowed (default true)
|
@@ -76,30 +78,30 @@ module Speculation
|
|
76
78
|
max ||= Float::MAX
|
77
79
|
|
78
80
|
gens = [[20, ->(_) { rand(min.to_f..max.to_f) }]]
|
79
|
-
gens << [1, ->(
|
81
|
+
gens << [1, ->(r) { r.choose(Float::INFINITY, -Float::INFINITY) }] if infinite
|
80
82
|
gens << [1, ->(_) { Float::NAN }] if nan
|
81
83
|
|
82
|
-
spec(
|
84
|
+
spec(self.and(*preds), :gen => ->(rantly) { rantly.freq(*gens) })
|
83
85
|
end
|
84
86
|
|
85
87
|
# @param range [Range<Integer>]
|
86
88
|
# @return Spec that validates ints in the given range
|
87
89
|
def self.int_in(range)
|
88
|
-
spec(
|
90
|
+
spec(self.and(Integer, ->(x) { range.include?(x) }),
|
89
91
|
:gen => ->(_) { rand(range) })
|
90
92
|
end
|
91
93
|
|
92
94
|
# @param time_range [Range<Time>]
|
93
95
|
# @return Spec that validates times in the given range
|
94
96
|
def self.time_in(time_range)
|
95
|
-
spec(
|
97
|
+
spec(self.and(Time, ->(x) { time_range.cover?(x) }),
|
96
98
|
:gen => ->(_) { rand(time_range) })
|
97
99
|
end
|
98
100
|
|
99
101
|
# @param date_range [Range<Date>]
|
100
102
|
# @return Spec that validates dates in the given range
|
101
103
|
def self.date_in(date_range)
|
102
|
-
spec(
|
104
|
+
spec(self.and(Date, ->(x) { date_range.cover?(x) }),
|
103
105
|
:gen => ->(_) { rand(date_range) })
|
104
106
|
end
|
105
107
|
|
@@ -112,13 +114,13 @@ module Speculation
|
|
112
114
|
# @param x [Hash, Object]
|
113
115
|
# @return [Hash, false] x if x is a (Speculation) regex op, else logical false
|
114
116
|
def self.regex?(x)
|
115
|
-
Utils.hash?(x) && x[:op
|
117
|
+
Utils.hash?(x) && x[ns(:op)] && x
|
116
118
|
end
|
117
119
|
|
118
120
|
# @param value return value of a `conform` call
|
119
121
|
# @return [Boolean] true if value is the result of an unsuccessful conform
|
120
122
|
def self.invalid?(value)
|
121
|
-
value.equal?(:invalid
|
123
|
+
value.equal?(ns(:invalid))
|
122
124
|
end
|
123
125
|
|
124
126
|
# @param spec [Spec]
|
@@ -129,13 +131,13 @@ module Speculation
|
|
129
131
|
specize(spec).conform(value)
|
130
132
|
end
|
131
133
|
|
132
|
-
# Takes a spec and a one-arg generator
|
134
|
+
# Takes a spec and a one-arg generator function and returns a version of the spec that uses that generator
|
133
135
|
# @param spec [Spec]
|
134
|
-
# @
|
136
|
+
# @param gen [Proc] generator proc that receives a Rantly instance
|
135
137
|
# @return [Spec]
|
136
|
-
def self.with_gen(spec,
|
138
|
+
def self.with_gen(spec, gen)
|
137
139
|
if regex?(spec)
|
138
|
-
spec.merge(:gfn
|
140
|
+
spec.merge(ns(:gfn) => gen)
|
139
141
|
else
|
140
142
|
specize(spec).tap { |s| s.gen = gen }
|
141
143
|
end
|
@@ -146,7 +148,7 @@ module Speculation
|
|
146
148
|
probs = specize(spec).explain(path, via, inn, value)
|
147
149
|
|
148
150
|
if probs && probs.any?
|
149
|
-
{ :problems
|
151
|
+
{ ns(:problems) => probs }
|
150
152
|
end
|
151
153
|
end
|
152
154
|
|
@@ -172,7 +174,7 @@ module Speculation
|
|
172
174
|
def self.explain_out(ed, out = STDOUT)
|
173
175
|
return out.puts("Success!") unless ed
|
174
176
|
|
175
|
-
ed.fetch(:problems
|
177
|
+
ed.fetch(ns(:problems)).each do |prob|
|
176
178
|
path, pred, val, reason, via, inn = prob.values_at(:path, :pred, :val, :reason, :via, :in)
|
177
179
|
|
178
180
|
out.print("In: ", inn.to_a.inspect, " ") unless inn.empty?
|
@@ -184,7 +186,7 @@ module Speculation
|
|
184
186
|
|
185
187
|
prob.each do |k, v|
|
186
188
|
unless [:path, :pred, :val, :reason, :via, :in].include?(k)
|
187
|
-
out.print("\n\t ", k.inspect, v.
|
189
|
+
out.print("\n\t ", k.inspect, PP.pp(v, String.new))
|
188
190
|
end
|
189
191
|
end
|
190
192
|
|
@@ -192,7 +194,7 @@ module Speculation
|
|
192
194
|
end
|
193
195
|
|
194
196
|
ed.each do |k, v|
|
195
|
-
out.puts(k,
|
197
|
+
out.puts("#{k} #{PP.pp(v, String.new)}") unless k == ns(:problems)
|
196
198
|
end
|
197
199
|
|
198
200
|
nil
|
@@ -226,7 +228,7 @@ module Speculation
|
|
226
228
|
Gen.such_that(->(x) { valid?(spec, x) }, g)
|
227
229
|
else
|
228
230
|
raise Speculation::Error.new("unable to construct gen at: #{path.inspect} for: #{spec.inspect}",
|
229
|
-
:failure
|
231
|
+
ns(:failure) => :no_gen, ns(:path) => path)
|
230
232
|
end
|
231
233
|
end
|
232
234
|
|
@@ -246,10 +248,9 @@ module Speculation
|
|
246
248
|
# @return [Proc]
|
247
249
|
def self.gen(spec, overrides = nil)
|
248
250
|
spec = Identifier(spec)
|
249
|
-
gensub(spec, overrides, [], :recursion_limit
|
251
|
+
gensub(spec, overrides, [], ns(:recursion_limit) => recursion_limit)
|
250
252
|
end
|
251
253
|
|
252
|
-
# rubocop:disable Style/MethodName
|
253
254
|
# @private
|
254
255
|
def self.Identifier(x)
|
255
256
|
case x
|
@@ -258,19 +259,17 @@ module Speculation
|
|
258
259
|
else x
|
259
260
|
end
|
260
261
|
end
|
261
|
-
# rubocop:enable Style/MethodName
|
262
262
|
|
263
263
|
# Given a namespace-qualified symbol key, and a spec, spec name, predicate or
|
264
264
|
# regex-op makes an entry in the registry mapping key to the spec
|
265
265
|
# @param key [Symbol] namespace-qualified symbol
|
266
266
|
# @param spec [Spec, Symbol, Proc, Hash] a spec, spec name, predicate or regex-op
|
267
|
-
# @return [Symbol,
|
267
|
+
# @return [Symbol, Method]
|
268
268
|
def self.def(key, spec)
|
269
269
|
key = Identifier(key)
|
270
270
|
|
271
|
-
unless Utils.ident?(key) && key.namespace
|
272
|
-
raise ArgumentError,
|
273
|
-
"key must be a namespaced Symbol, e.g. #{:my_spec.ns}, given #{key}, or a Speculation::Identifier"
|
271
|
+
unless Utils.ident?(key) && (!key.is_a?(Symbol) || NamespacedSymbols.namespace(key))
|
272
|
+
raise ArgumentError, "key must be a namespaced Symbol, e.g. #{ns(:my_spec)}, or a Method"
|
274
273
|
end
|
275
274
|
|
276
275
|
spec = if spec?(spec) || regex?(spec) || registry[spec]
|
@@ -280,10 +279,10 @@ module Speculation
|
|
280
279
|
end
|
281
280
|
|
282
281
|
@registry_ref.swap do |reg|
|
283
|
-
reg.merge(key => with_name(spec, key))
|
282
|
+
reg.merge(key => with_name(spec, key)).freeze
|
284
283
|
end
|
285
284
|
|
286
|
-
key
|
285
|
+
key.is_a?(Identifier) ? key.get_method : key
|
287
286
|
end
|
288
287
|
|
289
288
|
# @return [Hash] the registry hash
|
@@ -336,8 +335,8 @@ module Speculation
|
|
336
335
|
#
|
337
336
|
# The :req key array supports 'and_keys' and 'or_keys' for key groups:
|
338
337
|
#
|
339
|
-
# S.keys(req: [:x
|
340
|
-
# opt: [:z
|
338
|
+
# S.keys(req: [ns(:x), ns(:y), S.or_keys(ns(:secret), S.and_keys(ns(:user), ns(:pwd)))],
|
339
|
+
# opt: [ns(:z)])
|
341
340
|
#
|
342
341
|
# There are also _un versions of :req and :opt. These allow you to connect
|
343
342
|
# unqualified keys to specs. In each case, fully qualfied keywords are passed,
|
@@ -367,12 +366,12 @@ module Speculation
|
|
367
366
|
|
368
367
|
# @see keys
|
369
368
|
def self.or_keys(*ks)
|
370
|
-
[:or
|
369
|
+
[ns(:or), *ks]
|
371
370
|
end
|
372
371
|
|
373
372
|
# @see keys
|
374
373
|
def self.and_keys(*ks)
|
375
|
-
[:and
|
374
|
+
[ns(:and), *ks]
|
376
375
|
end
|
377
376
|
|
378
377
|
# @param key_preds [Hash] Takes key+pred hash
|
@@ -442,8 +441,8 @@ module Speculation
|
|
442
441
|
# @param options [Hash]
|
443
442
|
# @return [Spec] spec that validates associative collections
|
444
443
|
def self.every_kv(kpred, vpred, options)
|
445
|
-
every(tuple(kpred, vpred), :kfn
|
446
|
-
:into
|
444
|
+
every(tuple(kpred, vpred), ns(:kfn) => ->(_i, v) { v.first },
|
445
|
+
:into => {},
|
447
446
|
**options)
|
448
447
|
end
|
449
448
|
|
@@ -459,7 +458,7 @@ module Speculation
|
|
459
458
|
# @param opts [Hash]
|
460
459
|
# @return [Spec]
|
461
460
|
def self.coll_of(pred, opts = {})
|
462
|
-
every(pred, :conform_all
|
461
|
+
every(pred, ns(:conform_all) => true, **opts)
|
463
462
|
end
|
464
463
|
|
465
464
|
# Returns a spec for a hash whose keys satisfy kpred and vals satisfy vpred.
|
@@ -476,8 +475,8 @@ module Speculation
|
|
476
475
|
# @param options [Hash]
|
477
476
|
# @return [Spec]
|
478
477
|
def self.hash_of(kpred, vpred, options = {})
|
479
|
-
every_kv(kpred, vpred, :kind
|
480
|
-
:conform_all
|
478
|
+
every_kv(kpred, vpred, :kind => Utils.method(:hash?),
|
479
|
+
ns(:conform_all) => true,
|
481
480
|
**options)
|
482
481
|
end
|
483
482
|
|
@@ -499,7 +498,7 @@ module Speculation
|
|
499
498
|
# @return [Hash] regex op that matches zero or one value matching pred. Produces a
|
500
499
|
# single value (not a collection) if matched.
|
501
500
|
def self.zero_or_one(pred)
|
502
|
-
_alt([pred, accept(:nil
|
501
|
+
_alt([pred, accept(ns(:nil))], nil)
|
503
502
|
end
|
504
503
|
|
505
504
|
# @param kv_specs [Hash] key+pred pairs
|
@@ -530,14 +529,14 @@ module Speculation
|
|
530
529
|
# resulting value to the conjunction of the predicates, and any conforming
|
531
530
|
# they might perform.
|
532
531
|
def self.constrained(re, *preds)
|
533
|
-
{ :op
|
532
|
+
{ ns(:op) => ns(:amp), :p1 => re, :predicates => preds }
|
534
533
|
end
|
535
534
|
|
536
|
-
# @
|
535
|
+
# @yield [value] predicate function with the semantics of conform i.e. it should
|
537
536
|
# return either a (possibly converted) value or :"Speculation/invalid"
|
538
|
-
# @return [Spec] a spec that uses
|
539
|
-
def self.conformer(
|
540
|
-
spec_impl(
|
537
|
+
# @return [Spec] a spec that uses block as a predicate/conformer.
|
538
|
+
def self.conformer(&pred)
|
539
|
+
spec_impl(pred, true)
|
541
540
|
end
|
542
541
|
|
543
542
|
# Takes :args :ret and (optional) :block and :fn kwargs whose values are preds and returns a spec
|
@@ -556,7 +555,7 @@ module Speculation
|
|
556
555
|
# @return [Spec]
|
557
556
|
# @see fdef See 'fdef' for a single operation that creates an fspec and registers it, as well as a full description of :args, :block, :ret and :fn
|
558
557
|
def self.fspec(args: nil, ret: nil, fn: nil, block: nil, gen: nil)
|
559
|
-
FSpec.new(:
|
558
|
+
FSpec.new(:args => spec(args), :ret => spec(ret), :fn => spec(fn), :block => spec(block)).tap do |spec|
|
560
559
|
spec.gen = gen
|
561
560
|
end
|
562
561
|
end
|
@@ -575,8 +574,8 @@ module Speculation
|
|
575
574
|
# S.fdef(Hash.method(:[]),
|
576
575
|
# args: S.alt(
|
577
576
|
# hash: Hash,
|
578
|
-
# array_of_pairs: S.coll_of(S.tuple(
|
579
|
-
# kvs: S.constrained(S.one_or_more(
|
577
|
+
# array_of_pairs: S.coll_of(S.tuple(ns(S, :any), ns(S, :any)), kind: Array),
|
578
|
+
# kvs: S.constrained(S.one_or_more(ns(S, :any)), -> (kvs) { kvs.count.even? })
|
580
579
|
# ),
|
581
580
|
# ret: Hash
|
582
581
|
# )
|
@@ -589,13 +588,12 @@ module Speculation
|
|
589
588
|
# @option spec :fn a spec of the relationship between args and ret - the value passed is
|
590
589
|
# { args: conformed_args, block: given_block, ret: conformed_ret } and is expected to contain
|
591
590
|
# predicates that relate those values
|
592
|
-
# @return [
|
593
|
-
# key in the spec registry.
|
591
|
+
# @return [Method] the method spec'ed
|
594
592
|
# @note Note that :fn specs require the presence of :args and :ret specs to conform values, and so :fn
|
595
593
|
# specs will be ignored if :args or :ret are missing.
|
596
594
|
def self.fdef(method, spec)
|
597
|
-
|
598
|
-
|
595
|
+
self.def(Identifier(method), fspec(spec))
|
596
|
+
method
|
599
597
|
end
|
600
598
|
|
601
599
|
# @param spec
|
@@ -638,14 +636,14 @@ module Speculation
|
|
638
636
|
fspec ||= get_spec(method)
|
639
637
|
raise ArgumentError, "No fspec found for #{method}" unless fspec
|
640
638
|
|
641
|
-
Gen.sample(gen(fspec.
|
639
|
+
Gen.sample(gen(fspec.args), n).map { |args| [args, method.call(*args)] }
|
642
640
|
end
|
643
641
|
|
644
642
|
### impl ###
|
645
643
|
|
646
644
|
# @private
|
647
645
|
def self.recur_limit?(rmap, id, path, k)
|
648
|
-
rmap[id] > rmap[:recursion_limit
|
646
|
+
rmap[id] > rmap[ns(:recursion_limit)] &&
|
649
647
|
path.include?(k)
|
650
648
|
end
|
651
649
|
|
@@ -663,11 +661,11 @@ module Speculation
|
|
663
661
|
if spec
|
664
662
|
conform(spec, x)
|
665
663
|
elsif pred.is_a?(Module) || pred.is_a?(::Regexp)
|
666
|
-
pred === x ? x : :invalid
|
664
|
+
pred === x ? x : ns(:invalid)
|
667
665
|
elsif pred.is_a?(Set)
|
668
|
-
pred.include?(x) ? x : :invalid
|
666
|
+
pred.include?(x) ? x : ns(:invalid)
|
669
667
|
elsif pred.respond_to?(:call)
|
670
|
-
pred.call(x) ? x : :invalid
|
668
|
+
pred.call(x) ? x : ns(:invalid)
|
671
669
|
else
|
672
670
|
raise "#{pred} is not a class, proc, set or regexp"
|
673
671
|
end
|
@@ -685,11 +683,11 @@ module Speculation
|
|
685
683
|
|
686
684
|
if spec?(spec)
|
687
685
|
name = spec_name(spec)
|
688
|
-
via =
|
686
|
+
via = Utils.conj(via, name) if name
|
689
687
|
|
690
688
|
spec.explain(path, via, inn, value)
|
691
689
|
else
|
692
|
-
[{ :path => path, :val => value, :via => via, :in => inn, :pred => pred }]
|
690
|
+
[{ :path => path, :val => value, :via => via, :in => inn, :pred => [pred, [value]] }]
|
693
691
|
end
|
694
692
|
end
|
695
693
|
|
@@ -731,7 +729,7 @@ module Speculation
|
|
731
729
|
p = reg_resolve!(p)
|
732
730
|
|
733
731
|
id, op, ps, ks, p1, p2, ret, id, gen = p.values_at(
|
734
|
-
:id, :op
|
732
|
+
:id, ns(:op), :predicates, :keys, :p1, :p2, :return_value, :id, ns(:gen)
|
735
733
|
) if regex?(p)
|
736
734
|
|
737
735
|
id = p.id if spec?(p)
|
@@ -743,9 +741,9 @@ module Speculation
|
|
743
741
|
preds.zip(keys).map do |pred, k|
|
744
742
|
unless rmap && id && k && recur_limit?(rmap, id, path, k)
|
745
743
|
if id
|
746
|
-
Gen.delay { Speculation.re_gen(pred, overrides, k ?
|
744
|
+
Gen.delay { Speculation.re_gen(pred, overrides, k ? Utils.conj(path, k) : path, rmap) }
|
747
745
|
else
|
748
|
-
re_gen(pred, overrides, k ?
|
746
|
+
re_gen(pred, overrides, k ? Utils.conj(path, k) : path, rmap)
|
749
747
|
end
|
750
748
|
end
|
751
749
|
end
|
@@ -767,8 +765,8 @@ module Speculation
|
|
767
765
|
|
768
766
|
if p
|
769
767
|
case op
|
770
|
-
when :accept
|
771
|
-
if ret == :nil
|
768
|
+
when ns(:accept)
|
769
|
+
if ret == ns(:nil)
|
772
770
|
->(_rantly) { [] }
|
773
771
|
else
|
774
772
|
->(_rantly) { [ret] }
|
@@ -777,9 +775,9 @@ module Speculation
|
|
777
775
|
g = gensub(p, overrides, path, rmap)
|
778
776
|
|
779
777
|
->(rantly) { [g.call(rantly)] }
|
780
|
-
when :amp
|
778
|
+
when ns(:amp)
|
781
779
|
re_gen(p1, overrides, path, rmap)
|
782
|
-
when :pcat
|
780
|
+
when ns(:pcat)
|
783
781
|
gens = ggens.call(ps, ks)
|
784
782
|
|
785
783
|
if gens.all?
|
@@ -787,11 +785,11 @@ module Speculation
|
|
787
785
|
gens.flat_map { |gg| gg.call(rantly) }
|
788
786
|
end
|
789
787
|
end
|
790
|
-
when :alt
|
788
|
+
when ns(:alt)
|
791
789
|
gens = ggens.call(ps, ks).compact
|
792
790
|
|
793
791
|
->(rantly) { rantly.branch(*gens) } unless gens.empty?
|
794
|
-
when :rep
|
792
|
+
when ns(:rep)
|
795
793
|
if recur_limit?(rmap, id, [id], id)
|
796
794
|
->(_rantly) { [] }
|
797
795
|
else
|
@@ -812,11 +810,11 @@ module Speculation
|
|
812
810
|
x, *xs = data
|
813
811
|
|
814
812
|
if data.empty?
|
815
|
-
return :invalid
|
813
|
+
return ns(:invalid) unless accept_nil?(regex)
|
816
814
|
|
817
815
|
return_value = preturn(regex)
|
818
816
|
|
819
|
-
if return_value == :nil
|
817
|
+
if return_value == ns(:nil)
|
820
818
|
nil
|
821
819
|
else
|
822
820
|
return_value
|
@@ -827,7 +825,7 @@ module Speculation
|
|
827
825
|
if dp
|
828
826
|
re_conform(dp, xs)
|
829
827
|
else
|
830
|
-
:invalid
|
828
|
+
ns(:invalid)
|
831
829
|
end
|
832
830
|
end
|
833
831
|
end
|
@@ -845,22 +843,22 @@ module Speculation
|
|
845
843
|
end
|
846
844
|
|
847
845
|
if accept?(p)
|
848
|
-
if p[:op
|
849
|
-
return op_explain(p, path, via,
|
846
|
+
if p[ns(:op)] == ns(:pcat)
|
847
|
+
return op_explain(p, path, via, Utils.conj(inn, index), input[index..-1])
|
850
848
|
else
|
851
849
|
return [{ :path => path,
|
852
850
|
:reason => "Extra input",
|
853
851
|
:val => input,
|
854
852
|
:via => via,
|
855
|
-
:in =>
|
853
|
+
:in => Utils.conj(inn, index) }]
|
856
854
|
end
|
857
855
|
else
|
858
|
-
return op_explain(p, path, via,
|
856
|
+
return op_explain(p, path, via, Utils.conj(inn, index), input[index..-1]) ||
|
859
857
|
[{ :path => path,
|
860
858
|
:reason => "Extra input",
|
861
859
|
:val => input,
|
862
860
|
:via => via,
|
863
|
-
:in =>
|
861
|
+
:in => Utils.conj(inn, index) }]
|
864
862
|
end
|
865
863
|
end
|
866
864
|
|
@@ -908,7 +906,7 @@ module Speculation
|
|
908
906
|
if Utils.ident?(spec)
|
909
907
|
spec
|
910
908
|
elsif regex?(spec)
|
911
|
-
spec.merge(:name
|
909
|
+
spec.merge(ns(:name) => name)
|
912
910
|
else
|
913
911
|
spec.tap { |s| s.name = name }
|
914
912
|
end
|
@@ -918,7 +916,7 @@ module Speculation
|
|
918
916
|
if Utils.ident?(spec)
|
919
917
|
spec
|
920
918
|
elsif regex?(spec)
|
921
|
-
spec[:name
|
919
|
+
spec[ns(:name)]
|
922
920
|
elsif spec.respond_to?(:name)
|
923
921
|
spec.name
|
924
922
|
end
|
@@ -955,7 +953,7 @@ module Speculation
|
|
955
953
|
x = dt(pred, x)
|
956
954
|
|
957
955
|
if invalid?(x)
|
958
|
-
:invalid
|
956
|
+
ns(:invalid)
|
959
957
|
elsif preds.empty?
|
960
958
|
x
|
961
959
|
else
|
@@ -979,12 +977,12 @@ module Speculation
|
|
979
977
|
### regex ###
|
980
978
|
|
981
979
|
def accept(x)
|
982
|
-
{ :op
|
980
|
+
{ ns(:op) => ns(:accept), :return_value => x }
|
983
981
|
end
|
984
982
|
|
985
983
|
def accept?(hash)
|
986
984
|
if hash.is_a?(Hash)
|
987
|
-
hash[:op
|
985
|
+
hash[ns(:op)] == ns(:accept)
|
988
986
|
end
|
989
987
|
end
|
990
988
|
|
@@ -997,14 +995,14 @@ module Speculation
|
|
997
995
|
return unless regex[:predicates].all?
|
998
996
|
|
999
997
|
unless accept?(predicate)
|
1000
|
-
return { :op
|
998
|
+
return { ns(:op) => ns(:pcat),
|
1001
999
|
:predicates => regex[:predicates],
|
1002
1000
|
:keys => keys,
|
1003
1001
|
:return_value => regex[:return_value] }
|
1004
1002
|
end
|
1005
1003
|
|
1006
1004
|
val = keys ? { key => predicate[:return_value] } : predicate[:return_value]
|
1007
|
-
return_value = regex[:return_value]
|
1005
|
+
return_value = Utils.conj(regex[:return_value], val)
|
1008
1006
|
|
1009
1007
|
if rest_predicates
|
1010
1008
|
pcat(:predicates => rest_predicates,
|
@@ -1018,10 +1016,10 @@ module Speculation
|
|
1018
1016
|
def rep(p1, p2, return_value, splice)
|
1019
1017
|
return unless p1
|
1020
1018
|
|
1021
|
-
regex = { :op
|
1019
|
+
regex = { ns(:op) => ns(:rep), :p2 => p2, :splice => splice, :id => SecureRandom.uuid }
|
1022
1020
|
|
1023
1021
|
if accept?(p1)
|
1024
|
-
regex.merge(:p1 => p2, :return_value =>
|
1022
|
+
regex.merge(:p1 => p2, :return_value => Utils.conj(return_value, p1[:return_value]))
|
1025
1023
|
else
|
1026
1024
|
regex.merge(:p1 => p1, :return_value => return_value)
|
1027
1025
|
end
|
@@ -1037,13 +1035,13 @@ module Speculation
|
|
1037
1035
|
end
|
1038
1036
|
|
1039
1037
|
def _alt(predicates, keys)
|
1040
|
-
predicates, keys = filter_alt(predicates, keys,
|
1038
|
+
predicates, keys = filter_alt(predicates, keys, &Utils.method(:itself))
|
1041
1039
|
return unless predicates
|
1042
1040
|
|
1043
1041
|
predicate, *rest_predicates = predicates
|
1044
1042
|
key, *_rest_keys = keys
|
1045
1043
|
|
1046
|
-
return_value = { :op
|
1044
|
+
return_value = { ns(:op) => ns(:alt), :predicates => predicates, :keys => keys }
|
1047
1045
|
return return_value unless rest_predicates.empty?
|
1048
1046
|
|
1049
1047
|
return predicate unless key
|
@@ -1061,24 +1059,24 @@ module Speculation
|
|
1061
1059
|
end
|
1062
1060
|
|
1063
1061
|
def no_ret?(p1, pret)
|
1064
|
-
return true if pret == :nil
|
1062
|
+
return true if pret == ns(:nil)
|
1065
1063
|
|
1066
1064
|
regex = reg_resolve!(p1)
|
1067
|
-
op = regex[:op
|
1065
|
+
op = regex[ns(:op)]
|
1068
1066
|
|
1069
|
-
[:rep
|
1067
|
+
[ns(:rep), ns(:pcat)].include?(op) && pret.empty? || nil
|
1070
1068
|
end
|
1071
1069
|
|
1072
1070
|
def accept_nil?(regex)
|
1073
1071
|
regex = reg_resolve!(regex)
|
1074
1072
|
return unless regex?(regex)
|
1075
1073
|
|
1076
|
-
case regex[:op
|
1077
|
-
when :accept
|
1078
|
-
when :pcat
|
1079
|
-
when :alt
|
1080
|
-
when :rep
|
1081
|
-
when :amp
|
1074
|
+
case regex[ns(:op)]
|
1075
|
+
when ns(:accept) then true
|
1076
|
+
when ns(:pcat) then regex[:predicates].all?(&method(:accept_nil?))
|
1077
|
+
when ns(:alt) then regex[:predicates].any?(&method(:accept_nil?))
|
1078
|
+
when ns(:rep) then (regex[:p1] == regex[:p2]) || accept_nil?(regex[:p1])
|
1079
|
+
when ns(:amp)
|
1082
1080
|
p1 = regex[:p1]
|
1083
1081
|
|
1084
1082
|
return false unless accept_nil?(p1)
|
@@ -1086,7 +1084,7 @@ module Speculation
|
|
1086
1084
|
no_ret?(p1, preturn(p1)) ||
|
1087
1085
|
!invalid?(and_preds(preturn(p1), regex[:predicates]))
|
1088
1086
|
else
|
1089
|
-
raise "Unexpected #{:op
|
1087
|
+
raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
|
1090
1088
|
end
|
1091
1089
|
end
|
1092
1090
|
|
@@ -1097,23 +1095,23 @@ module Speculation
|
|
1097
1095
|
p0, *_pr = regex[:predicates]
|
1098
1096
|
k, *ks = regex[:keys]
|
1099
1097
|
|
1100
|
-
case regex[:op
|
1101
|
-
when :accept
|
1102
|
-
when :pcat
|
1103
|
-
when :rep
|
1104
|
-
when :amp
|
1098
|
+
case regex[ns(:op)]
|
1099
|
+
when ns(:accept) then regex[:return_value]
|
1100
|
+
when ns(:pcat) then add_ret(p0, regex[:return_value], k)
|
1101
|
+
when ns(:rep) then add_ret(regex[:p1], regex[:return_value], k)
|
1102
|
+
when ns(:amp)
|
1105
1103
|
pret = preturn(regex[:p1])
|
1106
1104
|
|
1107
1105
|
if no_ret?(regex[:p1], pret)
|
1108
|
-
:nil
|
1106
|
+
ns(:nil)
|
1109
1107
|
else
|
1110
1108
|
and_preds(pret, regex[:predicates])
|
1111
1109
|
end
|
1112
|
-
when :alt
|
1110
|
+
when ns(:alt)
|
1113
1111
|
ps, ks = filter_alt(regex[:predicates], regex[:keys], &method(:accept_nil?))
|
1114
1112
|
|
1115
1113
|
r = if ps.first.nil?
|
1116
|
-
:nil
|
1114
|
+
ns(:nil)
|
1117
1115
|
else
|
1118
1116
|
preturn(ps.first)
|
1119
1117
|
end
|
@@ -1124,7 +1122,7 @@ module Speculation
|
|
1124
1122
|
r
|
1125
1123
|
end
|
1126
1124
|
else
|
1127
|
-
raise "Unexpected #{:op
|
1125
|
+
raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
|
1128
1126
|
end
|
1129
1127
|
end
|
1130
1128
|
|
@@ -1140,22 +1138,22 @@ module Speculation
|
|
1140
1138
|
else
|
1141
1139
|
val = key ? { key => return_value } : return_value
|
1142
1140
|
|
1143
|
-
regex[:splice] ? Utils.into(r, val) :
|
1141
|
+
regex[:splice] ? Utils.into(r, val) : Utils.conj(r, val)
|
1144
1142
|
end
|
1145
1143
|
end
|
1146
1144
|
|
1147
|
-
case regex[:op
|
1148
|
-
when :accept
|
1145
|
+
case regex[ns(:op)]
|
1146
|
+
when ns(:accept), ns(:alt), ns(:amp)
|
1149
1147
|
return_value = preturn(regex)
|
1150
1148
|
|
1151
|
-
if return_value == :nil
|
1149
|
+
if return_value == ns(:nil)
|
1152
1150
|
r
|
1153
1151
|
else
|
1154
|
-
|
1152
|
+
Utils.conj(r, key ? { key => return_value } : return_value)
|
1155
1153
|
end
|
1156
|
-
when :pcat
|
1154
|
+
when ns(:pcat), ns(:rep) then prop.call
|
1157
1155
|
else
|
1158
|
-
raise "Unexpected #{:op
|
1156
|
+
raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
|
1159
1157
|
end
|
1160
1158
|
end
|
1161
1159
|
|
@@ -1178,9 +1176,9 @@ module Speculation
|
|
1178
1176
|
pred, *rest_preds = predicates
|
1179
1177
|
key, *rest_keys = keys
|
1180
1178
|
|
1181
|
-
case regex[:op
|
1182
|
-
when :accept
|
1183
|
-
when :pcat
|
1179
|
+
case regex[ns(:op)]
|
1180
|
+
when ns(:accept) then nil
|
1181
|
+
when ns(:pcat)
|
1184
1182
|
regex1 = pcat(:predicates => [deriv(pred, value), *rest_preds], :keys => keys, :return_value => return_value)
|
1185
1183
|
regex2 = nil
|
1186
1184
|
|
@@ -1192,9 +1190,9 @@ module Speculation
|
|
1192
1190
|
end
|
1193
1191
|
|
1194
1192
|
alt2(regex1, regex2)
|
1195
|
-
when :alt
|
1193
|
+
when ns(:alt)
|
1196
1194
|
_alt(predicates.map { |p| deriv(p, value) }, keys)
|
1197
|
-
when :rep
|
1195
|
+
when ns(:rep)
|
1198
1196
|
regex1 = rep(deriv(p1, value), p2, return_value, splice)
|
1199
1197
|
regex2 = nil
|
1200
1198
|
|
@@ -1203,24 +1201,25 @@ module Speculation
|
|
1203
1201
|
end
|
1204
1202
|
|
1205
1203
|
alt2(regex1, regex2)
|
1206
|
-
when :amp
|
1204
|
+
when ns(:amp)
|
1207
1205
|
p1 = deriv(p1, value)
|
1208
1206
|
return unless p1
|
1209
1207
|
|
1210
|
-
if p1[:op
|
1208
|
+
if p1[ns(:op)] == ns(:accept)
|
1211
1209
|
ret = and_preds(preturn(p1), predicates)
|
1212
1210
|
accept(ret) unless invalid?(ret)
|
1213
1211
|
else
|
1214
1212
|
constrained(p1, *predicates)
|
1215
1213
|
end
|
1216
1214
|
else
|
1217
|
-
raise "Unexpected #{:op
|
1215
|
+
raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
|
1218
1216
|
end
|
1219
1217
|
end
|
1220
1218
|
|
1221
|
-
def insufficient(path, via, inn)
|
1219
|
+
def insufficient(pred, path, via, inn)
|
1222
1220
|
[{ :path => path,
|
1223
1221
|
:reason => "Insufficient input",
|
1222
|
+
:pred => [pred, []],
|
1224
1223
|
:val => [],
|
1225
1224
|
:via => via,
|
1226
1225
|
:in => inn }]
|
@@ -1235,20 +1234,20 @@ module Speculation
|
|
1235
1234
|
|
1236
1235
|
unless regex?(p)
|
1237
1236
|
if input.empty?
|
1238
|
-
return insufficient(path, via, inn)
|
1237
|
+
return insufficient(p, path, via, inn)
|
1239
1238
|
else
|
1240
1239
|
return explain1(p, path, via, inn, x)
|
1241
1240
|
end
|
1242
1241
|
end
|
1243
1242
|
|
1244
|
-
case p[:op
|
1245
|
-
when :accept
|
1246
|
-
when :amp
|
1243
|
+
case p[ns(:op)]
|
1244
|
+
when ns(:accept) then nil
|
1245
|
+
when ns(:amp)
|
1247
1246
|
if input.empty?
|
1248
1247
|
if accept_nil?(p[:p1])
|
1249
1248
|
explain_pred_list(p[:predicates], path, via, inn, preturn(p[:p1]))
|
1250
1249
|
else
|
1251
|
-
insufficient(path, via, inn)
|
1250
|
+
insufficient(p, path, via, inn)
|
1252
1251
|
end
|
1253
1252
|
else
|
1254
1253
|
p1 = deriv(p[:p1], x)
|
@@ -1259,46 +1258,46 @@ module Speculation
|
|
1259
1258
|
op_explain(p[:p1], path, via, inn, input)
|
1260
1259
|
end
|
1261
1260
|
end
|
1262
|
-
when :pcat
|
1261
|
+
when ns(:pcat)
|
1263
1262
|
pks = p[:predicates].zip(p[:keys] || [])
|
1264
1263
|
pred, k = if pks.count == 1
|
1265
1264
|
pks.first
|
1266
1265
|
else
|
1267
1266
|
pks.lazy.reject { |(predicate, _)| accept_nil?(predicate) }.first
|
1268
1267
|
end
|
1269
|
-
path =
|
1268
|
+
path = Utils.conj(path, k) if k
|
1270
1269
|
|
1271
1270
|
if input.empty? && !pred
|
1272
|
-
insufficient(path, via, inn)
|
1271
|
+
insufficient(pred, path, via, inn)
|
1273
1272
|
else
|
1274
1273
|
op_explain(pred, path, via, inn, input)
|
1275
1274
|
end
|
1276
|
-
when :alt
|
1277
|
-
return insufficient(path, via, inn) if input.empty?
|
1275
|
+
when ns(:alt)
|
1276
|
+
return insufficient(p, path, via, inn) if input.empty?
|
1278
1277
|
|
1279
1278
|
probs = p[:predicates].zip(p[:keys]).flat_map { |(predicate, key)|
|
1280
|
-
op_explain(predicate, key ?
|
1279
|
+
op_explain(predicate, key ? Utils.conj(path, key) : path, via, inn, input)
|
1281
1280
|
}
|
1282
1281
|
|
1283
1282
|
probs.compact
|
1284
|
-
when :rep
|
1283
|
+
when ns(:rep)
|
1285
1284
|
op_explain(p[:p1], path, via, inn, input)
|
1286
1285
|
else
|
1287
|
-
raise "Unexpected #{:op
|
1286
|
+
raise "Unexpected #{ns(:op)} #{p[ns(:op)]}"
|
1288
1287
|
end
|
1289
1288
|
end
|
1290
1289
|
|
1291
1290
|
# Resets the spec registry to only builtin specs
|
1292
1291
|
def reset_registry!
|
1293
1292
|
builtins = {
|
1294
|
-
:any
|
1295
|
-
:boolean
|
1296
|
-
:positive_integer
|
1293
|
+
ns(:any) => with_gen(Utils.constantly(true), ->(r) { r.branch(*Gen::GEN_BUILTINS.values) }),
|
1294
|
+
ns(:boolean) => Set[true, false],
|
1295
|
+
ns(:positive_integer) => with_gen(self.and(Integer, ->(x) { x > 0 }), ->(r) { r.range(1) }),
|
1297
1296
|
# Rantly#positive_integer is actually a natural integer
|
1298
|
-
:natural_integer
|
1299
|
-
:negative_integer
|
1300
|
-
:empty
|
1301
|
-
}
|
1297
|
+
ns(:natural_integer) => with_gen(self.and(Integer, ->(x) { x >= 0 }), :positive_integer.to_proc),
|
1298
|
+
ns(:negative_integer) => with_gen(self.and(Integer, ->(x) { x < 0 }), ->(r) { r.range(nil, -1) }),
|
1299
|
+
ns(:empty) => with_gen(Utils.method(:empty?), Utils.constantly([]))
|
1300
|
+
}.freeze
|
1302
1301
|
|
1303
1302
|
@registry_ref.reset(builtins)
|
1304
1303
|
end
|