egison 0.2.1 → 0.3.0

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: c0a67c9b1b9350d0d0c09b823f1b988626a5a100
4
- data.tar.gz: 244276e1bb15b529bf0084c207f0e0735a640294
3
+ metadata.gz: 74ce8492a345ad492c9acb9e16c7c8d351a6b327
4
+ data.tar.gz: 26f3f0aef845aaf21312713df5e418b724d4cbb0
5
5
  SHA512:
6
- metadata.gz: 9dbeac11189e83c9f9236c62dbbb4879a1137f368932021088e2472a4b503ad6dad0ead29be8095b53ca5bfab7d2214bfee866edab166d0b523989d91dfc34c0
7
- data.tar.gz: 226cebb19768ea435db9ce4f420961c69a4160cfa9fb579535a55a631ad9e56d53975e41ea19df1e0ae6508d7928df9cb990c0e9ff21b2bad62a6b95e25061da
6
+ metadata.gz: 84f1a8ef64f513ea072ae2c6153c5bc3fd8868f065af6b74209797a949fe3573e0318a34f3de9b610003d76ee6745f8961c17856ca560be87f11e7e8bc20283e
7
+ data.tar.gz: dd5c108d612a531bdde3b27ac1238af2ddd136259059eb9114f6f7af5304d140b24307119a485a45d37dfdbf26e5829d47b2538908b4b6909f349f1760d742c1
data/README.md CHANGED
@@ -146,6 +146,22 @@ match_all([1, 2, 3, 2, 5]) do
146
146
  end #=> [2,2]
147
147
  ```
148
148
 
149
+ ### Pattern Matching against Stream (Infinite List)
150
+
151
+ We can do pattern-matching against streams with the `match_stream` expression.
152
+
153
+ ```
154
+ def nats
155
+ (1..Float::INFINITY)
156
+ end
157
+
158
+ match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)
159
+ #=>[[1, 2], [1, 3], [2, 1], [1, 4], [2, 3], [3, 1], [1, 5], [2, 4], [3, 2], [4, 1]]
160
+
161
+ match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)
162
+ #=>[[1, 1], [1, 2], [2, 1], [1, 3], [2, 2], [3, 1], [1, 4], [2, 3], [3, 2], [4, 1]]
163
+ ```
164
+
149
165
  ## Demonstrations
150
166
 
151
167
  ### Combinations
@@ -210,6 +226,25 @@ p(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond
210
226
  p(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Nothing"
211
227
  ```
212
228
 
229
+ ### Twin Primes
230
+
231
+ The following code enumerates all twin primes with pattern-matching!
232
+ I believe it is also a really exciting demonstration.
233
+
234
+ ```
235
+ require 'egison'
236
+ require 'prime'
237
+
238
+ twin_primes = match_stream(Prime) {
239
+ with(List.(*_, _x, __("x + 2"), *_)) {
240
+ [x, x + 2]
241
+ }
242
+ }
243
+
244
+ p twin_primes.take(10)
245
+ #=>[[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]
246
+ ```
247
+
213
248
  You can find more demonstrations in the [`sample`](https://github.com/egison/egison-ruby/tree/master/sample) directory.
214
249
 
215
250
  ## About Egison
@@ -0,0 +1,5 @@
1
+ I thank the following people for their great contribution.
2
+
3
+ * [Takasuke Nakamura](https://github.com/xyx-is)
4
+ * [Kazuki Tanaka](https://github.com/gogotanaka)
5
+ * [Shunsuke Gotoh](https://github.com/antimon2)
@@ -1,5 +1,6 @@
1
1
  require 'egison/version'
2
- require 'continuation'
2
+ # require 'continuation'
3
+ require 'egison/lazyarray'
3
4
 
4
5
  module PatternMatch
5
6
  module Matchable
@@ -47,6 +48,39 @@ module PatternMatch
47
48
  end
48
49
  end
49
50
 
51
+ class MatchingStateStream
52
+ def initialize(pat, tgt)
53
+ @states = [MatchingState.new(pat, tgt)]
54
+ @processes = []
55
+ end
56
+
57
+ def match(&block)
58
+ state = @states.shift
59
+ @processes << Egison::LazyArray.new(state.process_stream)
60
+ until @states.empty? && @processes.empty?
61
+ unless @processes.empty?
62
+ process(@processes.shift, &block)
63
+ end
64
+ unless @states.empty?
65
+ state = @states.shift
66
+ process(Egison::LazyArray.new(state.process_stream), &block)
67
+ end
68
+ end
69
+ end
70
+
71
+ def process(process_iter, &block)
72
+ unless process_iter.empty?
73
+ @processes << process_iter
74
+ ret = process_iter.shift
75
+ if ret.atoms.empty?
76
+ block.(ret.bindings)
77
+ else
78
+ @states << ret
79
+ end
80
+ end
81
+ end
82
+ end
83
+
50
84
  class MatchingState
51
85
  attr_accessor :atoms, :bindings
52
86
 
@@ -65,6 +99,17 @@ module PatternMatch
65
99
  new_state
66
100
  end
67
101
  end
102
+
103
+ def process_stream(&block)
104
+ return to_enum :process_stream unless block_given?
105
+ atom = @atoms.shift
106
+ atom.first.match_stream(atom.last, @bindings) do |new_atoms, new_bindings|
107
+ new_state = clone
108
+ new_state.atoms = new_atoms + new_state.atoms
109
+ new_state.bindings += new_bindings
110
+ block.(new_state)
111
+ end
112
+ end
68
113
  end
69
114
 
70
115
  class Pattern
@@ -76,6 +121,10 @@ module PatternMatch
76
121
  def match(tgt, bindings)
77
122
  end
78
123
 
124
+ def match_stream(tgt, bindings, &block)
125
+ match(tgt, bindings).each(&block)
126
+ end
127
+
79
128
  def to_a
80
129
  [PatternCollection.new(self)]
81
130
  end
@@ -128,6 +177,32 @@ module PatternMatch
128
177
  end
129
178
  end
130
179
  end
180
+
181
+ def match_stream(tgt, bindings, &block)
182
+ if subpatterns.empty?
183
+ if tgt.empty?
184
+ return block.([[], []])
185
+ end
186
+ else
187
+ subpatterns = @subpatterns.clone
188
+ px = subpatterns.shift
189
+ if px.quantified
190
+ if subpatterns.empty?
191
+ block.([[[px.pattern, tgt]], []])
192
+ else
193
+ @matcher.unjoin_stream(tgt) do |xs, ys|
194
+ block.([[px.pattern, xs], [PatternWithMatcher.new(@matcher, *subpatterns), ys]], [])
195
+ end
196
+ end
197
+ else
198
+ unless tgt.empty?
199
+ @matcher.uncons_stream(tgt) do |x, xs|
200
+ block.([[px, x], [PatternWithMatcher.new(@matcher, *subpatterns), xs]], [])
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
131
206
  end
132
207
 
133
208
  class Wildcard < PatternElement
@@ -337,6 +412,20 @@ module PatternMatch
337
412
  end
338
413
  end
339
414
 
415
+ class EnvE < Env
416
+ def with(pat, &block)
417
+ ctx = @ctx
418
+ tgt = @tgt
419
+ mstack = MatchingStateStream.new(pat,tgt)
420
+ ::Enumerator.new do |y|
421
+ mstack.match do |bindings|
422
+ y << with_bindings(ctx, bindings, &block)
423
+ end
424
+ end
425
+ rescue PatternNotMatch
426
+ end
427
+ end
428
+
340
429
  class PatternNotMatch < Exception; end
341
430
  class PatternMatchError < StandardError; end
342
431
  class NoMatchingPatternError < PatternMatchError; end
@@ -351,7 +440,7 @@ module PatternMatch
351
440
  private_constant c
352
441
  end
353
442
  end
354
- private_constant :Env, :Env2
443
+ private_constant :Env, :Env2, :EnvE
355
444
  end
356
445
  end
357
446
 
@@ -363,6 +452,14 @@ module Kernel
363
452
  env.instance_eval(&block)
364
453
  end
365
454
 
455
+ def match_stream(tgt, &block)
456
+ if !(tgt.kind_of?(Array) || tgt.kind_of?(Egison::LazyArray))
457
+ tgt = Egison::LazyArray.new(tgt)
458
+ end
459
+ env = PatternMatch.const_get(:EnvE).new(self, tgt)
460
+ env.instance_eval(&block)
461
+ end
462
+
366
463
  def match(tgt, &block)
367
464
  env = PatternMatch.const_get(:Env2).new(self, tgt)
368
465
  catch(:exit_match) do
@@ -372,4 +469,3 @@ module Kernel
372
469
 
373
470
  alias match_single match
374
471
  end
375
-
@@ -0,0 +1,166 @@
1
+ module Egison
2
+ class LazyArray
3
+ include Enumerable
4
+
5
+ class OrgEnum
6
+ def initialize(org_enum)
7
+ @src_enums = []
8
+ if org_enum.kind_of?(::Array)
9
+ @org_enum = [].to_enum # DUMMY
10
+ @cache = org_enum
11
+ @index = -1
12
+ @terminated = true
13
+ else
14
+ @org_enum = org_enum.to_enum
15
+ @cache = []
16
+ @index = -1
17
+ @terminated = false
18
+ end
19
+ end
20
+
21
+ def next
22
+ index = @index += 1
23
+ return @cache[index] if @cache.size > index
24
+ raise StopIteration.new('iteration reached an end') if @terminated
25
+ el = org_enum_next
26
+ @cache << el
27
+ el
28
+ rescue StopIteration => ex
29
+ @index -= 1
30
+ raise ex
31
+ end
32
+
33
+ def rewind(index=0)
34
+ @index = index - 1
35
+ end
36
+
37
+ def clone
38
+ obj = super
39
+ obj.instance_eval do
40
+ @src_enums = @src_enums.clone
41
+ end
42
+ obj
43
+ end
44
+
45
+ def concat other
46
+ if @terminated && other.kind_of?(::Array)
47
+ @cache.concat(other)
48
+ else
49
+ @src_enums.push(other)
50
+ @terminated = false
51
+ end
52
+ self
53
+ end
54
+
55
+ private
56
+ def org_enum_next
57
+ el = nil
58
+ while el.nil?
59
+ begin
60
+ el = @org_enum.next
61
+ rescue StopIteration => ex
62
+ if @src_enums.empty?
63
+ @terminated = true
64
+ raise ex
65
+ end
66
+ @org_enum = @src_enums.shift.to_enum
67
+ @cache = @cache.clone
68
+ end
69
+ end
70
+ el
71
+ end
72
+ end
73
+
74
+ private_constant :OrgEnum if respond_to?(:private_constant)
75
+
76
+ def initialize(org_enum)
77
+ @org_enum = OrgEnum.new(org_enum)
78
+ @cache = []
79
+ @terminated = false
80
+ end
81
+
82
+ def each(&block)
83
+ return to_enum unless block_given?
84
+ @cache.each(&block)
85
+ return if @terminated
86
+ while true # StopIteration will NOT be raised if `loop do ... end`
87
+ el = @org_enum.next
88
+ @cache.push(el)
89
+ block.(el)
90
+ end
91
+ rescue StopIteration => ex
92
+ @terminated = true
93
+ end
94
+
95
+ def shift
96
+ if @cache.size > 0
97
+ @cache.shift
98
+ elsif @terminated
99
+ nil
100
+ else
101
+ begin
102
+ @org_enum.next
103
+ rescue StopIteration => ex
104
+ @terminated = true
105
+ nil
106
+ end
107
+ end
108
+ end
109
+
110
+ def unshift(*obj)
111
+ @cache.unshift(*obj)
112
+ self
113
+ end
114
+
115
+ def empty?
116
+ return false unless @cache.empty?
117
+ return true if @terminated
118
+ begin
119
+ @cache << @org_enum.next
120
+ false
121
+ rescue StopIteration => ex
122
+ @terminated = true
123
+ true
124
+ end
125
+ end
126
+
127
+ def size
128
+ @terminated ? @cache.size : nil
129
+ end
130
+ alias :length :size
131
+
132
+ def clone
133
+ obj = super
134
+ obj.instance_eval do
135
+ @org_enum = @org_enum.clone
136
+ @cache = @cache.clone
137
+ end
138
+ obj
139
+ end
140
+ alias :dup :clone
141
+
142
+ def concat other
143
+ @org_enum.concat(other)
144
+ @terminated = false
145
+ self
146
+ end
147
+
148
+ def + other
149
+ clone.concat(other)
150
+ end
151
+
152
+ def inspect
153
+ "\#<#{self.class.name}#{@terminated ? @cache.inspect : "[#{@cache.join(', ')}...]"}>"
154
+ end
155
+ end
156
+ end
157
+
158
+ class ::Array
159
+ alias :org_plus_meth_esc_by_egison_lazyarray :+
160
+ def + other
161
+ if other.kind_of?(Egison::LazyArray)
162
+ return other.clone.unshift(*self)
163
+ end
164
+ org_plus_meth_esc_by_egison_lazyarray(other)
165
+ end
166
+ end
@@ -1,4 +1,5 @@
1
1
  require 'egison/core'
2
+ require 'egison/lazyarray'
2
3
 
3
4
  class Class
4
5
  include PatternMatch::Matchable
@@ -7,11 +8,20 @@ class Class
7
8
  raise NotImplementedError, "need to define `#{__method__}'"
8
9
  end
9
10
 
11
+ def uncons_stream(val, &block)
12
+ raise NotImplementedError, "need to define `#{__method__}'"
13
+ end
14
+
10
15
  private
11
16
 
12
17
  def accept_array_only(val)
13
18
  raise PatternMatch::PatternNotMatch unless val.kind_of?(Array)
14
19
  end
20
+
21
+ def test_conv_lazy_array(val)
22
+ raise PatternMatch::PatternNotMatch unless val.respond_to?(:each)
23
+ Egison::LazyArray.new(val)
24
+ end
15
25
  end
16
26
 
17
27
  class List
@@ -25,6 +35,15 @@ class << List
25
35
  [[x, val2]]
26
36
  end
27
37
 
38
+ def uncons_stream(val, &block)
39
+ if !(val.kind_of?(Array) || val.kind_of?(Egison::LazyArray))
40
+ val = test_conv_lazy_array(val)
41
+ end
42
+ val2 = val.clone
43
+ x = val2.shift
44
+ block.([x, val2])
45
+ end
46
+
28
47
  def unjoin(val)
29
48
  accept_array_only(val)
30
49
  val2 = val.clone
@@ -39,4 +58,20 @@ class << List
39
58
  end
40
59
  rets
41
60
  end
61
+
62
+ def unjoin_stream(val, &block)
63
+ if !(val.kind_of?(Array) || val.kind_of?(Egison::LazyArray))
64
+ val = test_conv_lazy_array(val)
65
+ end
66
+ val2 = val.clone
67
+ xs = []
68
+ ys = val2.clone
69
+ block.([xs, ys])
70
+ until val2.empty?
71
+ x = val2.shift
72
+ ys = val2.clone
73
+ xs += [x]
74
+ block.([xs, ys])
75
+ end
76
+ end
42
77
  end
@@ -1,4 +1,5 @@
1
1
  require 'egison/core'
2
+ require 'egison/lazyarray'
2
3
  require 'egison/matcher-core'
3
4
  require 'set'
4
5
 
@@ -15,6 +16,18 @@ class << Multiset
15
16
  end
16
17
  end
17
18
 
19
+ def uncons_stream(val, &block)
20
+ if !(val.kind_of?(Array) || val.kind_of?(Egison::LazyArray))
21
+ val = test_conv_lazy_array(val)
22
+ end
23
+ stream = match_stream(val) {
24
+ with(List.(*_hs, _x, *_ts)) do
25
+ [x, hs + ts]
26
+ end
27
+ }
28
+ stream.each(&block)
29
+ end
30
+
18
31
  def unjoin(val)
19
32
  accept_array_only(val)
20
33
  val2 = val.clone
@@ -31,6 +44,26 @@ class << Multiset
31
44
  rets
32
45
  end
33
46
  end
47
+
48
+ def unjoin_stream(val, &block)
49
+ if !(val.kind_of?(Array) || val.kind_of?(Egison::LazyArray))
50
+ val = test_conv_lazy_array(val)
51
+ end
52
+ val2 = val.clone
53
+ xs = []
54
+ ys = val2.clone
55
+ if val2.empty?
56
+ block.([xs, ys])
57
+ else
58
+ x = val2.shift
59
+ ys = val2.clone
60
+ rets_stream = Egison::LazyArray.new(to_enum(:unjoin_stream, ys))
61
+ # rets_stream.each{|xs2, ys2| block.([xs2, [x] + ys2])}
62
+ rets_stream.each{|xs2, ys2| block.([xs2, ys2.clone.unshift(x)])}
63
+ # rets_stream.each{|xs2, ys2| block.([[x] + xs2, ys2])}
64
+ rets_stream.each{|xs2, ys2| block.([xs2.clone.unshift(x), ys2])}
65
+ end
66
+ end
34
67
  end
35
68
 
36
69
  class << Set
@@ -43,6 +76,18 @@ class << Set
43
76
  end
44
77
  end
45
78
 
79
+ def uncons_stream(val, &block)
80
+ if !(val.kind_of?(Array) || val.kind_of?(Egison::LazyArray))
81
+ val = test_conv_lazy_array(val)
82
+ end
83
+ stream = match_stream(val) {
84
+ with(List.(*_, _x, *_)) do
85
+ [x, val]
86
+ end
87
+ }
88
+ stream.each(&block)
89
+ end
90
+
46
91
  def unjoin(val)
47
92
  accept_array_only(val)
48
93
  val2 = val.clone
@@ -59,4 +104,22 @@ class << Set
59
104
  rets
60
105
  end
61
106
  end
107
+
108
+ def unjoin_stream(val, &block)
109
+ if !(val.kind_of?(Array) || val.kind_of?(Egison::LazyArray))
110
+ val = test_conv_lazy_array(val)
111
+ end
112
+ val2 = val.clone
113
+ xs = []
114
+ ys = val2.clone
115
+ if val2.empty?
116
+ block.([xs, ys])
117
+ else
118
+ x = val2.shift
119
+ ys2 = val2.clone
120
+ rets_stream = Egison::LazyArray.new(to_enum(:unjoin_stream, ys2))
121
+ rets_stream.each{|xs2, _| block.([xs2, ys])}
122
+ rets_stream.each{|xs2, _| block.([xs2.clone.unshift(x), ys])}
123
+ end
124
+ end
62
125
  end
@@ -1,3 +1,3 @@
1
1
  module Egison
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,20 @@
1
+ require 'egison'
2
+ require 'prime'
3
+
4
+ p(match_stream(1..5){ with(List.(*_, _x, *_, _y, *_)) { [x, y] } }.to_a)
5
+
6
+ twin_primes = match_stream(Prime) {
7
+ with(List.(*_, _x, __("x + 2"), *_)) {
8
+ [x, x + 2]
9
+ }
10
+ }
11
+
12
+ p twin_primes.take(10)
13
+
14
+ def nats
15
+ (1..Float::INFINITY)
16
+ end
17
+
18
+ p match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)
19
+
20
+ p match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'egison'
3
+ require 'prime'
4
+
5
+ def twin_primes
6
+ match_stream(Prime) {
7
+ with(List.(*_, _x, __("x + 2"), *_)) {
8
+ [x, x + 2]
9
+ }
10
+ }
11
+ end
12
+
13
+ def nats
14
+ (1..Float::INFINITY)
15
+ end
16
+
17
+ describe "sample" do
18
+ describe "stream.rb" do
19
+ it %q{match_stream(1..5){ with(List.(*_, _x, *_, _y, *_)) { [x, y] } }.to_a } do
20
+ expect(match_stream(1..5){ with(List.(*_, _x, *_, _y, *_)) { [x, y] } }.to_a).to eq \
21
+ [[1, 2], [1, 3], [2, 3], [1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [4, 5]]
22
+ end
23
+
24
+ it %q{twin_primes.take(10)} do
25
+ expect(twin_primes.take(10)).to eq \
26
+ [[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]
27
+ end
28
+
29
+ # (take 10 (match-all nats (multiset integer) [<cons $m <cons $n _>> [m n]]))
30
+ it %q{match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)} do
31
+ expect(match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)).to eq \
32
+ [[1, 2], [1, 3], [2, 1], [1, 4], [2, 3], [3, 1], [1, 5], [2, 4], [3, 2], [4, 1]]
33
+ end
34
+
35
+ # (take 10 (match-all nats (set integer) [<cons $m <cons $n _>> [m n]]))
36
+ it %q{match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)} do
37
+ expect(match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)).to eq \
38
+ [[1, 1], [1, 2], [2, 1], [1, 3], [2, 2], [3, 1], [1, 4], [2, 3], [3, 2], [4, 1]]
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: egison
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Satoshi Egi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-29 00:00:00.000000000 Z
11
+ date: 2014-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -65,9 +65,11 @@ files:
65
65
  - Makefile
66
66
  - README.md
67
67
  - Rakefile
68
+ - THANKS.md
68
69
  - egison.gemspec
69
70
  - lib/egison.rb
70
71
  - lib/egison/core.rb
72
+ - lib/egison/lazyarray.rb
71
73
  - lib/egison/matcher-core.rb
72
74
  - lib/egison/matcher.rb
73
75
  - lib/egison/version.rb
@@ -75,6 +77,7 @@ files:
75
77
  - sample/join.rb
76
78
  - sample/poker_hands.rb
77
79
  - sample/set.rb
80
+ - sample/stream.rb
78
81
  - spec/lib/egison/core_spec.rb
79
82
  - spec/lib/egison/matcher_spec.rb
80
83
  - spec/lib/egison_spec.rb
@@ -82,6 +85,7 @@ files:
82
85
  - spec/sample/join_spec.rb
83
86
  - spec/sample/poker_hands_spec.rb
84
87
  - spec/sample/set_spec.rb
88
+ - spec/sample/stream_spec.rb
85
89
  - spec/spec_helper.rb
86
90
  homepage: https://github.com/egisatoshi/egison-ruby
87
91
  licenses: []