egison 0.0.1
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 +7 -0
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/LICENSE +46 -0
- data/Makefile +3 -0
- data/README.md +206 -0
- data/Rakefile +10 -0
- data/egison.gemspec +21 -0
- data/lib/egison.rb +3 -0
- data/lib/egison/core.rb +355 -0
- data/lib/egison/matcher.rb +104 -0
- data/lib/egison/version.rb +3 -0
- data/sample/combination.rb +5 -0
- data/sample/poker_hands.rb +38 -0
- data/sample/set.rb +3 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e5afc8d46f5b374e4e9aa32fb7b8328b9c687fd3
|
4
|
+
data.tar.gz: 37e963f48ccea8f645d18b16b29cb2a96005c436
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a999b3b1b4687d3fbc6b7ec65b5883d2992ba96478a8206ee6d7bf240475bb4113730ac2062e217c9877077f6ab2294268f98e7bcf8ab40182580d3ac459561f
|
7
|
+
data.tar.gz: 8cd1ee8f05b986418f223980e5c31491562caf7ceb4c020dd211c7d3f2ccfa4924ac6021da90ea6f029476c9dec57d1d89c7477432cfb18e52bb9c8ec88158e3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
Copyright (C) 2012-2014 Kazuki Tsujimoto, All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions
|
5
|
+
are met:
|
6
|
+
1. Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
13
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
14
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
15
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
16
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
17
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
18
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
19
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
20
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
21
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
22
|
+
SUCH DAMAGE.
|
23
|
+
|
24
|
+
Copyright (C) 2014 Satoshi Egi, All rights reserved.
|
25
|
+
|
26
|
+
Redistribution and use in source and binary forms, with or without
|
27
|
+
modification, are permitted provided that the following conditions
|
28
|
+
are met:
|
29
|
+
1. Redistributions of source code must retain the above copyright
|
30
|
+
notice, this list of conditions and the following disclaimer.
|
31
|
+
2. Redistributions in binary form must reproduce the above copyright
|
32
|
+
notice, this list of conditions and the following disclaimer in the
|
33
|
+
documentation and/or other materials provided with the distribution.
|
34
|
+
|
35
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
36
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
37
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
38
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
39
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
40
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
41
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
42
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
43
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
44
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
45
|
+
SUCH DAMAGE.
|
46
|
+
|
data/Makefile
ADDED
data/README.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# The Gem for Egison Pattern Matching
|
2
|
+
|
3
|
+
This Gem provides a way to access Egison pattern-matching from Ruby.
|
4
|
+
Egison is the world's first programming language that can represent non-linear pattern-match against unfree data types.
|
5
|
+
We can directly express pattern-matching against lists, multisets, and sets using this gem.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```
|
10
|
+
$ gem install egison
|
11
|
+
```
|
12
|
+
|
13
|
+
or
|
14
|
+
|
15
|
+
```
|
16
|
+
$ git clone git://github.com/egison/egison-ruby.git
|
17
|
+
$ cd egison-ruby
|
18
|
+
$ gem build egison.gemspec
|
19
|
+
$ gem install egison-*.gem
|
20
|
+
```
|
21
|
+
|
22
|
+
or
|
23
|
+
|
24
|
+
```
|
25
|
+
$ gem install bundler (if you need)
|
26
|
+
$ echo "gem 'egison', :git => 'git://github.com/egison/egison-ruby.git'" > Gemfile
|
27
|
+
$ bundle install --path vendor/bundle
|
28
|
+
```
|
29
|
+
|
30
|
+
## Basic Usage
|
31
|
+
|
32
|
+
egison library provides `Kernel#match` and `Kernel#match_all`.
|
33
|
+
|
34
|
+
```
|
35
|
+
require 'egison'
|
36
|
+
|
37
|
+
match_all(object) do
|
38
|
+
with(pattern) do
|
39
|
+
...
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
match(object) do
|
44
|
+
with(pattern) do
|
45
|
+
...
|
46
|
+
end
|
47
|
+
with(pattern) do
|
48
|
+
...
|
49
|
+
end
|
50
|
+
...
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
If a pattern matches, a block passed to `with` is called and returns its result.
|
55
|
+
|
56
|
+
In our pattern-matching system, there are cases that pattern-matching has multiple results.
|
57
|
+
`match_all` calls the block passed to `with` for each pattern-matching result and returns all results as an array.
|
58
|
+
`match_all` takes one single match-clause.
|
59
|
+
|
60
|
+
On the other hand, `match` takes multiple match-clauses.
|
61
|
+
It pattern-matches from the first match-clause.
|
62
|
+
If a pattern matches, it calls the block passed to the matched match-clause and returns a result for the first pattern-matching result.
|
63
|
+
|
64
|
+
## Patterns
|
65
|
+
|
66
|
+
### Element patterns and subcollection patterns
|
67
|
+
|
68
|
+
An <i>element pattern</i> matches the element of the target array.
|
69
|
+
|
70
|
+
A <i>subcollection pattern</i> matches the subcollection of the target array.
|
71
|
+
A subcollection pattern has `*` ahead.
|
72
|
+
|
73
|
+
A literal that contain `_` ahead is a <i>pattern-variable</i>.
|
74
|
+
We can refer the result of pattern-matching through them.
|
75
|
+
|
76
|
+
```
|
77
|
+
match_all([1, 2, 3]) do
|
78
|
+
with(List.(*_hs, _x, *_ts)) do
|
79
|
+
[hs, x, ts]
|
80
|
+
end
|
81
|
+
end #=> [[[],1,[2,3]],[[1],2,[3]],[[1,2],3,[]]
|
82
|
+
```
|
83
|
+
|
84
|
+
### Three matchers: List, Multiset, Set
|
85
|
+
|
86
|
+
We can write pattern-matching against lists, multisets, and sets.
|
87
|
+
When we regard an array as a multiset, the order of elements is ignored.
|
88
|
+
When we regard an array as a set, the duplicates and order of elements are ignored.
|
89
|
+
|
90
|
+
```
|
91
|
+
match_all([1, 2, 3]) do
|
92
|
+
with(List.(_a, _b, *_)) do
|
93
|
+
[a, b]
|
94
|
+
end
|
95
|
+
end #=> [[1, 2]]
|
96
|
+
|
97
|
+
match_all([1, 2, 3]) do
|
98
|
+
with(Multiset.(_a, _b, *_)) do
|
99
|
+
[a, b]
|
100
|
+
end
|
101
|
+
end #=> [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
|
102
|
+
|
103
|
+
match_all([1, 2, 3]) do
|
104
|
+
with(Set.(_a, _b, *_)) do
|
105
|
+
[a, b]
|
106
|
+
end
|
107
|
+
end #=> [[1, 1],[1, 2],[1, 3],[2, 1],[2, 2],[2, 3],[3, 1],[3, 2],[3, 3]]
|
108
|
+
```
|
109
|
+
|
110
|
+
### Non-linear patterns
|
111
|
+
|
112
|
+
Non-linear pattern is the most important feature of our pattern-matching system.
|
113
|
+
Our pattern-matching system allows users multiple occurrences of same variables in a pattern.
|
114
|
+
A Pattern whose form is `__("...")` is a value pattern.
|
115
|
+
In the place of `...`, we can write any ruby expression we like.
|
116
|
+
It matches the target when the target is equal with the value that `...` evaluated to.
|
117
|
+
|
118
|
+
```
|
119
|
+
match_all([5, 3, 4, 1, 2]) do
|
120
|
+
with(Multiset.(_a, __("a + 1"), __("a + 2"), *_)) do
|
121
|
+
a
|
122
|
+
end
|
123
|
+
end #=> [1,2,3]
|
124
|
+
```
|
125
|
+
|
126
|
+
When, the expression in the place of `...` is a single variable, we can omit `("` and `")` as follow.
|
127
|
+
|
128
|
+
```
|
129
|
+
match_all([1, 2, 3, 2, 5]) do
|
130
|
+
with(Multiset.(_a, __a, *_)) do
|
131
|
+
a
|
132
|
+
end
|
133
|
+
end #=> [2,2]
|
134
|
+
```
|
135
|
+
|
136
|
+
## Demonstration - Poker Hands
|
137
|
+
|
138
|
+
We can write patterns for all poker-hands in one single pattern.
|
139
|
+
It is as follow.
|
140
|
+
Isn't it exciting?
|
141
|
+
|
142
|
+
```
|
143
|
+
require 'egison'
|
144
|
+
|
145
|
+
def poker_hands cs
|
146
|
+
match(cs) do
|
147
|
+
with(Multiset.(_[_s, _n], _[__s, __("n+1")], _[__s, __("n+2")], _[__s, __("n+3")], _[__s, __("n+4")])) do
|
148
|
+
"Straight flush"
|
149
|
+
end
|
150
|
+
with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _[_, __n], _)) do
|
151
|
+
"Four of kind"
|
152
|
+
end
|
153
|
+
with(Multiset.(_[_, _m], _[_, __m], _[_, __m], _[_, _n], _[_, __n])) do
|
154
|
+
"Full house"
|
155
|
+
end
|
156
|
+
with(Multiset.(_[_s, _], _[__s, _], _[__s, _], _[__s, _], _[__s, _])) do
|
157
|
+
"Flush"
|
158
|
+
end
|
159
|
+
with(Multiset.(_[_, _n], _[_, __("n+1")], _[_, __("n+2")], _[_, __("n+3")], _[_, __("n+4")])) do
|
160
|
+
"Straight"
|
161
|
+
end
|
162
|
+
with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _, _)) do
|
163
|
+
"Three of kind"
|
164
|
+
end
|
165
|
+
with(Multiset.(_[_, _m], _[_, __m], _[_, _n], _[_, __n], _)) do
|
166
|
+
"Two pairs"
|
167
|
+
end
|
168
|
+
with(Multiset.(_[_, _n], _[_, __n], _, _, _)) do
|
169
|
+
"One pair"
|
170
|
+
end
|
171
|
+
with(Multiset.(_, _, _, _, _)) do
|
172
|
+
"Nothing"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
p(poker_hands([["diamond", 1], ["diamond", 3], ["diamond", 5], ["diamond", 4], ["diamond", 2]])) #=> "Straight flush"
|
178
|
+
p(poker_hands([["diamond", 1], ["club", 2], ["club", 1], ["heart", 1], ["diamond", 2]])) #=> "Full house"
|
179
|
+
p(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Straight"
|
180
|
+
p(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Nothing"
|
181
|
+
```
|
182
|
+
|
183
|
+
## About Egison
|
184
|
+
|
185
|
+
If you get to love the above pattern-matching, please try [the Egison programming language](http://www.egison.org), too.
|
186
|
+
Egison is the pattern-matching oriented pure functional programming language.
|
187
|
+
Actually, the original pattern-matching system of Egison is more powerful.
|
188
|
+
For example, we can do following things in the original Egison.
|
189
|
+
|
190
|
+
- We can pattern-match against infinite lists
|
191
|
+
- We can define new pattern-constructors.
|
192
|
+
- We can modularize useful patterns.
|
193
|
+
|
194
|
+
There is a new programming world!
|
195
|
+
|
196
|
+
## Contact
|
197
|
+
|
198
|
+
If you get interested in this Gem, please mail to [Satoshi Egi](http://www.egison.org/~egi/) or tweet to [@Egison_Lang](https://twitter.com/Egison_Lang).
|
199
|
+
|
200
|
+
## LICENSE
|
201
|
+
|
202
|
+
The license of this library code is BSD.
|
203
|
+
I learned how to extend Ruby and how to write a gem from the code of [the pattern-match gem](https://github.com/k-tsj/pattern-match) by Kazuki Tsujimoto.
|
204
|
+
I designed syntax of pattern-matching to go with that gem.
|
205
|
+
This library contains the copy from that gem.
|
206
|
+
The full license text is [here](https://github.com/egisatoshi/egison-ruby/blob/master/LICENSE).
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', __FILE__)
|
2
|
+
require 'bundler/setup'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
require "rake/testtask"
|
6
|
+
task :default => :test
|
7
|
+
Rake::TestTask.new do |t|
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/test_*.rb"]
|
10
|
+
end
|
data/egison.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'egison/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'egison'
|
6
|
+
s.version = Egison::VERSION
|
7
|
+
s.authors = ['Satoshi Egi']
|
8
|
+
s.email = ['egi@egison.org']
|
9
|
+
s.homepage = 'https://github.com/egisatoshi/egison-ruby'
|
10
|
+
s.summary = %q{The Egison pattern matching library}
|
11
|
+
s.description = %w{
|
12
|
+
The library to access Egison pattern-matching from Ruby.
|
13
|
+
}.join(' ')
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f) }
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
s.add_development_dependency 'rake'
|
20
|
+
s.rdoc_options = ['--main', 'README.rdoc']
|
21
|
+
end
|
data/lib/egison.rb
ADDED
data/lib/egison/core.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'egison/version'
|
2
|
+
require 'continuation'
|
3
|
+
|
4
|
+
module PatternMatch
|
5
|
+
module Matchable
|
6
|
+
def call(*subpatterns)
|
7
|
+
pattern_matcher(*subpatterns)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ::Object
|
12
|
+
private
|
13
|
+
|
14
|
+
def pattern_matcher(*subpatterns)
|
15
|
+
PatternWithMatcher.new(self, *subpatterns)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class MatchingStateStack
|
20
|
+
attr_accessor :states
|
21
|
+
attr_accessor :results
|
22
|
+
|
23
|
+
def initialize(pat, tgt)
|
24
|
+
@states = [MatchingState.new(pat, tgt)]
|
25
|
+
@results = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def match
|
29
|
+
while !@states.empty? do
|
30
|
+
process
|
31
|
+
end
|
32
|
+
@results
|
33
|
+
end
|
34
|
+
|
35
|
+
def process
|
36
|
+
state = @states.shift
|
37
|
+
rets = state.process
|
38
|
+
new_states = []
|
39
|
+
rets.each { |ret|
|
40
|
+
if ret.atoms.empty? then
|
41
|
+
@results = @results + [ret.bindings]
|
42
|
+
else
|
43
|
+
new_states = new_states + [ret]
|
44
|
+
end
|
45
|
+
}
|
46
|
+
@states = new_states + @states
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class MatchingState
|
51
|
+
attr_accessor :atoms, :bindings
|
52
|
+
|
53
|
+
def initialize(pat, tgt)
|
54
|
+
@atoms = [[pat, tgt]]
|
55
|
+
@bindings = []
|
56
|
+
end
|
57
|
+
|
58
|
+
def process
|
59
|
+
atom = @atoms.shift
|
60
|
+
rets = atom.first.match(atom.last, @bindings)
|
61
|
+
rets.map { |new_atoms, new_bindings|
|
62
|
+
new_state = self.clone
|
63
|
+
new_state.atoms = new_atoms + new_state.atoms
|
64
|
+
new_state.bindings = new_state.bindings + new_bindings
|
65
|
+
new_state
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Pattern
|
71
|
+
attr_accessor :quantified
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
end
|
75
|
+
|
76
|
+
def match(tgt, bindings)
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_a
|
80
|
+
[PatternCollection.new(self)]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class PatternElement < Pattern
|
85
|
+
def initialize
|
86
|
+
super()
|
87
|
+
@quantified = false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class PatternWithMatcher < PatternElement
|
92
|
+
attr_reader :matcher, :subpatterns
|
93
|
+
|
94
|
+
def initialize(matcher, *subpatterns)
|
95
|
+
super()
|
96
|
+
@matcher = matcher
|
97
|
+
@subpatterns = subpatterns
|
98
|
+
end
|
99
|
+
|
100
|
+
def match(tgt, bindings)
|
101
|
+
if subpatterns.empty? then
|
102
|
+
if tgt.empty? then
|
103
|
+
return [[[], []]]
|
104
|
+
else
|
105
|
+
return []
|
106
|
+
end
|
107
|
+
else
|
108
|
+
subpatterns = @subpatterns.clone
|
109
|
+
px = subpatterns.shift
|
110
|
+
if px.quantified then
|
111
|
+
if subpatterns.empty? then
|
112
|
+
[[[[px.pattern, tgt]], []]]
|
113
|
+
else
|
114
|
+
unjoineds = @matcher.unjoin(tgt)
|
115
|
+
unjoineds.map { |xs, ys| [[[px.pattern, xs], [PatternWithMatcher.new(@matcher, *subpatterns), ys]], []] }
|
116
|
+
end
|
117
|
+
else
|
118
|
+
if tgt.empty? then
|
119
|
+
[]
|
120
|
+
else
|
121
|
+
unconseds = @matcher.uncons(tgt)
|
122
|
+
unconseds.map { |x, xs| [[[px, x], [PatternWithMatcher.new(@matcher, *subpatterns), xs]], []] }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Wildcard < PatternElement
|
130
|
+
def initialize()
|
131
|
+
super()
|
132
|
+
end
|
133
|
+
|
134
|
+
def match(tgt, bindings)
|
135
|
+
[[[], []]]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class PatternVariable < PatternElement
|
140
|
+
attr_reader :name
|
141
|
+
|
142
|
+
def initialize(name)
|
143
|
+
super()
|
144
|
+
@name = name
|
145
|
+
end
|
146
|
+
|
147
|
+
def match(tgt, bindings)
|
148
|
+
[[[], [[name, tgt]]]]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class ValuePattern < PatternElement
|
153
|
+
def initialize(ctx, expr)
|
154
|
+
super()
|
155
|
+
@ctx = ctx
|
156
|
+
@expr = expr
|
157
|
+
end
|
158
|
+
|
159
|
+
def match(tgt, bindings)
|
160
|
+
val = with_bindings(@ctx, bindings, {:expr => @expr}) { eval expr }
|
161
|
+
if val.__send__(:===, tgt) then
|
162
|
+
[[[], []]]
|
163
|
+
else
|
164
|
+
[]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class BindingModule < ::Module
|
169
|
+
end
|
170
|
+
|
171
|
+
def with_bindings(obj, bindings, ext_bindings, &block)
|
172
|
+
binding_module(obj).module_eval do
|
173
|
+
begin
|
174
|
+
bindings.each do |name, val|
|
175
|
+
define_method(name) { val }
|
176
|
+
private name
|
177
|
+
end
|
178
|
+
ext_bindings.each do |name, val|
|
179
|
+
define_method(name) { val }
|
180
|
+
private name
|
181
|
+
end
|
182
|
+
obj.instance_eval(&block)
|
183
|
+
ensure
|
184
|
+
bindings.each do |name, _|
|
185
|
+
remove_method(name)
|
186
|
+
end
|
187
|
+
ext_bindings.each do |name, _|
|
188
|
+
remove_method(name)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def binding_module(obj)
|
195
|
+
m = obj.singleton_class.ancestors.find {|i| i.kind_of?(BindingModule) }
|
196
|
+
unless m
|
197
|
+
m = BindingModule.new
|
198
|
+
obj.singleton_class.class_eval do
|
199
|
+
if respond_to?(:prepend, true)
|
200
|
+
prepend m
|
201
|
+
else
|
202
|
+
include m
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
m
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class PatternCollection < Pattern
|
211
|
+
attr_accessor :pattern
|
212
|
+
|
213
|
+
def initialize(pat)
|
214
|
+
super()
|
215
|
+
@quantified = true
|
216
|
+
@pattern = pat
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class Env < BasicObject
|
221
|
+
def initialize(ctx, tgt)
|
222
|
+
@ctx = ctx
|
223
|
+
@tgt = tgt
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
def with(pat, &block)
|
229
|
+
ctx = @ctx
|
230
|
+
tgt = @tgt
|
231
|
+
mstack = MatchingStateStack.new(pat,tgt)
|
232
|
+
mstack.match
|
233
|
+
mstack.results.map { |bindings|
|
234
|
+
ret = with_bindings(ctx, bindings, &block)
|
235
|
+
}
|
236
|
+
rescue PatternNotMatch
|
237
|
+
end
|
238
|
+
|
239
|
+
def method_missing(name, *args)
|
240
|
+
::Kernel.raise ::ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
|
241
|
+
if /^__/.match(name.to_s)
|
242
|
+
ValuePattern.new(@ctx, name.to_s.gsub(/^__/, "").gsub("_plus_", "+").gsub("_minus_", "-"))
|
243
|
+
elsif /^_/.match(name.to_s)
|
244
|
+
PatternVariable.new(name.to_s.gsub(/^_/, "").to_sym)
|
245
|
+
else
|
246
|
+
undefined
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def _(*vals)
|
251
|
+
case vals.length
|
252
|
+
when 0
|
253
|
+
uscore = Wildcard.new()
|
254
|
+
class << uscore
|
255
|
+
def [](*args)
|
256
|
+
List.call(*args)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
uscore
|
260
|
+
when 1
|
261
|
+
ValuePattern.new(@ctx, vals[0])
|
262
|
+
else
|
263
|
+
undefined
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def __(val)
|
268
|
+
ValuePattern.new(@ctx, val)
|
269
|
+
end
|
270
|
+
|
271
|
+
class BindingModule < ::Module
|
272
|
+
end
|
273
|
+
|
274
|
+
def with_bindings(obj, bindings, &block)
|
275
|
+
binding_module(obj).module_eval do
|
276
|
+
begin
|
277
|
+
bindings.each do |name, val|
|
278
|
+
define_method(name) { val }
|
279
|
+
private name
|
280
|
+
end
|
281
|
+
obj.instance_eval(&block)
|
282
|
+
ensure
|
283
|
+
bindings.each do |name, _|
|
284
|
+
remove_method(name)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def binding_module(obj)
|
291
|
+
m = obj.singleton_class.ancestors.find {|i| i.kind_of?(BindingModule) }
|
292
|
+
unless m
|
293
|
+
m = BindingModule.new
|
294
|
+
obj.singleton_class.class_eval do
|
295
|
+
if respond_to?(:prepend, true)
|
296
|
+
prepend m
|
297
|
+
else
|
298
|
+
include m
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
m
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
class Env2 < Env
|
307
|
+
def with(pat, &block)
|
308
|
+
ctx = @ctx
|
309
|
+
tgt = @tgt
|
310
|
+
mstack = MatchingStateStack.new(pat,tgt)
|
311
|
+
mstack.match
|
312
|
+
if mstack.results.empty? then
|
313
|
+
nil
|
314
|
+
else
|
315
|
+
ret = with_bindings(ctx, mstack.results.first, &block)
|
316
|
+
::Kernel.throw(:exit_match, ret)
|
317
|
+
end
|
318
|
+
rescue PatternNotMatch
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
class PatternNotMatch < Exception; end
|
323
|
+
class PatternMatchError < StandardError; end
|
324
|
+
class NoMatchingPatternError < PatternMatchError; end
|
325
|
+
class MalformedPatternError < PatternMatchError; end
|
326
|
+
|
327
|
+
# Make Pattern and its subclasses/Env private.
|
328
|
+
if respond_to?(:private_constant)
|
329
|
+
constants.each do |c|
|
330
|
+
klass = const_get(c)
|
331
|
+
next unless klass.kind_of?(Class)
|
332
|
+
if klass <= Pattern
|
333
|
+
private_constant c
|
334
|
+
end
|
335
|
+
end
|
336
|
+
private_constant :Env, :Env2
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
module Kernel
|
341
|
+
private
|
342
|
+
|
343
|
+
def match_all(tgt, &block)
|
344
|
+
env = PatternMatch.const_get(:Env).new(self, tgt)
|
345
|
+
env.instance_eval(&block)
|
346
|
+
end
|
347
|
+
|
348
|
+
def match(tgt, &block)
|
349
|
+
env = PatternMatch.const_get(:Env2).new(self, tgt)
|
350
|
+
catch(:exit_match) do
|
351
|
+
env.instance_eval(&block)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'egison/core'
|
2
|
+
|
3
|
+
class Class
|
4
|
+
include PatternMatch::Matchable
|
5
|
+
|
6
|
+
def uncons(val)
|
7
|
+
raise NotImplementedError, "need to define `#{__method__}'"
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def accept_array_only(val)
|
13
|
+
raise PatternMatch::PatternNotMatch unless val.kind_of?(Array)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class List
|
18
|
+
end
|
19
|
+
|
20
|
+
class << List
|
21
|
+
def uncons(val)
|
22
|
+
accept_array_only(val)
|
23
|
+
val2 = val.clone
|
24
|
+
x = val2.shift
|
25
|
+
[[x, val2]]
|
26
|
+
end
|
27
|
+
|
28
|
+
def unjoin(val)
|
29
|
+
accept_array_only(val)
|
30
|
+
val2 = val.clone
|
31
|
+
xs = []
|
32
|
+
ys = val2.clone
|
33
|
+
rets = [[xs, ys]]
|
34
|
+
while !val2.empty? do
|
35
|
+
x = val2.shift
|
36
|
+
ys = val2.clone
|
37
|
+
xs = xs + [x]
|
38
|
+
rets = rets + [[xs, ys]]
|
39
|
+
end
|
40
|
+
rets
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Multiset
|
45
|
+
end
|
46
|
+
|
47
|
+
class << Multiset
|
48
|
+
def uncons(val)
|
49
|
+
accept_array_only(val)
|
50
|
+
rets = val.map {|x|
|
51
|
+
val2 = val.clone
|
52
|
+
val2.delete_at(val2.find_index(x))
|
53
|
+
[x, val2]
|
54
|
+
}
|
55
|
+
rets
|
56
|
+
end
|
57
|
+
|
58
|
+
def unjoin(val)
|
59
|
+
accept_array_only(val)
|
60
|
+
val2 = val.clone
|
61
|
+
xs = []
|
62
|
+
ys = val2.clone
|
63
|
+
rets = [[xs, ys]]
|
64
|
+
if !val2.empty? then
|
65
|
+
x = val2.shift
|
66
|
+
ys = val2.clone
|
67
|
+
rets2 = unjoin(ys)
|
68
|
+
rets = (rets2.map {|xs2, ys2| [xs2, [x]+ys2]}) + (rets2.map {|xs2, ys2| [[x]+xs2, ys2]})
|
69
|
+
rets
|
70
|
+
else
|
71
|
+
rets
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Set
|
77
|
+
end
|
78
|
+
|
79
|
+
class << Set
|
80
|
+
def uncons(val)
|
81
|
+
accept_array_only(val)
|
82
|
+
rets = val.map {|x|
|
83
|
+
val2 = val.clone
|
84
|
+
[x, val2]
|
85
|
+
}
|
86
|
+
rets
|
87
|
+
end
|
88
|
+
def unjoin(val)
|
89
|
+
accept_array_only(val)
|
90
|
+
val2 = val.clone
|
91
|
+
xs = []
|
92
|
+
ys = val2.clone
|
93
|
+
rets = [[xs, ys]]
|
94
|
+
if !val2.empty? then
|
95
|
+
x = val2.shift
|
96
|
+
ys2 = val2.clone
|
97
|
+
rets2 = unjoin(ys2)
|
98
|
+
rets = (rets2.map {|xs2, _| [xs2, ys]}) + (rets2.map {|xs2, ys2| [[x]+xs2, ys]})
|
99
|
+
rets
|
100
|
+
else
|
101
|
+
rets
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'egison'
|
2
|
+
|
3
|
+
def poker_hands cs
|
4
|
+
match(cs) do
|
5
|
+
with(Multiset.(_[_s, _n], _[__s, __("n+1")], _[__s, __("n+2")], _[__s, __("n+3")], _[__s, __("n+4")])) do
|
6
|
+
"Straight flush"
|
7
|
+
end
|
8
|
+
with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _[_, __n], _)) do
|
9
|
+
"Four of kind"
|
10
|
+
end
|
11
|
+
with(Multiset.(_[_, _m], _[_, __m], _[_, __m], _[_, _n], _[_, __n])) do
|
12
|
+
"Full house"
|
13
|
+
end
|
14
|
+
with(Multiset.(_[_s, _], _[__s, _], _[__s, _], _[__s, _], _[__s, _])) do
|
15
|
+
"Flush"
|
16
|
+
end
|
17
|
+
with(Multiset.(_[_, _n], _[_, __("n+1")], _[_, __("n+2")], _[_, __("n+3")], _[_, __("n+4")])) do
|
18
|
+
"Straight"
|
19
|
+
end
|
20
|
+
with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _, _)) do
|
21
|
+
"Three of kind"
|
22
|
+
end
|
23
|
+
with(Multiset.(_[_, _m], _[_, __m], _[_, _n], _[_, __n], _)) do
|
24
|
+
"Two pairs"
|
25
|
+
end
|
26
|
+
with(Multiset.(_[_, _n], _[_, __n], _, _, _)) do
|
27
|
+
"One pair"
|
28
|
+
end
|
29
|
+
with(Multiset.(_, _, _, _, _)) do
|
30
|
+
"Nothing"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
p(poker_hands([["diamond", 1], ["diamond", 3], ["diamond", 5], ["diamond", 4], ["diamond", 2]])) #=> "Straight flush"
|
36
|
+
p(poker_hands([["diamond", 1], ["club", 2], ["club", 1], ["heart", 1], ["diamond", 2]])) #=> "Full house"
|
37
|
+
p(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Straight"
|
38
|
+
p(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Nothing"
|
data/sample/set.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: egison
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Satoshi Egi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: The library to access Egison pattern-matching from Ruby.
|
28
|
+
email:
|
29
|
+
- egi@egison.org
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE
|
37
|
+
- Makefile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- egison.gemspec
|
41
|
+
- lib/egison.rb
|
42
|
+
- lib/egison/core.rb
|
43
|
+
- lib/egison/matcher.rb
|
44
|
+
- lib/egison/version.rb
|
45
|
+
- sample/combination.rb
|
46
|
+
- sample/poker_hands.rb
|
47
|
+
- sample/set.rb
|
48
|
+
homepage: https://github.com/egisatoshi/egison-ruby
|
49
|
+
licenses: []
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- "--main"
|
54
|
+
- README.rdoc
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.2.2
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: The Egison pattern matching library
|
73
|
+
test_files: []
|