egison 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: []