speculation 0.1.0 → 0.2.0
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 +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
|