speculation 0.4.0 → 0.4.2
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/README.md +154 -53
- data/examples/codebreaker.rb +8 -8
- data/examples/game_of_life.rb +120 -0
- data/examples/sinatra-web-app/Gemfile +1 -1
- data/examples/sinatra-web-app/Gemfile.lock +3 -5
- data/examples/sinatra-web-app/app.rb +5 -5
- data/examples/spec_guide.rb +33 -28
- data/lib/speculation.rb +175 -145
- data/lib/speculation/gen.rb +11 -0
- data/lib/speculation/namespaced_symbols.rb +4 -2
- data/lib/speculation/predicates.rb +48 -0
- data/lib/speculation/spec.rb +4 -0
- data/lib/speculation/spec/and_spec.rb +6 -2
- data/lib/speculation/spec/every_spec.rb +41 -18
- data/lib/speculation/spec/f_spec.rb +8 -4
- data/lib/speculation/spec/hash_spec.rb +49 -28
- data/lib/speculation/spec/merge_spec.rb +6 -2
- data/lib/speculation/spec/nilable_spec.rb +8 -4
- data/lib/speculation/spec/nonconforming_spec.rb +5 -1
- data/lib/speculation/spec/or_spec.rb +10 -3
- data/lib/speculation/spec/predicate_spec.rb +15 -4
- data/lib/speculation/spec/regex_spec.rb +8 -4
- data/lib/speculation/spec/tuple_spec.rb +14 -6
- data/lib/speculation/test.rb +24 -24
- data/lib/speculation/utils.rb +4 -46
- data/lib/speculation/version.rb +1 -1
- metadata +5 -3
@@ -1,9 +1,7 @@
|
|
1
|
-
|
2
|
-
remote:
|
3
|
-
revision: ae3ee095765816a8d5bfa5b29c808439f04633b0
|
4
|
-
ref: ae3ee09
|
1
|
+
PATH
|
2
|
+
remote: /Users/jamie/Projects/speculation
|
5
3
|
specs:
|
6
|
-
speculation (0.
|
4
|
+
speculation (0.4.1)
|
7
5
|
concurrent-ruby (~> 1.0)
|
8
6
|
rantly (~> 1.0)
|
9
7
|
|
@@ -64,7 +64,7 @@ module User
|
|
64
64
|
|
65
65
|
def self.serialize_validation_errors(user)
|
66
66
|
data = S.explain_data(ns(:user), user)
|
67
|
-
data[
|
67
|
+
data[:problems].map { |problem| Validation.serialize_problem(problem) }
|
68
68
|
end
|
69
69
|
|
70
70
|
def self.fake
|
@@ -121,7 +121,7 @@ module User
|
|
121
121
|
|
122
122
|
USER_ERROR_MESSAGE_MAP = {
|
123
123
|
[] => {
|
124
|
-
S::
|
124
|
+
S::Predicates.method(:key?) => ->(args) {
|
125
125
|
key = args.first
|
126
126
|
if key == S.or_keys(:email, :username)
|
127
127
|
"email or username is required"
|
@@ -146,8 +146,8 @@ module User
|
|
146
146
|
}
|
147
147
|
end
|
148
148
|
|
149
|
-
S.def ns(:email), S.with_gen(S.and(String, Validation::EMAIL_REGEX)
|
150
|
-
S.def ns(:username), S.with_gen(S.and(String, Validation.method(:validate_username_length))
|
151
|
-
S.def ns(:password), S.with_gen(S.and(String, Validation.method(:validate_password_length), Validation.method(:validate_password_complexity))
|
149
|
+
S.def ns(:email), S.with_gen(S.and(String, Validation::EMAIL_REGEX)) { Generators.method(:email) }
|
150
|
+
S.def ns(:username), S.with_gen(S.and(String, Validation.method(:validate_username_length))) { Generators.method(:username) }
|
151
|
+
S.def ns(:password), S.with_gen(S.and(String, Validation.method(:validate_password_length), Validation.method(:validate_password_complexity))) { Generators.method(:password) }
|
152
152
|
S.def ns(:user), S.keys(:req_un => [S.or_keys(ns(:email), ns(:username)), ns(:password)])
|
153
153
|
end
|
data/examples/spec_guide.rb
CHANGED
@@ -155,7 +155,7 @@ S.explain ns(:name_or_id), :foo
|
|
155
155
|
# as a string or explain_data to receive the errors as data.
|
156
156
|
|
157
157
|
S.explain_data ns(:name_or_id), :foo
|
158
|
-
# => {:
|
158
|
+
# => {:problems=>
|
159
159
|
# [{:path=>[:name],
|
160
160
|
# :val=>:foo,
|
161
161
|
# :via=>[:"Object/name_or_id"],
|
@@ -214,8 +214,8 @@ S.valid? ns(:person), ns(:first_name) => "Elon", ns(:last_name) => "Musk", ns(:e
|
|
214
214
|
|
215
215
|
# Fails required key check
|
216
216
|
S.explain ns(:person), ns(:first_name) => "Elon"
|
217
|
-
# >> val: {:"Object/first_name"=>"Elon"} fails spec: :"Object/person" predicate: [#<Method: Speculation::
|
218
|
-
# >> val: {:"Object/first_name"=>"Elon"} fails spec: :"Object/person" predicate: [#<Method: Speculation::
|
217
|
+
# >> val: {:"Object/first_name"=>"Elon"} fails spec: :"Object/person" predicate: [#<Method: Speculation::Predicates.key?>, [:"Object/last_name"]]
|
218
|
+
# >> val: {:"Object/first_name"=>"Elon"} fails spec: :"Object/person" predicate: [#<Method: Speculation::Predicates.key?>, [:"Object/email"]]
|
219
219
|
|
220
220
|
# Fails attribute conformance
|
221
221
|
S.explain ns(:person), ns(:first_name) => "Elon", ns(:last_name) => "Musk", ns(:email) => "n/a"
|
@@ -248,8 +248,8 @@ S.explain :"unq/person", :first_name => "Elon", :last_name => "Musk", :email =>
|
|
248
248
|
# >> In: [:email] val: "n/a" fails spec: :"Object/email_type" at: [:email] predicate: [/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/, ["n/a"]]
|
249
249
|
|
250
250
|
S.explain :"unq/person", :first_name => "Elon"
|
251
|
-
# >> val: {:first_name=>"Elon"} fails spec: :"unq/person" predicate: [#<Method: Speculation::
|
252
|
-
# >> val: {:first_name=>"Elon"} fails spec: :"unq/person" predicate: [#<Method: Speculation::
|
251
|
+
# >> val: {:first_name=>"Elon"} fails spec: :"unq/person" predicate: [#<Method: Speculation::Predicates.key?>, [:"Object/last_name"]]
|
252
|
+
# >> val: {:first_name=>"Elon"} fails spec: :"unq/person" predicate: [#<Method: Speculation::Predicates.key?>, [:"Object/email"]]
|
253
253
|
|
254
254
|
# Unqualified keys can also be used to validate record attributes - don't support
|
255
255
|
# Keyword args keys* - don't support
|
@@ -301,7 +301,7 @@ S.conform ns(:vnum3), [1, 2, 3] # => #<Set: {1, 2, 3}>
|
|
301
301
|
S.explain ns(:vnum3), Set[1, 2, 3] # not an array
|
302
302
|
# >> val: #<Set: {1, 2, 3}> fails spec: :"Object/vnum3" predicate: [Array, [#<Set: {1, 2, 3}>]]
|
303
303
|
S.explain ns(:vnum3), [1, 1, 1] # not distinct
|
304
|
-
# >> val: [1, 1, 1] fails spec: :"Object/vnum3" predicate: [#<Method: Speculation::
|
304
|
+
# >> val: [1, 1, 1] fails spec: :"Object/vnum3" predicate: [#<Method: Speculation::Predicates.distinct?>, [[1, 1, 1]]]
|
305
305
|
S.explain ns(:vnum3), [1, 2, :a] # not a number
|
306
306
|
# >> In: [2] val: :a fails spec: :"Object/vnum3" predicate: [Numeric, [:a]]
|
307
307
|
|
@@ -499,15 +499,15 @@ end
|
|
499
499
|
S.check_asserts = true
|
500
500
|
person_name 100 rescue $!
|
501
501
|
# => #<Speculation::Error: Spec assertion failed
|
502
|
-
# val: 100 fails predicate: [#<Method: Speculation::
|
502
|
+
# val: 100 fails predicate: [#<Method: Speculation::Predicates.hash?>, [100]]
|
503
503
|
# Speculation/failure :assertion_failed
|
504
|
-
# {:
|
504
|
+
# {:problems=>
|
505
505
|
# [{:path=>[],
|
506
|
-
# :pred=>[#<Method: Speculation::
|
506
|
+
# :pred=>[#<Method: Speculation::Predicates.hash?>, [100]],
|
507
507
|
# :val=>100,
|
508
508
|
# :via=>[],
|
509
509
|
# :in=>[]}],
|
510
|
-
# :
|
510
|
+
# :failure=>:assertion_failed}
|
511
511
|
# >
|
512
512
|
|
513
513
|
# A deeper level of integration is to call conform and use the return value to
|
@@ -925,7 +925,7 @@ S.exercise_fn(method(:ranged_rand))
|
|
925
925
|
# predicate implicitly presumes values of a particular type but the spec does
|
926
926
|
# not specify them:
|
927
927
|
|
928
|
-
Gen.generate S.gen(:even?.to_proc) rescue $! # => #<Speculation::Error: unable to construct gen at: [] for: Speculation::Spec(#<Proc:0x007fb69b1908f8(&:even?)>) {:
|
928
|
+
Gen.generate S.gen(:even?.to_proc) rescue $! # => #<Speculation::Error: unable to construct gen at: [] for: Speculation::Spec(#<Proc:0x007fb69b1908f8(&:even?)>) {:failure=>:no_gen, :"Speculation/path"=>[]}\n>
|
929
929
|
|
930
930
|
# In this case spec was not able to find a generator for the even? predicate.
|
931
931
|
# Most of the primitive generators in spec are mapped to the common type
|
@@ -1013,8 +1013,10 @@ Gen.sample sym_gen, 5
|
|
1013
1013
|
# To redefine our spec using this custom generator, use with_gen which takes a
|
1014
1014
|
# spec and a replacement generator as a block:
|
1015
1015
|
|
1016
|
-
|
1017
|
-
S.
|
1016
|
+
syms_spec = S.with_gen(S.and(Symbol, ->(s) { S::NamespacedSymbols.namespace(s) == "my.domain" })) do
|
1017
|
+
S.gen(Set[:"my.domain/name", :"my.domain/occupation", :"my.domain/id"])
|
1018
|
+
end
|
1019
|
+
S.def ns(:syms), syms_spec
|
1018
1020
|
|
1019
1021
|
S.valid? ns(:syms), :"my.domain/name"
|
1020
1022
|
Gen.sample S.gen(ns(:syms)), 5
|
@@ -1025,7 +1027,7 @@ Gen.sample S.gen(ns(:syms)), 5
|
|
1025
1027
|
# :"my.domain/id"]
|
1026
1028
|
|
1027
1029
|
# Note that with_gen (and other places that take a custom generator) take a
|
1028
|
-
#
|
1030
|
+
# no-arg block that returns the generator, allowing it to be lazily
|
1029
1031
|
# realized.
|
1030
1032
|
|
1031
1033
|
# One downside to this approach is we are missing what property testing is
|
@@ -1048,11 +1050,14 @@ Gen.sample sym_gen_2, 5 # => [:"my.domain/hLZnEpj", :"my.domain/kvy", :"my.domai
|
|
1048
1050
|
# Returning to our "hello" example, we now have the tools to make that
|
1049
1051
|
# generator:
|
1050
1052
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
}
|
1053
|
+
hello_spec = S.with_gen(->(s) { s.include?("hello") }) do
|
1054
|
+
->(rantly) {
|
1055
|
+
s1 = rantly.sized(rantly.range(0, 10)) { rantly.string(:alpha) }
|
1056
|
+
s2 = rantly.sized(rantly.range(0, 10)) { rantly.string(:alpha) }
|
1057
|
+
"#{s1}hello#{s2}"
|
1058
|
+
}
|
1059
|
+
end
|
1060
|
+
S.def ns(:hello), hello_spec
|
1056
1061
|
|
1057
1062
|
Gen.sample S.gen(ns(:hello))
|
1058
1063
|
# => ["XRLhtLshelloaY",
|
@@ -1136,7 +1141,7 @@ ranged_rand 8, 5 rescue $!
|
|
1136
1141
|
# Speculation/args [8, 5]
|
1137
1142
|
# Speculation/failure :instrument
|
1138
1143
|
# Speculation::Test/caller "/var/folders/4l/j2mycv0j4rx7z47sp01r93vc3kfxzs/T/seeing_is_believing_temp_dir20170304-91770-1jtmc1y/program.rb:901:in `<main>'"
|
1139
|
-
# {:
|
1144
|
+
# {:problems=>
|
1140
1145
|
# [{:path=>[:args],
|
1141
1146
|
# :val=>{:start=>8, :end=>5},
|
1142
1147
|
# :via=>[],
|
@@ -1144,9 +1149,9 @@ ranged_rand 8, 5 rescue $!
|
|
1144
1149
|
# :pred=>
|
1145
1150
|
# [#<Proc:0x007fb69a8a0fb8@/var/folders/4l/j2mycv0j4rx7z47sp01r93vc3kfxzs/T/seeing_is_believing_temp_dir20170304-91770-1jtmc1y/program.rb:548 (lambda)>,
|
1146
1151
|
# [{:start=>8, :end=>5}]]}],
|
1147
|
-
# :
|
1148
|
-
# :
|
1149
|
-
# :
|
1152
|
+
# :args=>[8, 5],
|
1153
|
+
# :failure=>:instrument,
|
1154
|
+
# :caller=>
|
1150
1155
|
# "/var/folders/4l/j2mycv0j4rx7z47sp01r93vc3kfxzs/T/seeing_is_believing_temp_dir20170304-91770-1jtmc1y/program.rb:901:in `<main>'"}
|
1151
1156
|
# >
|
1152
1157
|
|
@@ -1172,7 +1177,7 @@ ranged_rand 8, 5 rescue $!
|
|
1172
1177
|
|
1173
1178
|
STest.check method(:ranged_rand)
|
1174
1179
|
# => [{:spec=>Speculation::FSpec(main.ranged_rand),
|
1175
|
-
# :
|
1180
|
+
# :ret=>{:num_tests=>1000, :result=>true},
|
1176
1181
|
# :method=>#<Method: main.ranged_rand>}]
|
1177
1182
|
|
1178
1183
|
# check also takes a number of options ~~that can be passed to test.check to
|
@@ -1193,7 +1198,7 @@ STest.abbrev_result STest.check(method(:ranged_rand)).first
|
|
1193
1198
|
# >> {:spec=>"Speculation::FSpec(main.ranged_rand)",
|
1194
1199
|
# >> :method=>#<Method: main.ranged_rand>,
|
1195
1200
|
# >> :failure=>
|
1196
|
-
# >> {:
|
1201
|
+
# >> {:problems=>
|
1197
1202
|
# >> [{:path=>[:fn],
|
1198
1203
|
# >> :val=>{:args=>{:start=>-1, :end=>0}, :block=>nil, :ret=>0},
|
1199
1204
|
# >> :via=>[],
|
@@ -1201,10 +1206,10 @@ STest.abbrev_result STest.check(method(:ranged_rand)).first
|
|
1201
1206
|
# >> :pred=>
|
1202
1207
|
# >> [#<Proc:0x007fb69a8a0c98@/var/folders/4l/j2mycv0j4rx7z47sp01r93vc3kfxzs/T/seeing_is_believing_temp_dir20170304-91770-1jtmc1y/program.rb:551 (lambda)>,
|
1203
1208
|
# >> [{:args=>{:start=>-1, :end=>0}, :block=>nil, :ret=>0}]]}],
|
1204
|
-
# >> :
|
1205
|
-
# >> :
|
1209
|
+
# >> :args=>[-1, 0],
|
1210
|
+
# >> :val=>
|
1206
1211
|
# >> {:args=>{:start=>-1, :end=>0}, :block=>nil, :ret=>0},
|
1207
|
-
# >> :
|
1212
|
+
# >> :failure=>:check_failed}}
|
1208
1213
|
|
1209
1214
|
# check has reported an error in the :fn spec. We can see the arguments passed
|
1210
1215
|
# were -1 and 0 and the return value was -0, which is out of the expected
|
data/lib/speculation.rb
CHANGED
@@ -12,6 +12,7 @@ require "speculation/version"
|
|
12
12
|
require "speculation/namespaced_symbols"
|
13
13
|
require "speculation/method_identifier"
|
14
14
|
require "speculation/utils"
|
15
|
+
require "speculation/predicates"
|
15
16
|
require "speculation/spec"
|
16
17
|
require "speculation/error"
|
17
18
|
|
@@ -22,9 +23,8 @@ module Speculation
|
|
22
23
|
# Enables or disables spec asserts. Defaults to false.
|
23
24
|
attr_accessor :check_asserts
|
24
25
|
|
25
|
-
# A soft limit on how many times a branching spec (or/alt/zero_or_more) can
|
26
|
-
#
|
27
|
-
# will be chosen.
|
26
|
+
# A soft limit on how many times a branching spec (or/alt/zero_or_more/opt keys) can be recursed
|
27
|
+
# through during generation. After this a non-recursive branch will be chosen.
|
28
28
|
attr_accessor :recursion_limit
|
29
29
|
|
30
30
|
# The number of times an anonymous fn specified by fspec will be
|
@@ -47,29 +47,6 @@ module Speculation
|
|
47
47
|
|
48
48
|
@registry_ref = Concurrent::Atom.new({})
|
49
49
|
|
50
|
-
INVALID = ns(:invalid)
|
51
|
-
|
52
|
-
# @private
|
53
|
-
OP = ns(:op)
|
54
|
-
# @private
|
55
|
-
ALT = ns(:alt)
|
56
|
-
# @private
|
57
|
-
AMP = ns(:amp)
|
58
|
-
# @private
|
59
|
-
PCAT = ns(:pcat)
|
60
|
-
# @private
|
61
|
-
REP = ns(:rep)
|
62
|
-
# @private
|
63
|
-
ACCEPT = ns(:accept)
|
64
|
-
# @private
|
65
|
-
NIL = ns(:nil)
|
66
|
-
# @private
|
67
|
-
RECURSION_LIMIT = ns(:recursion_limit)
|
68
|
-
# @private
|
69
|
-
GEN = ns(:gen)
|
70
|
-
# @private
|
71
|
-
NAME = ns(:name)
|
72
|
-
|
73
50
|
# Can be enabled or disabled at runtime:
|
74
51
|
# - enabled/disabled by setting `check_asserts`.
|
75
52
|
# - enabled by setting environment variable SPECULATION_CHECK_ASSERTS to the
|
@@ -83,7 +60,7 @@ module Speculation
|
|
83
60
|
return x unless check_asserts
|
84
61
|
return x if valid?(spec, x)
|
85
62
|
|
86
|
-
ed = _explain_data(spec, [], [], [], x).merge(
|
63
|
+
ed = _explain_data(spec, [], [], [], x).merge(:failure => :assertion_failed)
|
87
64
|
out = StringIO.new
|
88
65
|
explain_out(ed, out)
|
89
66
|
|
@@ -110,28 +87,28 @@ module Speculation
|
|
110
87
|
gens << [1, ->(r) { r.choose(Float::INFINITY, -Float::INFINITY) }] if infinite
|
111
88
|
gens << [1, ->(_) { Float::NAN }] if nan
|
112
89
|
|
113
|
-
spec(self.and(*preds), :gen => ->(rantly) { rantly.freq(*gens) })
|
90
|
+
spec(self.and(*preds), :gen => ->() { ->(rantly) { rantly.freq(*gens) } })
|
114
91
|
end
|
115
92
|
|
116
93
|
# @param range [Range<Integer>]
|
117
94
|
# @return Spec that validates ints in the given range
|
118
95
|
def self.int_in(range)
|
119
96
|
spec(self.and(Integer, ->(x) { range.include?(x) }),
|
120
|
-
:gen => ->(_) { rand(range) })
|
97
|
+
:gen => ->() { ->(_) { rand(range) } })
|
121
98
|
end
|
122
99
|
|
123
100
|
# @param time_range [Range<Time>]
|
124
101
|
# @return Spec that validates times in the given range
|
125
102
|
def self.time_in(time_range)
|
126
103
|
spec(self.and(Time, ->(x) { time_range.cover?(x) }),
|
127
|
-
:gen => ->(_) { rand(time_range) })
|
104
|
+
:gen => ->() { ->(_) { rand(time_range) } })
|
128
105
|
end
|
129
106
|
|
130
107
|
# @param date_range [Range<Date>]
|
131
108
|
# @return Spec that validates dates in the given range
|
132
109
|
def self.date_in(date_range)
|
133
110
|
spec(self.and(Date, ->(x) { date_range.cover?(x) }),
|
134
|
-
:gen => ->(_) { rand(date_range) })
|
111
|
+
:gen => ->() { ->(_) { rand(date_range) } })
|
135
112
|
end
|
136
113
|
|
137
114
|
# @param x [Spec, Object]
|
@@ -143,30 +120,43 @@ module Speculation
|
|
143
120
|
# @param x [Hash, Object]
|
144
121
|
# @return [Hash, false] x if x is a (Speculation) regex op, else logical false
|
145
122
|
def self.regex?(x)
|
146
|
-
|
123
|
+
x.is_a?(Hash) && x[:op] && x
|
147
124
|
end
|
148
125
|
|
149
126
|
# @param value return value of a `conform` call
|
150
127
|
# @return [Boolean] true if value is the result of an unsuccessful conform
|
151
128
|
def self.invalid?(value)
|
152
|
-
value.equal?(
|
129
|
+
value.equal?(:"Speculation/invalid")
|
153
130
|
end
|
154
131
|
|
155
132
|
# @param spec [Spec]
|
156
133
|
# @param value value to conform
|
157
|
-
# @return [Symbol, Object] :Speculation/invalid if value does not match spec, else the (possibly
|
134
|
+
# @return [Symbol, Object] :Speculation/invalid if value does not match spec, else the (possibly
|
135
|
+
# destructured) value
|
158
136
|
def self.conform(spec, value)
|
159
137
|
spec = MethodIdentifier(spec)
|
160
138
|
specize(spec).conform(value)
|
161
139
|
end
|
162
140
|
|
163
|
-
# Takes a spec and a one-arg generator function and returns a version of the spec that uses that generator
|
164
141
|
# @param spec [Spec]
|
165
|
-
# @
|
142
|
+
# @value value [Object] value created by `conform` call and given `spec`
|
143
|
+
# @return value with conform destructuring undone
|
144
|
+
def self.unform(spec, value)
|
145
|
+
specize(spec).unform(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Takes a spec and a no-arg generator returning block and returns a version of the spec that uses
|
149
|
+
# that generator
|
150
|
+
# @param spec [Spec]
|
151
|
+
# @yieldreturn Rantly generator
|
166
152
|
# @return [Spec]
|
167
|
-
def self.with_gen(spec, gen)
|
153
|
+
def self.with_gen(spec, &gen)
|
154
|
+
if gen && !gen.arity.zero?
|
155
|
+
raise ArgumentError, "gen must be a no-arg block that returns a generator"
|
156
|
+
end
|
157
|
+
|
168
158
|
if regex?(spec)
|
169
|
-
spec.merge(
|
159
|
+
spec.merge(:gfn => gen)
|
170
160
|
else
|
171
161
|
specize(spec).with_gen(gen)
|
172
162
|
end
|
@@ -177,19 +167,21 @@ module Speculation
|
|
177
167
|
probs = specize(spec).explain(path, via, inn, value)
|
178
168
|
|
179
169
|
if probs && probs.any?
|
180
|
-
{
|
170
|
+
{ :problems => probs,
|
171
|
+
:spec => spec,
|
172
|
+
:value => value }
|
181
173
|
end
|
182
174
|
end
|
183
175
|
|
184
176
|
# Given a spec and a value x which ought to conform, returns nil if x
|
185
|
-
# conforms, else a hash with at least the key :
|
177
|
+
# conforms, else a hash with at least the key :problems whose
|
186
178
|
# value is a collection of problem-hashes, where problem-hash has at least
|
187
179
|
# :path :pred and :val keys describing the predicate and the value that failed
|
188
180
|
# at that path.
|
189
181
|
# @param spec [Spec]
|
190
182
|
# @param x value which ought to conform
|
191
183
|
# @return [nil, Hash] nil if x conforms, else a hash with at least the key
|
192
|
-
# :
|
184
|
+
# :problems whose value is a collection of problem-hashes,
|
193
185
|
# where problem-hash has at least :path :pred and :val keys describing the
|
194
186
|
# predicate and the value that failed at that path.
|
195
187
|
def self.explain_data(spec, x)
|
@@ -203,7 +195,9 @@ module Speculation
|
|
203
195
|
def self.explain_out(ed, out = STDOUT)
|
204
196
|
return out.puts("Success!") unless ed
|
205
197
|
|
206
|
-
ed.fetch(
|
198
|
+
problems = Utils.sort_descending(ed.fetch(:problems)) { |prob| prob[:path] }
|
199
|
+
|
200
|
+
problems.each do |prob|
|
207
201
|
path, pred, val, reason, via, inn = prob.values_at(:path, :pred, :val, :reason, :via, :in)
|
208
202
|
|
209
203
|
out.print("In: ", inn.to_a.inspect, " ") unless inn.empty?
|
@@ -223,7 +217,7 @@ module Speculation
|
|
223
217
|
end
|
224
218
|
|
225
219
|
ed.each do |k, v|
|
226
|
-
out.puts("#{k} #{PP.pp(v, String.new)}") unless k ==
|
220
|
+
out.puts("#{k.inspect} #{PP.pp(v, String.new)}") unless k == :problems
|
227
221
|
end
|
228
222
|
|
229
223
|
nil
|
@@ -251,13 +245,14 @@ module Speculation
|
|
251
245
|
|
252
246
|
spec = specize(spec)
|
253
247
|
gfn = overrides[spec_name(spec) || spec] || overrides[path]
|
248
|
+
gfn = gfn.call if gfn
|
254
249
|
g = gfn || spec.gen(overrides, path, rmap)
|
255
250
|
|
256
251
|
if g
|
257
252
|
Gen.such_that(g) { |x| valid?(spec, x) }
|
258
253
|
else
|
259
254
|
raise Speculation::Error.new("unable to construct gen at: #{path.inspect} for: #{spec.inspect}",
|
260
|
-
|
255
|
+
:failure => :no_gen, :path => path)
|
261
256
|
end
|
262
257
|
end
|
263
258
|
|
@@ -277,7 +272,7 @@ module Speculation
|
|
277
272
|
# @return [Proc]
|
278
273
|
def self.gen(spec, overrides = nil)
|
279
274
|
spec = MethodIdentifier(spec)
|
280
|
-
gensub(spec, overrides, [],
|
275
|
+
gensub(spec, overrides, [], :recursion_limit => recursion_limit)
|
281
276
|
end
|
282
277
|
|
283
278
|
# @private
|
@@ -329,8 +324,8 @@ module Speculation
|
|
329
324
|
# NOTE: it is not generally necessary to wrap predicates in spec when using
|
330
325
|
# `S.def` etc., only to attach a unique generator.
|
331
326
|
#
|
332
|
-
# Optionally takes :gen generator function, which must be a proc
|
333
|
-
# (Rantly instance) that generates a valid value.
|
327
|
+
# Optionally takes :gen generator function, which must be a no-arg proc that returns a generator
|
328
|
+
# (proc that receives a Rantly instance) that generates a valid value.
|
334
329
|
#
|
335
330
|
# @param pred [Proc, Method, Set, Class, Regexp, Hash] Takes a single predicate. A
|
336
331
|
# predicate can be one of:
|
@@ -346,8 +341,8 @@ module Speculation
|
|
346
341
|
# zero_or_more, one_or_more, zero_or_one, in which case it will return a
|
347
342
|
# regex-conforming spec, useful when nesting an independent regex.
|
348
343
|
#
|
349
|
-
# @param gen [Proc] generator function, which must be a proc
|
350
|
-
# arg (Rantly instance) that generates a valid value.
|
344
|
+
# @param gen [Proc] generator returning function, which must be a zero arg proc that returns a
|
345
|
+
# proc of one arg (Rantly instance) that generates a valid value.
|
351
346
|
# @return [Spec]
|
352
347
|
def self.spec(pred, gen: nil)
|
353
348
|
spec_impl(pred, gen, false) if pred
|
@@ -381,20 +376,20 @@ module Speculation
|
|
381
376
|
# @param opt [Array<Symbol>]
|
382
377
|
# @param req_un [Array<Symbol>]
|
383
378
|
# @param opt_un [Array<Symbol>]
|
384
|
-
# @param gen [Proc] generator function, which must be a
|
385
|
-
# (Rantly instance) that generates a valid value
|
379
|
+
# @param gen [Proc] generator returning function, which must be a zero arg proc that
|
380
|
+
# returns a proc of one arg (Rantly instance) that generates a valid value.
|
386
381
|
def self.keys(req: [], opt: [], req_un: [], opt_un: [], gen: nil)
|
387
382
|
HashSpec.new(req, opt, req_un, opt_un, gen)
|
388
383
|
end
|
389
384
|
|
390
385
|
# @see keys
|
391
386
|
def self.or_keys(*ks)
|
392
|
-
[
|
387
|
+
[:"Speculation/or", *ks]
|
393
388
|
end
|
394
389
|
|
395
390
|
# @see keys
|
396
391
|
def self.and_keys(*ks)
|
397
|
-
[
|
392
|
+
[:"Speculation/and", *ks]
|
398
393
|
end
|
399
394
|
|
400
395
|
# @param key_preds [Hash] Takes key+pred hash
|
@@ -440,8 +435,8 @@ module Speculation
|
|
440
435
|
# @option opts :into [Array, Hash, Set] (Array) one of [], {}, Set[], the
|
441
436
|
# default collection to generate into (default: empty coll as generated by
|
442
437
|
# :kind pred if supplied, else [])
|
443
|
-
# @option opts :gen [Proc] generator
|
444
|
-
# (Rantly instance) that generates a valid value.
|
438
|
+
# @option opts :gen [Proc] generator returning function, which must be a zero arg proc that
|
439
|
+
# returns a proc of one arg (Rantly instance) that generates a valid value.
|
445
440
|
# @see coll_of
|
446
441
|
# @see every_kv
|
447
442
|
# @return [Spec] spec that validates collection elements against pred
|
@@ -461,9 +456,9 @@ module Speculation
|
|
461
456
|
# @param vpred val pred
|
462
457
|
# @param options [Hash]
|
463
458
|
# @return [Spec] spec that validates associative collections
|
464
|
-
def self.every_kv(kpred, vpred, options)
|
465
|
-
every(tuple(kpred, vpred),
|
466
|
-
:into
|
459
|
+
def self.every_kv(kpred, vpred, options = {})
|
460
|
+
every(tuple(kpred, vpred), :kfn => ->(_i, v) { v.first },
|
461
|
+
:into => {},
|
467
462
|
**options)
|
468
463
|
end
|
469
464
|
|
@@ -479,13 +474,13 @@ module Speculation
|
|
479
474
|
# @param opts [Hash]
|
480
475
|
# @return [Spec]
|
481
476
|
def self.coll_of(pred, opts = {})
|
482
|
-
every(pred,
|
477
|
+
every(pred, :conform_all => true, **opts)
|
483
478
|
end
|
484
479
|
|
485
480
|
# Returns a spec for a hash whose keys satisfy kpred and vals satisfy vpred.
|
486
481
|
# Unlike 'every_kv', hash_of will exhaustively conform every value.
|
487
482
|
#
|
488
|
-
# Same options as 'every', :kind defaults to `Speculation::
|
483
|
+
# Same options as 'every', :kind defaults to `Speculation::Predicates.hash?`, with
|
489
484
|
# the addition of:
|
490
485
|
#
|
491
486
|
# :conform_keys - conform keys as well as values (default false)
|
@@ -496,8 +491,8 @@ module Speculation
|
|
496
491
|
# @param options [Hash]
|
497
492
|
# @return [Spec]
|
498
493
|
def self.hash_of(kpred, vpred, options = {})
|
499
|
-
every_kv(kpred, vpred, :kind
|
500
|
-
|
494
|
+
every_kv(kpred, vpred, :kind => Predicates.method(:hash?),
|
495
|
+
:conform_all => true,
|
501
496
|
**options)
|
502
497
|
end
|
503
498
|
|
@@ -519,7 +514,7 @@ module Speculation
|
|
519
514
|
# @return [Hash] regex op that matches zero or one value matching pred. Produces a
|
520
515
|
# single value (not a collection) if matched.
|
521
516
|
def self.zero_or_one(pred)
|
522
|
-
_alt([pred, accept(
|
517
|
+
_alt([pred, accept(:nil)], nil)
|
523
518
|
end
|
524
519
|
|
525
520
|
# @param kv_specs [Hash] key+pred pairs
|
@@ -550,14 +545,15 @@ module Speculation
|
|
550
545
|
# resulting value to the conjunction of the predicates, and any conforming
|
551
546
|
# they might perform.
|
552
547
|
def self.constrained(re, *preds)
|
553
|
-
{
|
548
|
+
{ :op => :amp, :p1 => re, :predicates => preds }
|
554
549
|
end
|
555
550
|
|
556
|
-
# @param f
|
551
|
+
# @param f [#call] function with the semantics of conform i.e. it should
|
557
552
|
# return either a (possibly converted) value or :"Speculation/invalid"
|
553
|
+
# @param unformer [#call] function that does the unform of the result of `f`
|
558
554
|
# @return [Spec] a spec that uses pred as a predicate/conformer.
|
559
|
-
def self.conformer(f)
|
560
|
-
spec_impl(f, nil, true)
|
555
|
+
def self.conformer(f, unformer = nil)
|
556
|
+
spec_impl(f, nil, true, unformer)
|
561
557
|
end
|
562
558
|
|
563
559
|
# Takes :args :ret and (optional) :block and :fn kwargs whose values are preds and returns a spec
|
@@ -571,8 +567,8 @@ module Speculation
|
|
571
567
|
# @param ret predicate
|
572
568
|
# @param fn predicate
|
573
569
|
# @param block predicate
|
574
|
-
# @param gen [Proc] generator
|
575
|
-
# instance) that generates a valid value.
|
570
|
+
# @param gen [Proc] generator returning function, which must be a zero arg proc that
|
571
|
+
# returns a proc of one arg (Rantly instance) that generates a valid value.
|
576
572
|
# @return [Spec]
|
577
573
|
# @see fdef See 'fdef' for a single operation that creates an fspec and registers it, as well as a
|
578
574
|
# full description of :args, :block, :ret and :fn
|
@@ -662,7 +658,7 @@ module Speculation
|
|
662
658
|
# @return [Array] an array of triples of [args, block, ret].
|
663
659
|
def self.exercise_fn(method, n = 10, fspec = nil)
|
664
660
|
fspec ||= get_spec(method)
|
665
|
-
raise ArgumentError, "No
|
661
|
+
raise ArgumentError, "No :args spec found for #{method}" unless fspec && fspec.args
|
666
662
|
|
667
663
|
block_gen = fspec.block ? gen(fspec.block) : Utils.constantly(nil)
|
668
664
|
gen = Gen.tuple(gen(fspec.args), block_gen)
|
@@ -674,7 +670,7 @@ module Speculation
|
|
674
670
|
|
675
671
|
# @private
|
676
672
|
def self.recur_limit?(rmap, id, path, k)
|
677
|
-
rmap[id] > rmap[
|
673
|
+
rmap[id] > rmap[:recursion_limit] &&
|
678
674
|
path.include?(k)
|
679
675
|
end
|
680
676
|
|
@@ -692,11 +688,11 @@ module Speculation
|
|
692
688
|
if spec
|
693
689
|
conform(spec, x)
|
694
690
|
elsif pred.is_a?(Module) || pred.is_a?(::Regexp)
|
695
|
-
pred === x ? x :
|
691
|
+
pred === x ? x : :"Speculation/invalid"
|
696
692
|
elsif pred.is_a?(Set)
|
697
|
-
pred.include?(x) ? x :
|
693
|
+
pred.include?(x) ? x : :"Speculation/invalid"
|
698
694
|
elsif pred.respond_to?(:call)
|
699
|
-
pred.call(x) ? x :
|
695
|
+
pred.call(x) ? x : :"Speculation/invalid"
|
700
696
|
else
|
701
697
|
raise "#{pred} is not a class, proc, set or regexp"
|
702
698
|
end
|
@@ -723,16 +719,16 @@ module Speculation
|
|
723
719
|
end
|
724
720
|
|
725
721
|
# @private
|
726
|
-
def self.spec_impl(pred, gen, should_conform)
|
722
|
+
def self.spec_impl(pred, gen, should_conform, unconformer = nil)
|
727
723
|
if spec?(pred)
|
728
|
-
with_gen(pred, gen)
|
724
|
+
with_gen(pred, &gen)
|
729
725
|
elsif regex?(pred)
|
730
726
|
RegexSpec.new(pred, gen)
|
731
727
|
elsif Utils.ident?(pred)
|
732
728
|
spec = the_spec(pred)
|
733
|
-
gen ? with_gen(spec, gen) : spec
|
729
|
+
gen ? with_gen(spec, &gen) : spec
|
734
730
|
else
|
735
|
-
PredicateSpec.new(pred, should_conform, gen)
|
731
|
+
PredicateSpec.new(pred, should_conform, gen, unconformer)
|
736
732
|
end
|
737
733
|
end
|
738
734
|
|
@@ -761,7 +757,7 @@ module Speculation
|
|
761
757
|
p = reg_resolve!(p)
|
762
758
|
|
763
759
|
id, op, ps, ks, p1, p2, ret, id, gen = p.values_at(
|
764
|
-
:id,
|
760
|
+
:id, :op, :predicates, :keys, :p1, :p2, :return_value, :id, :gfn
|
765
761
|
) if regex?(p)
|
766
762
|
|
767
763
|
id = p.id if spec?(p)
|
@@ -786,19 +782,21 @@ module Speculation
|
|
786
782
|
overrides[path]
|
787
783
|
|
788
784
|
if ogen
|
785
|
+
gen = ogen.call
|
786
|
+
|
789
787
|
if [:accept, nil].include?(op)
|
790
|
-
return
|
788
|
+
return Gen.fmap(gen) { |x| [x] }
|
791
789
|
else
|
792
|
-
return
|
790
|
+
return gen
|
793
791
|
end
|
794
792
|
end
|
795
793
|
|
796
|
-
return gen if gen
|
794
|
+
return gen.call if gen
|
797
795
|
|
798
796
|
if p
|
799
797
|
case op
|
800
|
-
when
|
801
|
-
if ret ==
|
798
|
+
when :accept
|
799
|
+
if ret == :nil
|
802
800
|
->(_rantly) { [] }
|
803
801
|
else
|
804
802
|
->(_rantly) { [ret] }
|
@@ -806,10 +804,10 @@ module Speculation
|
|
806
804
|
when nil
|
807
805
|
g = gensub(p, overrides, path, rmap)
|
808
806
|
|
809
|
-
|
810
|
-
when
|
807
|
+
Gen.fmap(g) { |x| [x] }
|
808
|
+
when :amp
|
811
809
|
re_gen(p1, overrides, path, rmap)
|
812
|
-
when
|
810
|
+
when :pcat
|
813
811
|
gens = ggens.call(ps, ks)
|
814
812
|
|
815
813
|
if gens.all?
|
@@ -817,11 +815,11 @@ module Speculation
|
|
817
815
|
gens.flat_map { |gg| gg.call(rantly) }
|
818
816
|
end
|
819
817
|
end
|
820
|
-
when
|
818
|
+
when :alt
|
821
819
|
gens = ggens.call(ps, ks).compact
|
822
820
|
|
823
821
|
->(rantly) { rantly.branch(*gens) } unless gens.empty?
|
824
|
-
when
|
822
|
+
when :rep
|
825
823
|
if recur_limit?(rmap, id, [id], id)
|
826
824
|
->(_rantly) { [] }
|
827
825
|
else
|
@@ -841,15 +839,45 @@ module Speculation
|
|
841
839
|
def self.re_conform(regex, data)
|
842
840
|
data.each do |x|
|
843
841
|
regex = deriv(regex, x)
|
844
|
-
return
|
842
|
+
return :"Speculation/invalid" unless regex
|
845
843
|
end
|
846
844
|
|
847
845
|
if accept_nil?(regex)
|
848
846
|
return_value = preturn(regex)
|
849
847
|
|
850
|
-
return_value ==
|
848
|
+
return_value == :nil ? nil : return_value
|
851
849
|
else
|
852
|
-
|
850
|
+
:"Speculation/invalid"
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
# @private
|
855
|
+
def self.op_unform(regex, value)
|
856
|
+
return unform(regex, value) unless regex?(regex)
|
857
|
+
|
858
|
+
case regex[:op]
|
859
|
+
when :accept
|
860
|
+
[regex[:return_value]]
|
861
|
+
when :amp
|
862
|
+
px = regex[:predicates].reverse.reduce(value) { |val, pred| op_unform(pred, val) }
|
863
|
+
op_unform(regex[:p1], px)
|
864
|
+
when :rep
|
865
|
+
value.flat_map { |val| op_unform(regex[:p1], val) }
|
866
|
+
when :pcat
|
867
|
+
if regex[:keys] # it's a `cat`
|
868
|
+
kps = Hash[regex[:keys].zip(regex[:predicates])]
|
869
|
+
regex[:keys].flat_map { |key| value.include?(key) ? op_unform(kps[key], value[key]) : [] }
|
870
|
+
else # it's a `one_or_more`
|
871
|
+
value.flat_map { |val| op_unform(regex[:predicates].first, val) }
|
872
|
+
end
|
873
|
+
when :alt
|
874
|
+
if regex[:keys] # it's an `alt`
|
875
|
+
kps = Hash[regex[:keys].zip(regex[:predicates])]
|
876
|
+
k, v = value
|
877
|
+
op_unform(kps[k], v)
|
878
|
+
else # it's a `zero_or_one`
|
879
|
+
[unform(regex[:predicates].first, value)]
|
880
|
+
end
|
853
881
|
end
|
854
882
|
end
|
855
883
|
|
@@ -866,7 +894,7 @@ module Speculation
|
|
866
894
|
end
|
867
895
|
|
868
896
|
if accept?(p)
|
869
|
-
if p[
|
897
|
+
if p[:op] == :pcat
|
870
898
|
return op_explain(p, path, via, Utils.conj(inn, index), input[index..-1])
|
871
899
|
else
|
872
900
|
return [{ :path => path,
|
@@ -929,7 +957,7 @@ module Speculation
|
|
929
957
|
if Utils.ident?(spec)
|
930
958
|
spec
|
931
959
|
elsif regex?(spec)
|
932
|
-
spec.merge(
|
960
|
+
spec.merge(:name => name)
|
933
961
|
else
|
934
962
|
spec.tap { |s| s.name = name }
|
935
963
|
end
|
@@ -939,7 +967,7 @@ module Speculation
|
|
939
967
|
if Utils.ident?(spec)
|
940
968
|
spec
|
941
969
|
elsif regex?(spec)
|
942
|
-
spec[
|
970
|
+
spec[:name]
|
943
971
|
elsif spec.respond_to?(:name)
|
944
972
|
spec.name
|
945
973
|
end
|
@@ -973,7 +1001,7 @@ module Speculation
|
|
973
1001
|
def and_preds(x, preds)
|
974
1002
|
preds.each do |pred|
|
975
1003
|
x = dt(pred, x)
|
976
|
-
return
|
1004
|
+
return :"Speculation/invalid" if invalid?(x)
|
977
1005
|
end
|
978
1006
|
|
979
1007
|
x
|
@@ -986,6 +1014,8 @@ module Speculation
|
|
986
1014
|
case spec
|
987
1015
|
when Symbol, MethodIdentifier
|
988
1016
|
specize(reg_resolve!(spec))
|
1017
|
+
when nil
|
1018
|
+
raise ArgumentError, "#{spec.inspect} can not be a spec"
|
989
1019
|
else
|
990
1020
|
spec_impl(spec, nil, false)
|
991
1021
|
end
|
@@ -995,12 +1025,12 @@ module Speculation
|
|
995
1025
|
### regex ###
|
996
1026
|
|
997
1027
|
def accept(x)
|
998
|
-
{
|
1028
|
+
{ :op => :accept, :return_value => x }
|
999
1029
|
end
|
1000
1030
|
|
1001
1031
|
def accept?(hash)
|
1002
1032
|
if hash.is_a?(Hash)
|
1003
|
-
hash[
|
1033
|
+
hash[:op] == :accept
|
1004
1034
|
end
|
1005
1035
|
end
|
1006
1036
|
|
@@ -1013,7 +1043,7 @@ module Speculation
|
|
1013
1043
|
return unless regex[:predicates].all?
|
1014
1044
|
|
1015
1045
|
unless accept?(predicate)
|
1016
|
-
return {
|
1046
|
+
return { :op => :pcat,
|
1017
1047
|
:predicates => regex[:predicates],
|
1018
1048
|
:keys => keys,
|
1019
1049
|
:return_value => regex[:return_value] }
|
@@ -1034,7 +1064,7 @@ module Speculation
|
|
1034
1064
|
def rep(p1, p2, return_value, splice)
|
1035
1065
|
return unless p1
|
1036
1066
|
|
1037
|
-
regex = {
|
1067
|
+
regex = { :op => :rep, :p2 => p2, :splice => splice, :id => SecureRandom.uuid }
|
1038
1068
|
|
1039
1069
|
if accept?(p1)
|
1040
1070
|
regex.merge(:p1 => p2, :return_value => Utils.conj(return_value, p1[:return_value]))
|
@@ -1059,7 +1089,7 @@ module Speculation
|
|
1059
1089
|
predicate, *rest_predicates = predicates
|
1060
1090
|
key, *_rest_keys = keys
|
1061
1091
|
|
1062
|
-
return_value = {
|
1092
|
+
return_value = { :op => :alt, :predicates => predicates, :keys => keys }
|
1063
1093
|
return return_value unless rest_predicates.empty?
|
1064
1094
|
|
1065
1095
|
return predicate unless key
|
@@ -1077,24 +1107,24 @@ module Speculation
|
|
1077
1107
|
end
|
1078
1108
|
|
1079
1109
|
def no_ret?(p1, pret)
|
1080
|
-
return true if pret ==
|
1110
|
+
return true if pret == :nil
|
1081
1111
|
|
1082
1112
|
regex = reg_resolve!(p1)
|
1083
|
-
op = regex[
|
1113
|
+
op = regex[:op]
|
1084
1114
|
|
1085
|
-
[
|
1115
|
+
[:rep, :pcat].include?(op) && pret.empty? || nil
|
1086
1116
|
end
|
1087
1117
|
|
1088
1118
|
def accept_nil?(regex)
|
1089
1119
|
regex = reg_resolve!(regex)
|
1090
1120
|
return unless regex?(regex)
|
1091
1121
|
|
1092
|
-
case regex[
|
1093
|
-
when
|
1094
|
-
when
|
1095
|
-
when
|
1096
|
-
when
|
1097
|
-
when
|
1122
|
+
case regex[:op]
|
1123
|
+
when :accept then true
|
1124
|
+
when :pcat then regex[:predicates].all? { |p| accept_nil?(p) }
|
1125
|
+
when :alt then regex[:predicates].any? { |p| accept_nil?(p) }
|
1126
|
+
when :rep then (regex[:p1] == regex[:p2]) || accept_nil?(regex[:p1])
|
1127
|
+
when :amp
|
1098
1128
|
p1 = regex[:p1]
|
1099
1129
|
|
1100
1130
|
return false unless accept_nil?(p1)
|
@@ -1102,7 +1132,7 @@ module Speculation
|
|
1102
1132
|
no_ret?(p1, preturn(p1)) ||
|
1103
1133
|
!invalid?(and_preds(preturn(p1), regex[:predicates]))
|
1104
1134
|
else
|
1105
|
-
raise "Unexpected
|
1135
|
+
raise "Unexpected op #{regex[:op]}"
|
1106
1136
|
end
|
1107
1137
|
end
|
1108
1138
|
|
@@ -1113,30 +1143,30 @@ module Speculation
|
|
1113
1143
|
p0, *_pr = regex[:predicates]
|
1114
1144
|
k, *_ks = regex[:keys]
|
1115
1145
|
|
1116
|
-
case regex[
|
1117
|
-
when
|
1118
|
-
when
|
1119
|
-
when
|
1120
|
-
when
|
1146
|
+
case regex[:op]
|
1147
|
+
when :accept then regex[:return_value]
|
1148
|
+
when :pcat then add_ret(p0, regex[:return_value], k)
|
1149
|
+
when :rep then add_ret(regex[:p1], regex[:return_value], k)
|
1150
|
+
when :amp
|
1121
1151
|
pret = preturn(regex[:p1])
|
1122
1152
|
|
1123
1153
|
if no_ret?(regex[:p1], pret)
|
1124
|
-
|
1154
|
+
:nil
|
1125
1155
|
else
|
1126
1156
|
and_preds(pret, regex[:predicates])
|
1127
1157
|
end
|
1128
|
-
when
|
1158
|
+
when :alt
|
1129
1159
|
pred, key = regex[:predicates].zip(Array(regex[:keys])).find { |(p, _k)| accept_nil?(p) }
|
1130
1160
|
|
1131
1161
|
r = if pred.nil?
|
1132
|
-
|
1162
|
+
:nil
|
1133
1163
|
else
|
1134
1164
|
preturn(pred)
|
1135
1165
|
end
|
1136
1166
|
|
1137
1167
|
key ? [key, r] : r
|
1138
1168
|
else
|
1139
|
-
raise "Unexpected
|
1169
|
+
raise "Unexpected op #{regex[:op]}"
|
1140
1170
|
end
|
1141
1171
|
end
|
1142
1172
|
|
@@ -1144,16 +1174,16 @@ module Speculation
|
|
1144
1174
|
regex = reg_resolve!(regex)
|
1145
1175
|
return r unless regex?(regex)
|
1146
1176
|
|
1147
|
-
case regex[
|
1148
|
-
when
|
1177
|
+
case regex[:op]
|
1178
|
+
when :accept, :alt, :amp
|
1149
1179
|
return_value = preturn(regex)
|
1150
1180
|
|
1151
|
-
if return_value ==
|
1181
|
+
if return_value == :nil
|
1152
1182
|
r
|
1153
1183
|
else
|
1154
1184
|
Utils.conj(r, key ? { key => return_value } : return_value)
|
1155
1185
|
end
|
1156
|
-
when
|
1186
|
+
when :pcat, :rep
|
1157
1187
|
return_value = preturn(regex)
|
1158
1188
|
|
1159
1189
|
if return_value.empty?
|
@@ -1164,7 +1194,7 @@ module Speculation
|
|
1164
1194
|
regex[:splice] ? Utils.into(r, val) : Utils.conj(r, val)
|
1165
1195
|
end
|
1166
1196
|
else
|
1167
|
-
raise "Unexpected
|
1197
|
+
raise "Unexpected op #{regex[:op]}"
|
1168
1198
|
end
|
1169
1199
|
end
|
1170
1200
|
|
@@ -1187,9 +1217,9 @@ module Speculation
|
|
1187
1217
|
pred, *rest_preds = predicates
|
1188
1218
|
key, *rest_keys = keys
|
1189
1219
|
|
1190
|
-
case regex[
|
1191
|
-
when
|
1192
|
-
when
|
1220
|
+
case regex[:op]
|
1221
|
+
when :accept then nil
|
1222
|
+
when :pcat
|
1193
1223
|
regex1 = pcat(:predicates => [deriv(pred, value), *rest_preds], :keys => keys, :return_value => return_value)
|
1194
1224
|
regex2 = nil
|
1195
1225
|
|
@@ -1201,9 +1231,9 @@ module Speculation
|
|
1201
1231
|
end
|
1202
1232
|
|
1203
1233
|
alt2(regex1, regex2)
|
1204
|
-
when
|
1234
|
+
when :alt
|
1205
1235
|
_alt(predicates.map { |p| deriv(p, value) }, keys)
|
1206
|
-
when
|
1236
|
+
when :rep
|
1207
1237
|
regex1 = rep(deriv(p1, value), p2, return_value, splice)
|
1208
1238
|
regex2 = nil
|
1209
1239
|
|
@@ -1212,18 +1242,18 @@ module Speculation
|
|
1212
1242
|
end
|
1213
1243
|
|
1214
1244
|
alt2(regex1, regex2)
|
1215
|
-
when
|
1245
|
+
when :amp
|
1216
1246
|
p1 = deriv(p1, value)
|
1217
1247
|
return unless p1
|
1218
1248
|
|
1219
|
-
if p1[
|
1249
|
+
if p1[:op] == :accept
|
1220
1250
|
ret = and_preds(preturn(p1), predicates)
|
1221
1251
|
accept(ret) unless invalid?(ret)
|
1222
1252
|
else
|
1223
1253
|
constrained(p1, *predicates)
|
1224
1254
|
end
|
1225
1255
|
else
|
1226
|
-
raise "Unexpected
|
1256
|
+
raise "Unexpected op #{regex[:op]}"
|
1227
1257
|
end
|
1228
1258
|
end
|
1229
1259
|
|
@@ -1251,9 +1281,9 @@ module Speculation
|
|
1251
1281
|
end
|
1252
1282
|
end
|
1253
1283
|
|
1254
|
-
case p[
|
1255
|
-
when
|
1256
|
-
when
|
1284
|
+
case p[:op]
|
1285
|
+
when :accept then nil
|
1286
|
+
when :amp
|
1257
1287
|
if input.empty?
|
1258
1288
|
if accept_nil?(p[:p1])
|
1259
1289
|
explain_pred_list(p[:predicates], path, via, inn, preturn(p[:p1]))
|
@@ -1269,7 +1299,7 @@ module Speculation
|
|
1269
1299
|
op_explain(p[:p1], path, via, inn, input)
|
1270
1300
|
end
|
1271
1301
|
end
|
1272
|
-
when
|
1302
|
+
when :pcat
|
1273
1303
|
pks = p[:predicates].zip(Array(p[:keys]))
|
1274
1304
|
pred, k = if pks.count == 1
|
1275
1305
|
pks.first
|
@@ -1284,7 +1314,7 @@ module Speculation
|
|
1284
1314
|
else
|
1285
1315
|
op_explain(pred, path, via, inn, input)
|
1286
1316
|
end
|
1287
|
-
when
|
1317
|
+
when :alt
|
1288
1318
|
return insufficient(p, path, via, inn) if input.empty?
|
1289
1319
|
|
1290
1320
|
probs = p[:predicates].zip(Array(p[:keys])).flat_map { |(predicate, key)|
|
@@ -1292,21 +1322,21 @@ module Speculation
|
|
1292
1322
|
}
|
1293
1323
|
|
1294
1324
|
probs.compact
|
1295
|
-
when
|
1325
|
+
when :rep
|
1296
1326
|
op_explain(p[:p1], path, via, inn, input)
|
1297
1327
|
else
|
1298
|
-
raise "Unexpected
|
1328
|
+
raise "Unexpected :op #{p[:op]}"
|
1299
1329
|
end
|
1300
1330
|
end
|
1301
1331
|
end
|
1302
1332
|
|
1303
1333
|
@registry_ref.reset(
|
1304
|
-
ns(:any) => with_gen(Utils.constantly(true)
|
1334
|
+
ns(:any) => with_gen(Utils.constantly(true)) { ->(r) { r.branch(*Gen::GEN_BUILTINS.values) } },
|
1305
1335
|
ns(:boolean) => Set[true, false],
|
1306
|
-
ns(:positive_integer) => with_gen(self.and(Integer, ->(x) { x > 0 })
|
1336
|
+
ns(:positive_integer) => with_gen(self.and(Integer, ->(x) { x > 0 })) { ->(r) { r.range(1) } },
|
1307
1337
|
# Rantly#positive_integer is actually a natural integer
|
1308
|
-
ns(:natural_integer) => with_gen(self.and(Integer, ->(x) { x >= 0 })
|
1309
|
-
ns(:negative_integer) => with_gen(self.and(Integer, ->(x) { x < 0 })
|
1310
|
-
ns(:empty) => with_gen(
|
1338
|
+
ns(:natural_integer) => with_gen(self.and(Integer, ->(x) { x >= 0 })) { :positive_integer.to_proc },
|
1339
|
+
ns(:negative_integer) => with_gen(self.and(Integer, ->(x) { x < 0 })) { ->(r) { r.range(nil, -1) } },
|
1340
|
+
ns(:empty) => with_gen(Predicates.method(:empty?)) { Utils.constantly([]) }
|
1311
1341
|
)
|
1312
1342
|
end
|