speculation 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 11bb7e3c5871a34e1419aa9b02bfce56bdc334a2
4
- data.tar.gz: b4d3470f9bb704a94b61f333a632f193a8baa21d
3
+ metadata.gz: 21bae113d98b1aae417ce30534a9488f34ddbaa0
4
+ data.tar.gz: aa53139eed9b655d4ce4a1ff9984881e94f4e954
5
5
  SHA512:
6
- metadata.gz: 1db14032f7e8755385fcd56346c8d04e712b3faf496caa9ecd77784f2ae7442edd0addea2d757d19e418970cbf7a408e82cef6291436f20dc18420312c893092
7
- data.tar.gz: '096a7f073629269c834a691a2ad370b9e8722ffdc97fd8885a60b2ec299c29cfb3ce90be6d43b8414a5669afe31468cd6f3a8b3fa2f588a2991a7272cbc7fe22'
6
+ metadata.gz: 23a0f3f05968623d4cc91d34f7929fc2e76c42679930e9abb35e6f7e9fadfc7648d48a18c4b80452ec6871832ab1c16d4880f735dc1c6033649560fa12e1eaa7
7
+ data.tar.gz: 525d9d6479c8accd5b9347b7f8f97568ca708213c82db5538a731e072ba8b31e31df20e79ee5247b787611c8d16db8b22858a101419c5dbdff180447f6329e9f
data/.rubocop.yml CHANGED
@@ -4,7 +4,7 @@ AllCops:
4
4
  - '*.*'
5
5
  - 'vendor/**/*'
6
6
  - 'examples/**/*'
7
- TargetRubyVersion: 2.4
7
+ TargetRubyVersion: 2.0
8
8
 
9
9
  Metrics/ClassLength:
10
10
  Enabled: false
@@ -98,3 +98,12 @@ Performance/RedundantBlockCall:
98
98
 
99
99
  Style/MethodName:
100
100
  Enabled: false
101
+
102
+ Style/SymbolArray:
103
+ Enabled: false
104
+
105
+ Style/IndentHeredoc:
106
+ Enabled: false
107
+
108
+ Style/EmptyLiteral:
109
+ Enabled: false
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  source "https://rubygems.org"
3
4
 
4
5
  gemspec
data/README.md CHANGED
@@ -11,10 +11,13 @@ The goal of this project is to match clojure.spec as closely as possible, from d
11
11
  - [sinatra-web-app](examples/sinatra-web-app): A small sinatra web application demonstrating model validation and API error message generation.
12
12
  - [spec_guide.rb](examples/spec_guide.rb): Speculation port of Clojure's [spec guide](https://clojure.org/guides/spec)
13
13
  - [codebreaker.rb](examples/codebreaker.rb): Speculation port of the 'codebreaker' game described in [Interactive development with clojure.spec](http://blog.cognitect.com/blog/2016/10/5/interactive-development-with-clojurespec)
14
+ - [json_parser.rb](examples/json_parser.rb): JSON parser using Speculation.
14
15
 
15
16
  ## Usage
16
17
 
17
- The API is more-or-less the same as `clojure.spec`. If you're already familiar clojure.spec with then you should feel at home with Speculation. Clojure and Ruby and quite different languages, so naturally there are some differences:
18
+ Documentation is available at [RubyDoc](http://www.rubydoc.info/github/english/speculation). The API is more-or-less the same as `clojure.spec`. If you're already familiar clojure.spec with then you should feel at home with Speculation. Most guides, talks and discussion around clojure.spec should apply equally well to Speculation. Clojure and Ruby and quite different languages, so naturally there are some differences:
19
+
20
+ ## Differences with clojure.spec
18
21
 
19
22
  ### Built in predicates
20
23
 
data/Rakefile CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "bundler/gem_tasks"
3
4
  require "rubocop"
4
5
  require "yard"
5
6
 
6
7
  task :rubocop do
7
- status = RuboCop::CLI.new.run([])
8
+ status = RuboCop::CLI.new.run(["--display-cop-names"])
8
9
  raise "failed with status #{status}" unless status.zero?
9
10
  end
10
11
 
@@ -0,0 +1,328 @@
1
+ # Exercise from Ruby Quiz: http://rubyquiz.com/quiz155.html
2
+ # Borrowed heavily from TreeTop parser used in http://learnruby.com/examples/ruby-quiz-155.shtml
3
+
4
+ require "bundler/setup"
5
+ require "minitest/autorun"
6
+ require "speculation"
7
+ require "speculation/gen"
8
+ require "speculation/test"
9
+
10
+ S = Speculation
11
+ Gen = S::Gen
12
+ STest = S::Test
13
+
14
+ module JSONParser
15
+ extend S::NamespacedSymbols
16
+
17
+ def self.re_literal(string, conformed)
18
+ kvs = string.each_char.each_with_index.reduce({}) { |h, (v, i)| h.merge(i => Set[v]) }
19
+ S.cat(kvs)
20
+ end
21
+
22
+ S.def ns(:maybe_spaces), S.zero_or_more(Set[" "])
23
+
24
+ S.def ns(:null), re_literal("null", nil)
25
+ S.def ns(:true), re_literal("true", true)
26
+ S.def ns(:false), re_literal("false", false)
27
+
28
+ S.def ns(:boolean), S.alt(:true => ns(:true),
29
+ :false => ns(:false))
30
+
31
+ S.def ns(:number), S.alt(:integer => ns(:integer),
32
+ :float => ns(:float))
33
+
34
+ S.def ns(:digit), /\A[[:digit:]]\z/
35
+ S.def ns(:digits), S.one_or_more(ns(:digit))
36
+ S.def ns(:exp), Set["e", "E"]
37
+ S.def ns(:maybe_sign), S.zero_or_one(Set["-", "+"])
38
+ S.def ns(:exponent), S.cat(:pre => ns(:exp),
39
+ :sign => ns(:maybe_sign),
40
+ :digits => ns(:digits))
41
+ S.def ns(:maybe_exponent), S.zero_or_one(ns(:exponent))
42
+
43
+ S.def ns(:integer), S.cat(:neg => S.zero_or_one(Set["-"]),
44
+ :digits => ns(:digits),
45
+ :exponent => ns(:maybe_exponent))
46
+
47
+ S.def ns(:maybe_neg), S.zero_or_one(Set["-"])
48
+ S.def ns(:float), S.cat(:neg => ns(:maybe_neg),
49
+ :digits => ns(:digits),
50
+ :dot => Set["."],
51
+ :more_digits => ns(:digits),
52
+ :exponent => ns(:maybe_exponent))
53
+
54
+ S.def ns(:quote), Set['"']
55
+ S.def ns(:maybe_characters), S.zero_or_more(ns(:character))
56
+ S.def ns(:string), S.cat(:open => ns(:quote),
57
+ :contents => ns(:maybe_characters),
58
+ :close => ns(:quote))
59
+
60
+
61
+ S.def ns(:character), S.alt(:escaped => ns(:escaped_character),
62
+ :special => ns(:special_character),
63
+ :unicode => ns(:unicode_character),
64
+ :regular => ns(:regular_character))
65
+
66
+ S.def ns(:unicode_character), S.cat(:escape => Set['\\'],
67
+ :u => Set['u'],
68
+ :digits => S.constrained(S.one_or_more(ns(:hex_digit)), ->(digits) { digits.count == 4 }))
69
+
70
+ S.def ns(:hex_digit), /[0-9a-fA-F]/
71
+
72
+ S.def ns(:escaped_character), S.cat(:escape => Set['\\'],
73
+ :val => Set['\\', '"'])
74
+
75
+ special_character_map = {
76
+ "b" => "\b",
77
+ "f" => "\f",
78
+ "n" => "\n",
79
+ "r" => "\r",
80
+ "t" => "\t",
81
+ }
82
+
83
+ S.def ns(:special_character), S.cat(:escape => Set['\\'],
84
+ :val => Set['b', 'f', 'n', 'r', 't'])
85
+
86
+ S.def ns(:regular_character), ->(s) { !['\\', '"'].include?(s) }
87
+
88
+ S.def ns(:open_square_bracket), Set["["]
89
+ S.def ns(:close_square_bracket), Set["]"]
90
+ S.def ns(:empty_array), S.cat(:open => ns(:open_square_bracket),
91
+ :spaces => ns(:maybe_spaces),
92
+ :close => ns(:close_square_bracket))
93
+
94
+ S.def ns(:non_empty_array), S.cat(:open => ns(:open_square_bracket),
95
+ :space => ns(:maybe_spaces),
96
+ :value_list => ns(:value_list),
97
+ :more_space => ns(:maybe_spaces),
98
+ :close => ns(:close_square_bracket))
99
+
100
+ S.def ns(:array), S.alt(:non_empty => ns(:non_empty_array),
101
+ :empty => ns(:empty_array))
102
+
103
+ S.def ns(:value_list), S.alt(:val => ns(:json),
104
+ :rest => S.cat(:val => ns(:json),
105
+ :comma => Set[","],
106
+ :space => ns(:maybe_spaces),
107
+ :tail => ns(:value_list)))
108
+
109
+ S.def ns(:open_brace), Set["{"]
110
+ S.def ns(:close_brace), Set["}"]
111
+ S.def ns(:empty_object), S.cat(:open => ns(:open_brace),
112
+ :spaces => ns(:maybe_spaces),
113
+ :close => ns(:close_brace))
114
+
115
+ S.def ns(:non_empty_object), S.cat(:open => ns(:open_brace),
116
+ :spaces => ns(:maybe_spaces),
117
+ :contents => ns(:object_contents),
118
+ :more_spaces => ns(:maybe_spaces),
119
+ :close => ns(:close_brace))
120
+
121
+ S.def ns(:object), S.alt(:empty_object => ns(:empty_object),
122
+ :non_empty_object => ns(:non_empty_object))
123
+
124
+ S.def ns(:kv), S.cat(:before => ns(:maybe_spaces),
125
+ :key => ns(:string),
126
+ :colon => Set[":"],
127
+ :after => ns(:maybe_spaces),
128
+ :val => ns(:json))
129
+
130
+ S.def ns(:object_contents), S.alt(:kv => ns(:kv),
131
+ :rest => S.cat(:kv => ns(:kv),
132
+ :comma => Set[","],
133
+ :space => ns(:maybe_spaces),
134
+ :tail => ns(:object_contents)))
135
+
136
+ S.def ns(:json), S.cat(:pre => ns(:maybe_spaces),
137
+ :val => S.alt(:null => ns(:null),
138
+ :boolean => ns(:boolean),
139
+ :number => ns(:number),
140
+ :string => ns(:string),
141
+ :array => ns(:array),
142
+ :object => ns(:object)),
143
+ :post => ns(:maybe_spaces))
144
+
145
+ def self.parse(s)
146
+ chars = s.split("")
147
+ json_data = S.conform(ns(:json), chars)
148
+
149
+ if S.invalid?(json_data)
150
+ fail S.explain_str(ns(:json), chars)
151
+ else
152
+ transform_json(json_data)
153
+ end
154
+ end
155
+
156
+ def self.transform_json(data)
157
+ tag, val = data[:val]
158
+
159
+ case tag
160
+ when :boolean then transform_boolean(val)
161
+ when :null then nil
162
+ when :number then transform_number(val)
163
+ when :string then transform_string(val)
164
+ when :array then transform_array(val)
165
+ when :object then transform_object(val)
166
+ end
167
+ end
168
+
169
+ def self.transform_boolean(tagged_val)
170
+ tag, val = tagged_val
171
+
172
+ case tag
173
+ when :true then true
174
+ when :false then false
175
+ end
176
+ end
177
+
178
+ def self.transform_number(tagged_val)
179
+ tag, val = tagged_val
180
+
181
+ case tag
182
+ when :integer
183
+ Integer(val.values_at(:neg, :digits, :exponent).join)
184
+ when :float
185
+ exp = Hash(val[:exponent]).values_at(:pre, :sign, :digits).join
186
+ val = val.merge(:exp => exp)
187
+ Float(val.values_at(:neg, :digits, :dot, :more_digits, :exp).join)
188
+ end
189
+ end
190
+
191
+ SPECIAL_CHARACTER_MAP = {
192
+ "b" => "\b",
193
+ "f" => "\f",
194
+ "n" => "\n",
195
+ "r" => "\r",
196
+ "t" => "\t",
197
+ }
198
+
199
+ def self.transform_string(val)
200
+ Array(val[:contents]).map { |(tag, char)|
201
+ case tag
202
+ when :regular then char
203
+ when :special then SPECIAL_CHARACTER_MAP.fetch(char[:val])
204
+ when :escaped then char[:val]
205
+ when :unicode then [char[:digits].join.hex].pack("U")
206
+ end
207
+ }.join
208
+ end
209
+
210
+ def self.transform_array(tagged_val)
211
+ tag, val = tagged_val
212
+
213
+ case tag
214
+ when :empty then []
215
+ when :non_empty then transform_value_list(val[:value_list])
216
+ end
217
+ end
218
+
219
+ def self.transform_value_list(tagged_val)
220
+ tag, val = tagged_val
221
+
222
+ case tag
223
+ when :rest
224
+ head = [transform_json(val[:val])]
225
+ tail = transform_value_list(val[:tail])
226
+ head + tail
227
+ when :val
228
+ [transform_json(val)]
229
+ end
230
+ end
231
+
232
+ def self.transform_object(tagged_val)
233
+ tag, val = tagged_val
234
+
235
+ case tag
236
+ when :empty_object then {}
237
+ when :non_empty_object then transform_object_contents(val[:contents])
238
+ end
239
+ end
240
+
241
+ def self.transform_object_contents(tagged_val)
242
+ tag, val = tagged_val
243
+
244
+ case tag
245
+ when :kv
246
+ { transform_string(val[:key]) => transform_json(val[:val]) }
247
+ when :rest
248
+ transform_object_contents([:kv, val[:kv]]).
249
+ merge(transform_object_contents(val[:tail]))
250
+ end
251
+ end
252
+ end
253
+
254
+ class TestJSONParser < Minitest::Test
255
+ def test_keyword_parsing
256
+ assert_parses true, "true"
257
+ assert_parses false, "false"
258
+ assert_parses nil, "null"
259
+ end
260
+
261
+ def test_number_parsing
262
+ assert_parses 42, "42"
263
+ assert_parses -13, "-13"
264
+ assert_parses 3.1415, "3.1415"
265
+ assert_parses -0.01, "-0.01"
266
+
267
+ assert_parses 0.2e1, "0.2e1"
268
+ assert_parses 0.2e+1, "0.2e+1"
269
+ assert_parses 0.2e-1, "0.2e-1"
270
+ assert_parses 0.2E1, "0.2e1"
271
+ end
272
+
273
+ def test_string_parsing
274
+ assert_parses String.new, '""'
275
+ assert_parses "JSON", '"JSON"'
276
+
277
+ assert_parses 'nested "quotes"', '"nested \"quotes\""'
278
+ assert_parses "\n", '"\\n"'
279
+
280
+ assert_parses "µ", '"\\u00b5"'
281
+ end
282
+
283
+ def test_array_parsing
284
+ assert_parses [], '[]'
285
+
286
+ assert_parses ["foo", "bar", "baz"], '["foo", "bar", "baz"]'
287
+ assert_parses ["JSON", 3.1415, true], '["JSON", 3.1415, true]'
288
+ assert_parses [1, [2, [3]]], '[1, [2, [3]]]'
289
+ end
290
+
291
+ def test_object_parsing
292
+ assert_parses Hash[], '{}'
293
+ assert_parses Hash["foo" => "bar"], '{"foo": "bar"}'
294
+ assert_parses Hash["foo" => "bar", "baz" => "qux"], '{"foo": "bar", "baz": "qux"}'
295
+ assert_parses Hash["JSON" => 3.1415, "data" => true], '{"JSON": 3.1415, "data": true}'
296
+
297
+ assert_parses Hash["Array" => [1, 2, 3], "Object" => {"nested" => "objects"}],
298
+ '{"Array": [1, 2, 3], "Object": {"nested": "objects"}}'
299
+ end
300
+
301
+ def test_parse_errors
302
+ assert_invalid "{"
303
+ assert_invalid %q{{"key": true false}}
304
+
305
+ assert_invalid "["
306
+ assert_invalid "[1,,2]"
307
+ assert_invalid '"'
308
+ assert_invalid '"\\i"'
309
+
310
+ assert_invalid "$1,000"
311
+ assert_invalid "1_000"
312
+ assert_invalid "1K"
313
+
314
+ assert_invalid "unknown"
315
+ end
316
+
317
+ def assert_invalid(json_string)
318
+ assert_raises(RuntimeError) { JSONParser.parse(json_string) }
319
+ end
320
+
321
+ def assert_parses(expected_val, json_string)
322
+ if expected_val.nil?
323
+ assert_nil(JSONParser.parse(json_string))
324
+ else
325
+ assert_equal(expected_val, JSONParser.parse(json_string))
326
+ end
327
+ end
328
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "pp"
3
4
 
4
5
  module Speculation
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "set"
3
4
  require "rantly"
4
5
  require "rantly/property"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent"
3
4
 
4
5
  module Speculation
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class EverySpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class HashSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class MergeSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class NilableSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class OrSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class PredicateSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class RegexSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
4
  # @private
4
5
  class TupleSpec < Spec
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent"
3
4
  require "pp"
4
5
  require "speculation/pmap"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "set"
3
4
 
4
5
  module Speculation
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Speculation
3
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1".freeze
4
5
  end
data/lib/speculation.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent"
3
4
  require "set"
4
5
  require "securerandom"
@@ -44,6 +45,27 @@ module Speculation
44
45
 
45
46
  INVALID = ns(:invalid)
46
47
 
48
+ # @private
49
+ OP = ns(:op)
50
+ # @private
51
+ ALT = ns(:alt)
52
+ # @private
53
+ AMP = ns(:amp)
54
+ # @private
55
+ PCAT = ns(:pcat)
56
+ # @private
57
+ REP = ns(:rep)
58
+ # @private
59
+ ACCEPT = ns(:accept)
60
+ # @private
61
+ NIL = ns(:nil)
62
+ # @private
63
+ RECURSION_LIMIT = ns(:recursion_limit)
64
+ # @private
65
+ GEN = ns(:gen)
66
+ # @private
67
+ NAME = ns(:name)
68
+
47
69
  # Can be enabled or disabled at runtime:
48
70
  # - enabled/disabled by setting `check_asserts`.
49
71
  # - enabled by setting environment variable SPECULATION_CHECK_ASSERTS to the
@@ -71,10 +93,11 @@ module Speculation
71
93
  # @return [Spec] that validates floats
72
94
  def self.float_in(min: nil, max: nil, infinite: true, nan: true)
73
95
  preds = [Float]
74
- preds << ->(x) { !x.nan? } unless nan
75
- preds << ->(x) { !x.infinite? } unless infinite
76
- preds << ->(x) { x <= max } if max
77
- preds << ->(x) { x >= min } if min
96
+
97
+ preds.push(->(x) { !x.nan? }) unless nan
98
+ preds.push(->(x) { !x.infinite? }) unless infinite
99
+ preds.push(->(x) { x <= max }) if max
100
+ preds.push(->(x) { x >= min }) if min
78
101
 
79
102
  min ||= Float::MIN
80
103
  max ||= Float::MAX
@@ -116,7 +139,7 @@ module Speculation
116
139
  # @param x [Hash, Object]
117
140
  # @return [Hash, false] x if x is a (Speculation) regex op, else logical false
118
141
  def self.regex?(x)
119
- Utils.hash?(x) && x[ns(:op)] && x
142
+ Utils.hash?(x) && x[OP] && x
120
143
  end
121
144
 
122
145
  # @param value return value of a `conform` call
@@ -250,7 +273,7 @@ module Speculation
250
273
  # @return [Proc]
251
274
  def self.gen(spec, overrides = nil)
252
275
  spec = Identifier(spec)
253
- gensub(spec, overrides, [], ns(:recursion_limit) => recursion_limit)
276
+ gensub(spec, overrides, [], RECURSION_LIMIT => recursion_limit)
254
277
  end
255
278
 
256
279
  # @private
@@ -500,7 +523,7 @@ module Speculation
500
523
  # @return [Hash] regex op that matches zero or one value matching pred. Produces a
501
524
  # single value (not a collection) if matched.
502
525
  def self.zero_or_one(pred)
503
- _alt([pred, accept(ns(:nil))], nil)
526
+ _alt([pred, accept(NIL)], nil)
504
527
  end
505
528
 
506
529
  # @param kv_specs [Hash] key+pred pairs
@@ -531,7 +554,7 @@ module Speculation
531
554
  # resulting value to the conjunction of the predicates, and any conforming
532
555
  # they might perform.
533
556
  def self.constrained(re, *preds)
534
- { ns(:op) => ns(:amp), :p1 => re, :predicates => preds }
557
+ { OP => AMP, :p1 => re, :predicates => preds }
535
558
  end
536
559
 
537
560
  # @yield [value] predicate function with the semantics of conform i.e. it should
@@ -555,7 +578,8 @@ module Speculation
555
578
  # @param gen [Proc] generator proc, which must be a proc of one arg (Rantly
556
579
  # instance) that generates a valid value.
557
580
  # @return [Spec]
558
- # @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
581
+ # @see fdef See 'fdef' for a single operation that creates an fspec and registers it, as well as a
582
+ # full description of :args, :block, :ret and :fn
559
583
  def self.fspec(args: nil, ret: nil, fn: nil, block: nil, gen: nil)
560
584
  FSpec.new(:args => spec(args), :ret => spec(ret), :fn => spec(fn), :block => spec(block)).tap do |spec|
561
585
  spec.gen = gen
@@ -645,7 +669,7 @@ module Speculation
645
669
 
646
670
  # @private
647
671
  def self.recur_limit?(rmap, id, path, k)
648
- rmap[id] > rmap[ns(:recursion_limit)] &&
672
+ rmap[id] > rmap[RECURSION_LIMIT] &&
649
673
  path.include?(k)
650
674
  end
651
675
 
@@ -731,7 +755,7 @@ module Speculation
731
755
  p = reg_resolve!(p)
732
756
 
733
757
  id, op, ps, ks, p1, p2, ret, id, gen = p.values_at(
734
- :id, ns(:op), :predicates, :keys, :p1, :p2, :return_value, :id, ns(:gen)
758
+ :id, OP, :predicates, :keys, :p1, :p2, :return_value, :id, GEN
735
759
  ) if regex?(p)
736
760
 
737
761
  id = p.id if spec?(p)
@@ -767,8 +791,8 @@ module Speculation
767
791
 
768
792
  if p
769
793
  case op
770
- when ns(:accept)
771
- if ret == ns(:nil)
794
+ when ACCEPT
795
+ if ret == NIL
772
796
  ->(_rantly) { [] }
773
797
  else
774
798
  ->(_rantly) { [ret] }
@@ -777,9 +801,9 @@ module Speculation
777
801
  g = gensub(p, overrides, path, rmap)
778
802
 
779
803
  ->(rantly) { [g.call(rantly)] }
780
- when ns(:amp)
804
+ when AMP
781
805
  re_gen(p1, overrides, path, rmap)
782
- when ns(:pcat)
806
+ when PCAT
783
807
  gens = ggens.call(ps, ks)
784
808
 
785
809
  if gens.all?
@@ -787,11 +811,11 @@ module Speculation
787
811
  gens.flat_map { |gg| gg.call(rantly) }
788
812
  end
789
813
  end
790
- when ns(:alt)
814
+ when ALT
791
815
  gens = ggens.call(ps, ks).compact
792
816
 
793
817
  ->(rantly) { rantly.branch(*gens) } unless gens.empty?
794
- when ns(:rep)
818
+ when REP
795
819
  if recur_limit?(rmap, id, [id], id)
796
820
  ->(_rantly) { [] }
797
821
  else
@@ -809,26 +833,17 @@ module Speculation
809
833
 
810
834
  # @private
811
835
  def self.re_conform(regex, data)
812
- x, *xs = data
813
-
814
- if data.empty?
815
- return INVALID unless accept_nil?(regex)
836
+ data.each do |x|
837
+ regex = deriv(regex, x)
838
+ return INVALID unless regex
839
+ end
816
840
 
841
+ if accept_nil?(regex)
817
842
  return_value = preturn(regex)
818
843
 
819
- if return_value == ns(:nil)
820
- nil
821
- else
822
- return_value
823
- end
844
+ return_value == NIL ? nil : return_value
824
845
  else
825
- dp = deriv(regex, x)
826
-
827
- if dp
828
- re_conform(dp, xs)
829
- else
830
- INVALID
831
- end
846
+ INVALID
832
847
  end
833
848
  end
834
849
 
@@ -845,7 +860,7 @@ module Speculation
845
860
  end
846
861
 
847
862
  if accept?(p)
848
- if p[ns(:op)] == ns(:pcat)
863
+ if p[OP] == PCAT
849
864
  return op_explain(p, path, via, Utils.conj(inn, index), input[index..-1])
850
865
  else
851
866
  return [{ :path => path,
@@ -908,7 +923,7 @@ module Speculation
908
923
  if Utils.ident?(spec)
909
924
  spec
910
925
  elsif regex?(spec)
911
- spec.merge(ns(:name) => name)
926
+ spec.merge(NAME => name)
912
927
  else
913
928
  spec.tap { |s| s.name = name }
914
929
  end
@@ -918,7 +933,7 @@ module Speculation
918
933
  if Utils.ident?(spec)
919
934
  spec
920
935
  elsif regex?(spec)
921
- spec[ns(:name)]
936
+ spec[NAME]
922
937
  elsif spec.respond_to?(:name)
923
938
  spec.name
924
939
  end
@@ -950,17 +965,12 @@ module Speculation
950
965
  end
951
966
 
952
967
  def and_preds(x, preds)
953
- pred, *preds = preds
954
-
955
- x = dt(pred, x)
956
-
957
- if invalid?(x)
958
- INVALID
959
- elsif preds.empty?
960
- x
961
- else
962
- and_preds(x, preds)
968
+ preds.each do |pred|
969
+ x = dt(pred, x)
970
+ return INVALID if invalid?(x)
963
971
  end
972
+
973
+ x
964
974
  end
965
975
 
966
976
  def specize(spec)
@@ -979,12 +989,12 @@ module Speculation
979
989
  ### regex ###
980
990
 
981
991
  def accept(x)
982
- { ns(:op) => ns(:accept), :return_value => x }
992
+ { OP => ACCEPT, :return_value => x }
983
993
  end
984
994
 
985
995
  def accept?(hash)
986
996
  if hash.is_a?(Hash)
987
- hash[ns(:op)] == ns(:accept)
997
+ hash[OP] == ACCEPT
988
998
  end
989
999
  end
990
1000
 
@@ -997,7 +1007,7 @@ module Speculation
997
1007
  return unless regex[:predicates].all?
998
1008
 
999
1009
  unless accept?(predicate)
1000
- return { ns(:op) => ns(:pcat),
1010
+ return { OP => PCAT,
1001
1011
  :predicates => regex[:predicates],
1002
1012
  :keys => keys,
1003
1013
  :return_value => regex[:return_value] }
@@ -1006,7 +1016,7 @@ module Speculation
1006
1016
  val = keys ? { key => predicate[:return_value] } : predicate[:return_value]
1007
1017
  return_value = Utils.conj(regex[:return_value], val)
1008
1018
 
1009
- if rest_predicates
1019
+ if rest_predicates.any?
1010
1020
  pcat(:predicates => rest_predicates,
1011
1021
  :keys => rest_keys,
1012
1022
  :return_value => return_value)
@@ -1018,7 +1028,7 @@ module Speculation
1018
1028
  def rep(p1, p2, return_value, splice)
1019
1029
  return unless p1
1020
1030
 
1021
- regex = { ns(:op) => ns(:rep), :p2 => p2, :splice => splice, :id => SecureRandom.uuid }
1031
+ regex = { OP => REP, :p2 => p2, :splice => splice, :id => SecureRandom.uuid }
1022
1032
 
1023
1033
  if accept?(p1)
1024
1034
  regex.merge(:p1 => p2, :return_value => Utils.conj(return_value, p1[:return_value]))
@@ -1027,23 +1037,23 @@ module Speculation
1027
1037
  end
1028
1038
  end
1029
1039
 
1030
- def filter_alt(ps, ks, &block)
1040
+ def filter_alt(ps, ks)
1031
1041
  if ks
1032
- pks = ps.zip(ks).select { |xs| yield(xs.first) }
1042
+ pks = ps.zip(ks).select { |(p, _k)| yield(p) }
1033
1043
  [pks.map(&:first), pks.map(&:last)]
1034
1044
  else
1035
- [ps.select(&block), ks]
1045
+ [ps.select { |p| yield(p) }, ks]
1036
1046
  end
1037
1047
  end
1038
1048
 
1039
1049
  def _alt(predicates, keys)
1040
- predicates, keys = filter_alt(predicates, keys, &Utils.method(:itself))
1041
- return unless predicates
1050
+ predicates, keys = filter_alt(predicates, keys) { |p| p }
1051
+ return if predicates.empty?
1042
1052
 
1043
1053
  predicate, *rest_predicates = predicates
1044
1054
  key, *_rest_keys = keys
1045
1055
 
1046
- return_value = { ns(:op) => ns(:alt), :predicates => predicates, :keys => keys }
1056
+ return_value = { OP => ALT, :predicates => predicates, :keys => keys }
1047
1057
  return return_value unless rest_predicates.empty?
1048
1058
 
1049
1059
  return predicate unless key
@@ -1061,24 +1071,24 @@ module Speculation
1061
1071
  end
1062
1072
 
1063
1073
  def no_ret?(p1, pret)
1064
- return true if pret == ns(:nil)
1074
+ return true if pret == NIL
1065
1075
 
1066
1076
  regex = reg_resolve!(p1)
1067
- op = regex[ns(:op)]
1077
+ op = regex[OP]
1068
1078
 
1069
- [ns(:rep), ns(:pcat)].include?(op) && pret.empty? || nil
1079
+ [REP, PCAT].include?(op) && pret.empty? || nil
1070
1080
  end
1071
1081
 
1072
1082
  def accept_nil?(regex)
1073
1083
  regex = reg_resolve!(regex)
1074
1084
  return unless regex?(regex)
1075
1085
 
1076
- case regex[ns(:op)]
1077
- when ns(:accept) then true
1078
- when ns(:pcat) then regex[:predicates].all?(&method(:accept_nil?))
1079
- when ns(:alt) then regex[:predicates].any?(&method(:accept_nil?))
1080
- when ns(:rep) then (regex[:p1] == regex[:p2]) || accept_nil?(regex[:p1])
1081
- when ns(:amp)
1086
+ case regex[OP]
1087
+ when ACCEPT then true
1088
+ when PCAT then regex[:predicates].all? { |p| accept_nil?(p) }
1089
+ when ALT then regex[:predicates].any? { |p| accept_nil?(p) }
1090
+ when REP then (regex[:p1] == regex[:p2]) || accept_nil?(regex[:p1])
1091
+ when AMP
1082
1092
  p1 = regex[:p1]
1083
1093
 
1084
1094
  return false unless accept_nil?(p1)
@@ -1086,7 +1096,7 @@ module Speculation
1086
1096
  no_ret?(p1, preturn(p1)) ||
1087
1097
  !invalid?(and_preds(preturn(p1), regex[:predicates]))
1088
1098
  else
1089
- raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
1099
+ raise "Unexpected #{OP} #{regex[OP]}"
1090
1100
  end
1091
1101
  end
1092
1102
 
@@ -1095,36 +1105,32 @@ module Speculation
1095
1105
  return unless regex?(regex)
1096
1106
 
1097
1107
  p0, *_pr = regex[:predicates]
1098
- k, *ks = regex[:keys]
1108
+ k, *_ks = regex[:keys]
1099
1109
 
1100
- case regex[ns(:op)]
1101
- when ns(:accept) then regex[:return_value]
1102
- when ns(:pcat) then add_ret(p0, regex[:return_value], k)
1103
- when ns(:rep) then add_ret(regex[:p1], regex[:return_value], k)
1104
- when ns(:amp)
1110
+ case regex[OP]
1111
+ when ACCEPT then regex[:return_value]
1112
+ when PCAT then add_ret(p0, regex[:return_value], k)
1113
+ when REP then add_ret(regex[:p1], regex[:return_value], k)
1114
+ when AMP
1105
1115
  pret = preturn(regex[:p1])
1106
1116
 
1107
1117
  if no_ret?(regex[:p1], pret)
1108
- ns(:nil)
1118
+ NIL
1109
1119
  else
1110
1120
  and_preds(pret, regex[:predicates])
1111
1121
  end
1112
- when ns(:alt)
1113
- ps, ks = filter_alt(regex[:predicates], regex[:keys], &method(:accept_nil?))
1122
+ when ALT
1123
+ pred, key = regex[:predicates].zip(Array(regex[:keys])).find { |(p, _k)| accept_nil?(p) }
1114
1124
 
1115
- r = if ps.first.nil?
1116
- ns(:nil)
1125
+ r = if pred.nil?
1126
+ NIL
1117
1127
  else
1118
- preturn(ps.first)
1128
+ preturn(pred)
1119
1129
  end
1120
1130
 
1121
- if ks && ks.first
1122
- [ks.first, r]
1123
- else
1124
- r
1125
- end
1131
+ key ? [key, r] : r
1126
1132
  else
1127
- raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
1133
+ raise "Unexpected #{OP} #{regex[OP]}"
1128
1134
  end
1129
1135
  end
1130
1136
 
@@ -1132,30 +1138,27 @@ module Speculation
1132
1138
  regex = reg_resolve!(regex)
1133
1139
  return r unless regex?(regex)
1134
1140
 
1135
- prop = -> do
1141
+ case regex[OP]
1142
+ when ACCEPT, ALT, AMP
1136
1143
  return_value = preturn(regex)
1137
1144
 
1138
- if return_value.empty?
1145
+ if return_value == NIL
1139
1146
  r
1140
1147
  else
1141
- val = key ? { key => return_value } : return_value
1142
-
1143
- regex[:splice] ? Utils.into(r, val) : Utils.conj(r, val)
1148
+ Utils.conj(r, key ? { key => return_value } : return_value)
1144
1149
  end
1145
- end
1146
-
1147
- case regex[ns(:op)]
1148
- when ns(:accept), ns(:alt), ns(:amp)
1150
+ when PCAT, REP
1149
1151
  return_value = preturn(regex)
1150
1152
 
1151
- if return_value == ns(:nil)
1153
+ if return_value.empty?
1152
1154
  r
1153
1155
  else
1154
- Utils.conj(r, key ? { key => return_value } : return_value)
1156
+ val = key ? { key => return_value } : return_value
1157
+
1158
+ regex[:splice] ? Utils.into(r, val) : Utils.conj(r, val)
1155
1159
  end
1156
- when ns(:pcat), ns(:rep) then prop.call
1157
1160
  else
1158
- raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
1161
+ raise "Unexpected #{OP} #{regex[OP]}"
1159
1162
  end
1160
1163
  end
1161
1164
 
@@ -1178,9 +1181,9 @@ module Speculation
1178
1181
  pred, *rest_preds = predicates
1179
1182
  key, *rest_keys = keys
1180
1183
 
1181
- case regex[ns(:op)]
1182
- when ns(:accept) then nil
1183
- when ns(:pcat)
1184
+ case regex[OP]
1185
+ when ACCEPT then nil
1186
+ when PCAT
1184
1187
  regex1 = pcat(:predicates => [deriv(pred, value), *rest_preds], :keys => keys, :return_value => return_value)
1185
1188
  regex2 = nil
1186
1189
 
@@ -1192,9 +1195,9 @@ module Speculation
1192
1195
  end
1193
1196
 
1194
1197
  alt2(regex1, regex2)
1195
- when ns(:alt)
1198
+ when ALT
1196
1199
  _alt(predicates.map { |p| deriv(p, value) }, keys)
1197
- when ns(:rep)
1200
+ when REP
1198
1201
  regex1 = rep(deriv(p1, value), p2, return_value, splice)
1199
1202
  regex2 = nil
1200
1203
 
@@ -1203,18 +1206,18 @@ module Speculation
1203
1206
  end
1204
1207
 
1205
1208
  alt2(regex1, regex2)
1206
- when ns(:amp)
1209
+ when AMP
1207
1210
  p1 = deriv(p1, value)
1208
1211
  return unless p1
1209
1212
 
1210
- if p1[ns(:op)] == ns(:accept)
1213
+ if p1[OP] == ACCEPT
1211
1214
  ret = and_preds(preturn(p1), predicates)
1212
1215
  accept(ret) unless invalid?(ret)
1213
1216
  else
1214
1217
  constrained(p1, *predicates)
1215
1218
  end
1216
1219
  else
1217
- raise "Unexpected #{ns(:op)} #{regex[ns(:op)]}"
1220
+ raise "Unexpected #{OP} #{regex[OP]}"
1218
1221
  end
1219
1222
  end
1220
1223
 
@@ -1242,9 +1245,9 @@ module Speculation
1242
1245
  end
1243
1246
  end
1244
1247
 
1245
- case p[ns(:op)]
1246
- when ns(:accept) then nil
1247
- when ns(:amp)
1248
+ case p[OP]
1249
+ when ACCEPT then nil
1250
+ when AMP
1248
1251
  if input.empty?
1249
1252
  if accept_nil?(p[:p1])
1250
1253
  explain_pred_list(p[:predicates], path, via, inn, preturn(p[:p1]))
@@ -1260,13 +1263,14 @@ module Speculation
1260
1263
  op_explain(p[:p1], path, via, inn, input)
1261
1264
  end
1262
1265
  end
1263
- when ns(:pcat)
1264
- pks = p[:predicates].zip(p[:keys] || [])
1266
+ when PCAT
1267
+ pks = p[:predicates].zip(Array(p[:keys]))
1265
1268
  pred, k = if pks.count == 1
1266
1269
  pks.first
1267
1270
  else
1268
- pks.lazy.reject { |(predicate, _)| accept_nil?(predicate) }.first
1271
+ pks.find { |(predicate, _)| !accept_nil?(predicate) }
1269
1272
  end
1273
+
1270
1274
  path = Utils.conj(path, k) if k
1271
1275
 
1272
1276
  if input.empty? && !pred
@@ -1274,18 +1278,18 @@ module Speculation
1274
1278
  else
1275
1279
  op_explain(pred, path, via, inn, input)
1276
1280
  end
1277
- when ns(:alt)
1281
+ when ALT
1278
1282
  return insufficient(p, path, via, inn) if input.empty?
1279
1283
 
1280
- probs = p[:predicates].zip(p[:keys]).flat_map { |(predicate, key)|
1284
+ probs = p[:predicates].zip(Array(p[:keys])).flat_map { |(predicate, key)|
1281
1285
  op_explain(predicate, key ? Utils.conj(path, key) : path, via, inn, input)
1282
1286
  }
1283
1287
 
1284
1288
  probs.compact
1285
- when ns(:rep)
1289
+ when REP
1286
1290
  op_explain(p[:p1], path, via, inn, input)
1287
1291
  else
1288
- raise "Unexpected #{ns(:op)} #{p[ns(:op)]}"
1292
+ raise "Unexpected #{OP} #{p[OP]}"
1289
1293
  end
1290
1294
  end
1291
1295
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: speculation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie English
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-10 00:00:00.000000000 Z
11
+ date: 2017-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -168,6 +168,7 @@ files:
168
168
  - bin/console
169
169
  - bin/setup
170
170
  - examples/codebreaker.rb
171
+ - examples/json_parser.rb
171
172
  - examples/sinatra-web-app/Gemfile
172
173
  - examples/sinatra-web-app/Gemfile.lock
173
174
  - examples/sinatra-web-app/app.rb