├── .gitignore
├── .rubocop_todo.yml
├── Gemfile
├── LICENSE
├── Makefile
├── README.md
├── Rakefile
├── THANKS.md
├── egison.gemspec
├── lib
├── egison.rb
└── egison
│ ├── core.rb
│ ├── lazyarray.rb
│ ├── matcher-core.rb
│ ├── matcher.rb
│ └── version.rb
├── sample
├── combination.rb
├── fib.rb
├── greet.rb
├── join.rb
├── poker_hands.rb
├── primes.rb
├── set.rb
└── stream.rb
└── spec
├── lib
├── egison
│ ├── core_spec.rb
│ └── matcher_spec.rb
└── egison_spec.rb
├── sample
├── combination_spec.rb
├── greet_spec.rb
├── join_spec.rb
├── join_stream_spec.rb
├── poker_hands_spec.rb
├── prime_spec.rb
├── set_spec.rb
└── stream_spec.rb
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle
2 | Gemfile.lock
3 | coverage
4 | pkg/*
5 | vendor/*
6 | *.gem
7 | pattern-match
8 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by `rubocop --auto-gen-config`
2 | # on 2014-09-22 15:31:38 -0700 using RuboCop version 0.26.1.
3 | # The point is for the user to remove these configuration records
4 | # one by one as the offenses are removed from the code base.
5 | # Note that changes in the inspected code, or installation of new
6 | # versions of RuboCop, may require this file to be generated again.
7 |
8 | # Offense count: 1
9 | Lint/Eval:
10 | Enabled: false
11 |
12 | # Offense count: 3
13 | Lint/HandleExceptions:
14 | Enabled: false
15 |
16 | # Offense count: 1
17 | Lint/LiteralInCondition:
18 | Enabled: false
19 |
20 | # Offense count: 1
21 | # Cop supports --auto-correct.
22 | Lint/UnusedBlockArgument:
23 | Enabled: false
24 |
25 | # Offense count: 4
26 | # Cop supports --auto-correct.
27 | Lint/UnusedMethodArgument:
28 | Enabled: false
29 |
30 | # Offense count: 4
31 | Lint/UselessAssignment:
32 | Enabled: false
33 |
34 | # Offense count: 5
35 | Metrics/BlockNesting:
36 | Max: 4
37 |
38 | # Offense count: 2
39 | Metrics/CyclomaticComplexity:
40 | Max: 9
41 |
42 | # Offense count: 74
43 | # Configuration parameters: AllowURI, URISchemes.
44 | Metrics/LineLength:
45 | Max: 919
46 |
47 | # Offense count: 21
48 | # Configuration parameters: CountComments.
49 | Metrics/MethodLength:
50 | Max: 40
51 |
52 | # Offense count: 3
53 | Metrics/PerceivedComplexity:
54 | Max: 13
55 |
56 | # Offense count: 4
57 | # Cop supports --auto-correct.
58 | Style/Alias:
59 | Enabled: false
60 |
61 | # Offense count: 22
62 | # Cop supports --auto-correct.
63 | Style/Blocks:
64 | Enabled: false
65 |
66 | # Offense count: 2
67 | # Configuration parameters: EnforcedStyle, SupportedStyles.
68 | Style/ClassAndModuleChildren:
69 | Enabled: false
70 |
71 | # Offense count: 28
72 | Style/Documentation:
73 | Enabled: false
74 |
75 | # Offense count: 1
76 | # Cop supports --auto-correct.
77 | Style/EmptyLinesAroundAccessModifier:
78 | Enabled: false
79 |
80 | # Offense count: 1
81 | # Configuration parameters: Exclude.
82 | Style/FileName:
83 | Enabled: false
84 |
85 | # Offense count: 3
86 | # Configuration parameters: MinBodyLength.
87 | Style/GuardClause:
88 | Enabled: false
89 |
90 | # Offense count: 3
91 | # Configuration parameters: MaxLineLength.
92 | Style/IfUnlessModifier:
93 | Enabled: false
94 |
95 | # Offense count: 1
96 | # Cop supports --auto-correct.
97 | Style/InfiniteLoop:
98 | Enabled: false
99 |
100 | # Offense count: 73
101 | # Cop supports --auto-correct.
102 | # Configuration parameters: EnforcedStyle, SupportedStyles.
103 | Style/LambdaCall:
104 | Enabled: false
105 |
106 | # Offense count: 5
107 | # Cop supports --auto-correct.
108 | Style/LeadingCommentSpace:
109 | Enabled: false
110 |
111 | # Offense count: 2
112 | # Configuration parameters: EnforcedStyle, SupportedStyles.
113 | Style/MethodName:
114 | Enabled: false
115 |
116 | # Offense count: 3
117 | Style/ModuleFunction:
118 | Enabled: false
119 |
120 | # Offense count: 2
121 | # Cop supports --auto-correct.
122 | # Configuration parameters: PreferredDelimiters.
123 | Style/PercentLiteralDelimiters:
124 | Enabled: false
125 |
126 | # Offense count: 1
127 | # Configuration parameters: EnforcedStyle, SupportedStyles.
128 | Style/RaiseArgs:
129 | Enabled: false
130 |
131 | # Offense count: 1
132 | # Cop supports --auto-correct.
133 | # Configuration parameters: SupportedStyles.
134 | Style/SpaceAroundEqualsInParameterDefault:
135 | EnforcedStyle: no_space
136 |
137 | # Offense count: 10
138 | # Cop supports --auto-correct.
139 | # Configuration parameters: EnforcedStyle, SupportedStyles.
140 | Style/SpaceBeforeBlockBraces:
141 | Enabled: false
142 |
143 | # Offense count: 129
144 | # Cop supports --auto-correct.
145 | # Configuration parameters: EnforcedStyle, SupportedStyles.
146 | Style/StringLiterals:
147 | Enabled: false
148 |
149 | # Offense count: 10
150 | # Cop supports --auto-correct.
151 | # Configuration parameters: EnforcedStyle, SupportedStyles.
152 | Style/TrailingBlankLines:
153 | Enabled: false
154 |
155 | # Offense count: 2
156 | # Cop supports --auto-correct.
157 | Style/TrailingWhitespace:
158 | Enabled: false
159 |
160 | # Offense count: 21
161 | # Cop supports --auto-correct.
162 | Style/UnneededPercentQ:
163 | Enabled: false
164 |
165 | # Offense count: 3
166 | # Cop supports --auto-correct.
167 | Style/WhileUntilDo:
168 | Enabled: false
169 |
170 | # Offense count: 2
171 | # Configuration parameters: MaxLineLength.
172 | Style/WhileUntilModifier:
173 | Enabled: false
174 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gem 'rubocop', require: false
4 |
5 | gemspec
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2014 Satoshi Egi, 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 | ===
25 |
26 | This software includes the copy from the pattern-match gem.
27 |
28 | https://github.com/k-tsj/pattern-match
29 |
30 | Copyright (C) 2012-2014 Kazuki Tsujimoto, All rights reserved.
31 |
32 | Redistribution and use in source and binary forms, with or without
33 | modification, are permitted provided that the following conditions
34 | are met:
35 | 1. Redistributions of source code must retain the above copyright
36 | notice, this list of conditions and the following disclaimer.
37 | 2. Redistributions in binary form must reproduce the above copyright
38 | notice, this list of conditions and the following disclaimer in the
39 | documentation and/or other materials provided with the distribution.
40 |
41 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
42 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
44 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
45 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
46 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
47 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
49 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
50 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51 | SUCH DAMAGE.
52 |
53 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | make compile
3 | make test
4 | compile:
5 | gem build egison.gemspec
6 | gem install egison-*.gem
7 |
8 | test:
9 | bundle exec rspec spec
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Gem for Egison Pattern Matching
2 |
3 | This Gem provides a way to access non-linear pattern-matching against unfree data types from Ruby.
4 | We can directly express pattern-matching against lists, multisets, and sets using this gem.
5 |
6 | ## Installation
7 |
8 | ```shell
9 | $ gem install egison
10 | ```
11 |
12 | or
13 |
14 | ```shell
15 | $ git clone https://github.com/egison/egison-ruby.git
16 | $ cd egison-ruby
17 | $ make
18 | ```
19 |
20 | or
21 |
22 | ```shell
23 | $ gem install bundler (if you need)
24 | $ echo "gem 'egison', :git => 'https://github.com/egison/egison-ruby.git'" > Gemfile
25 | $ bundle install
26 | ```
27 |
28 | ## Basic Usage
29 |
30 | The library provides `Egison#match_all` and `Egison#match`.
31 |
32 | ```Ruby
33 | require 'egison'
34 |
35 | include 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 element pattern matches the element of the target array.
69 |
70 | A subcollection pattern matches the subcollection of the target array.
71 | A subcollection pattern has `*` ahead.
72 |
73 | A literal that contain `_` ahead is a pattern-variable.
74 | We can refer the result of pattern-matching through them.
75 |
76 | ```Ruby
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 | `_` is a wildcard.
91 | It matches with any object.
92 | Note that `__` and `___` are also interpreted as a wildcard.
93 | This is because `_` and `__` are system variables and sometimes have its own meaning.
94 |
95 | ```Ruby
96 | match_all([1, 2, 3]) do
97 | with(List.(_a, _b, *_)) do
98 | [a, b]
99 | end
100 | end #=> [[1, 2]]
101 |
102 | match_all([1, 2, 3]) do
103 | with(Multiset.(_a, _b, *_)) do
104 | [a, b]
105 | end
106 | end #=> [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
107 |
108 | match_all([1, 2, 3]) do
109 | with(Set.(_a, _b, *_)) do
110 | [a, b]
111 | end
112 | end #=> [[1, 1],[1, 2],[1, 3],[2, 1],[2, 2],[2, 3],[3, 1],[3, 2],[3, 3]]
113 | ```
114 |
115 | Note that `_[]` is provided as syntactic sugar for `List.()`.
116 |
117 | ```Ruby
118 | match_all([1, 2, 3]) do
119 | with(_[_a, _b, *_]) do
120 | [a, b]
121 | end
122 | end #=> [[1, 2]]
123 | ```
124 |
125 | ### Non-Linear Patterns
126 |
127 | Non-linear pattern is the most important feature of our pattern-matching system.
128 | Our pattern-matching system allows users multiple occurrences of same variables in a pattern.
129 | A Pattern whose form is `__("...")` is a value pattern.
130 | In the place of `...`, we can write any ruby expression we like.
131 | It matches the target when the target is equal with the value that `...` evaluated to.
132 |
133 | ```Ruby
134 | match_all([5, 3, 4, 1, 2]) do
135 | with(Multiset.(_a, __("a + 1"), __("a + 2"), *_)) do
136 | a
137 | end
138 | end #=> [1,2,3]
139 | ```
140 |
141 | When, the expression in the place of `...` is a single variable, we can omit `("` and `")` as follow.
142 |
143 | ```Ruby
144 | match_all([1, 2, 3, 2, 5]) do
145 | with(Multiset.(_a, __a, *_)) do
146 | a
147 | end
148 | end #=> [2,2]
149 | ```
150 |
151 | ### Pattern Matching against Stream (Infinite List)
152 |
153 | We can do pattern-matching against streams with the `match_stream` expression.
154 |
155 | ```Ruby
156 | def nats
157 | (1..Float::INFINITY)
158 | end
159 |
160 | match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)
161 | #=>[[1, 2], [1, 3], [2, 1], [1, 4], [2, 3], [3, 1], [1, 5], [2, 4], [3, 2], [4, 1]]
162 |
163 | match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)
164 | #=>[[1, 1], [1, 2], [2, 1], [1, 3], [2, 2], [3, 1], [1, 4], [2, 3], [3, 2], [4, 1]]
165 | ```
166 |
167 | ## Demonstrations
168 |
169 | ### Combinations
170 |
171 | We can enumerates all combinations of the elements of a collection with pattern-matching.
172 |
173 | ```Ruby
174 | require 'egison'
175 |
176 | include Egison
177 |
178 | p(match_all([1,2,3,4,5]) do with(List.(*_, _x, *_, _y, *_)) { [x, y] } end)
179 | #=> [[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
180 |
181 | p(match_all([1,2,3,4,5]) do with(List.(*_, _x, *_, _y, *_, _z, *_)) { [x, y, z] } end)
182 | #=> [[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]
183 |
184 | ```
185 |
186 | ### Poker Hands
187 |
188 | We can write patterns for all poker-hands in one single pattern.
189 | It is as follow.
190 | Isn't it exciting?
191 |
192 | ```Ruby
193 | require 'egison'
194 |
195 | include Egison
196 |
197 | def poker_hands cs
198 | match(cs) do
199 | with(Multiset.(_[_s, _n], _[__s, __("n+1")], _[__s, __("n+2")], _[__s, __("n+3")], _[__s, __("n+4")])) do
200 | "Straight flush"
201 | end
202 | with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _[_, __n], _)) do
203 | "Four of kind"
204 | end
205 | with(Multiset.(_[_, _m], _[_, __m], _[_, __m], _[_, _n], _[_, __n])) do
206 | "Full house"
207 | end
208 | with(Multiset.(_[_s, _], _[__s, _], _[__s, _], _[__s, _], _[__s, _])) do
209 | "Flush"
210 | end
211 | with(Multiset.(_[_, _n], _[_, __("n+1")], _[_, __("n+2")], _[_, __("n+3")], _[_, __("n+4")])) do
212 | "Straight"
213 | end
214 | with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _, _)) do
215 | "Three of kind"
216 | end
217 | with(Multiset.(_[_, _m], _[_, __m], _[_, _n], _[_, __n], _)) do
218 | "Two pairs"
219 | end
220 | with(Multiset.(_[_, _n], _[_, __n], _, _, _)) do
221 | "One pair"
222 | end
223 | with(Multiset.(_, _, _, _, _)) do
224 | "Nothing"
225 | end
226 | end
227 | end
228 |
229 | p(poker_hands([["diamond", 1], ["diamond", 3], ["diamond", 5], ["diamond", 4], ["diamond", 2]])) #=> "Straight flush"
230 | p(poker_hands([["diamond", 1], ["club", 2], ["club", 1], ["heart", 1], ["diamond", 2]])) #=> "Full house"
231 | p(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Straight"
232 | p(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Nothing"
233 | ```
234 |
235 | ### Twin Primes and Prime Triplets
236 |
237 | The following code enumerates all twin primes with pattern-matching!
238 | I believe it is also a really exciting demonstration.
239 |
240 | ```Ruby
241 | require 'egison'
242 | require 'prime'
243 |
244 | include Egison
245 |
246 | twin_primes = match_stream(Prime) {
247 | with(List.(*_, _p, __("p + 2"), *_)) {
248 | [p, p + 2]
249 | }
250 | }
251 |
252 | p twin_primes.take(10)
253 | #=>[[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]
254 | ```
255 |
256 | We can also enumerate prime triplets using **and-patterns** and **or-patterns** effectively.
257 |
258 | ```Ruby
259 | prime_triplets = match_stream(Prime) {
260 | with(List.(*_, _p, And(Or(__("p + 2"), __("p + 4")), _m), __("p + 6"), *_)) {
261 | [p, m, p + 6]
262 | }
263 | }
264 |
265 | p prime_triplets.take(10)
266 | #=>[[5, 7, 11], [7, 11, 13], [11, 13, 17], [13, 17, 19], [17, 19, 23], [37, 41, 43], [41, 43, 47], [67, 71, 73], [97, 101, 103], [101, 103, 107]]
267 | ```
268 |
269 | ### Algebraic Data Types
270 |
271 | Of course, we can also patten match against algebraic data types as ordinary functional programming languages.
272 | Here is a simple example.
273 |
274 | ```Ruby
275 | class User < Struct.new(:name, :gender, :married, :doctor, :professor)
276 | def greet
277 | match(self) do
278 | with(User.(_name, _, _, _, true)) { "Hello, Prof. #{name}!" }
279 | with(User.(_name, _, _, true, _)) { "Hello, Dr. #{name}!" }
280 | with(User.(_name, :female, true, _, _)) { "Hello, Mrs. #{name}!" }
281 | with(User.(_name, :female, _, _, _)) { "Hello, Ms. #{name}!" }
282 | with(User.(_name, :male, _, _, _)) { "Hello, Mr. #{name}!" }
283 | with(User.(_name, _, _, _, _)) { "Hello, #{name}!" }
284 | end
285 | end
286 | end
287 |
288 | u1 = User.new("Egi", :male, true, false, false)
289 | p(u1.greet)#=>"Hello, Mr. Egi!"
290 |
291 | u2 = User.new("Nanaka", :girl, false, false, false)
292 | p(u2.greet)#=>"Hello, Nanaka!"
293 |
294 | u3 = User.new("Hirai", :male, true, true, false)
295 | p(u3.greet)#=>"Hello, Dr. Hirai!"
296 |
297 | u4 = User.new("Hagiya", :male, true, true, true)
298 | p(u4.greet)#=>"Hello, Prof. Hagiya!"
299 | ```
300 |
301 | You can find more demonstrations in the [`sample`](https://github.com/egison/egison-ruby/tree/master/sample) directory.
302 |
303 | ## About Egison
304 |
305 | If you get to love the above pattern-matching, please try [the Egison programming language](https://github.com/egison/egison), too.
306 | Egison is the pattern-matching oriented, purely functional programming language.
307 | Actually, the original pattern-matching system of Egison is more powerful.
308 | For example, we can do following things in the original Egison.
309 |
310 | - We can define new pattern-constructors.
311 | - We can modularize useful patterns.
312 |
313 | There is a new programming world!
314 |
315 | ## Contact
316 |
317 | If you get interested in this Gem, please contact Satoshi Egi or tweet to @Egison_Lang.
318 |
319 | We will talk about this gem in RubyKaigi 2014!
320 |
321 | ## LICENSE
322 |
323 | The license of this library code is BSD.
324 | I learned how to implement pattern-matching in Ruby and how to write a gem from the code of the pattern-match gem by Kazuki Tsujimoto.
325 | I designed syntax of pattern-matching to go with that gem.
326 | This library contains the copy from that gem.
327 | The full license text is [here](https://github.com/egisatoshi/egison-ruby/blob/master/LICENSE).
328 |
329 | ## Sponsors
330 |
331 | This project is sponsored by [Rakuten, Inc.](http://global.rakuten.com/corp/) and [Rakuten Institute of Technology](http://rit.rakuten.co.jp/).
332 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rspec/core/rake_task"
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task default: :spec
7 |
--------------------------------------------------------------------------------
/THANKS.md:
--------------------------------------------------------------------------------
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)
6 |
--------------------------------------------------------------------------------
/egison.gemspec:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.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/egison/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.add_development_dependency 'rspec'
21 | s.add_development_dependency 'simplecov'
22 | s.rdoc_options = ['--main', 'README.rdoc']
23 | end
24 |
--------------------------------------------------------------------------------
/lib/egison.rb:
--------------------------------------------------------------------------------
1 | require 'egison/version'
2 | require 'egison/core'
3 | require 'egison/matcher'
4 |
--------------------------------------------------------------------------------
/lib/egison/core.rb:
--------------------------------------------------------------------------------
1 | require 'egison/lazyarray'
2 |
3 | module PatternMatch
4 | module Matchable
5 | def call(*subpatterns)
6 | pattern_matcher(*subpatterns)
7 | end
8 | end
9 |
10 | class ::Object
11 | private
12 |
13 | def pattern_matcher(*subpatterns)
14 | PatternWithMatcher.new(self, *subpatterns)
15 | end
16 | end
17 |
18 | class MatchingStateStack
19 | attr_accessor :states, :results
20 |
21 | def initialize(pat, tgt)
22 | @states = [MatchingState.new(pat, tgt)]
23 | @results = []
24 | end
25 |
26 | def match
27 | process until @states.empty?
28 | @results
29 | end
30 |
31 | def process
32 | state = @states.shift
33 | rets = state.process
34 | new_states = []
35 | rets.each do |ret|
36 | if ret.atoms.empty?
37 | @results += [ret.bindings]
38 | else
39 | new_states += [ret]
40 | end
41 | end
42 | @states = new_states + @states
43 | end
44 | end
45 |
46 | class MatchingStateStream
47 | def initialize(pat, tgt)
48 | @state = MatchingState.new(pat, tgt)
49 | @processes = []
50 | end
51 |
52 | def match(&block)
53 | @processes << Egison::LazyArray.new(@state.process_stream)
54 | until @processes.empty?
55 | process(@processes.shift, &block)
56 | end
57 | end
58 |
59 | def process(process_iter, &block)
60 | unless process_iter.empty?
61 | ret = process_iter.shift
62 | if ret.atoms.empty?
63 | block.(ret.bindings)
64 | else
65 | @processes << Egison::LazyArray.new(ret.process_stream)
66 | end
67 | @processes << process_iter
68 | end
69 | end
70 | end
71 |
72 | class MatchingState
73 | attr_accessor :atoms, :bindings
74 |
75 | def initialize(pat, tgt)
76 | @atoms = [[pat, tgt]]
77 | @bindings = []
78 | end
79 |
80 | def process
81 | atom = @atoms.shift
82 | rets = atom.first.match(atom.last, @bindings)
83 | rets.map do |new_atoms, new_bindings|
84 | clone.tap do |new_state|
85 | new_state.atoms = new_atoms + new_state.atoms
86 | new_state.bindings += new_bindings
87 | end
88 | end
89 | end
90 |
91 | def process_stream(&block)
92 | return to_enum :process_stream unless block_given?
93 | atom = @atoms.shift
94 | atom.first.match_stream(atom.last, @bindings) do |new_atoms, new_bindings|
95 | new_state = clone
96 | new_state.atoms = new_atoms + new_state.atoms
97 | new_state.bindings += new_bindings
98 | block.(new_state)
99 | end
100 | end
101 | end
102 |
103 | class Pattern
104 | attr_accessor :quantified
105 |
106 | def initialize
107 | end
108 |
109 | def match(_tgt, _bindings)
110 | end
111 |
112 | def match_stream(tgt, bindings, &block)
113 | match(tgt, bindings).each(&block)
114 | end
115 |
116 | def to_a
117 | [PatternCollection.new(self)]
118 | end
119 | end
120 |
121 | class PatternElement < Pattern
122 | def initialize
123 | super()
124 | @quantified = false
125 | end
126 | end
127 |
128 | class PatternWithMatcher < PatternElement
129 | attr_reader :matcher, :subpatterns
130 |
131 | def initialize(matcher, *subpatterns)
132 | super()
133 | @matcher = matcher
134 | @subpatterns = subpatterns
135 | end
136 |
137 | def match(tgt, _)
138 | tgt = tgt.to_a
139 |
140 | return [[[], []]] * @matcher.unnil(tgt).count if subpatterns.empty?
141 |
142 | subpatterns = @subpatterns.clone
143 | px = subpatterns.shift
144 | if px.is_a?(Pattern)
145 | if px.quantified
146 | return [[[[px.pattern, tgt]], []]] if subpatterns.empty?
147 |
148 | unjoineds = @matcher.unjoin(tgt)
149 | unjoineds.map do |xs, ys|
150 | [[[px.pattern, xs], [PatternWithMatcher.new(@matcher, *subpatterns), ys]], []]
151 | end
152 | else
153 | return [] if tgt.empty?
154 |
155 | unconseds = @matcher.uncons(tgt)
156 | unconseds.map do |x, xs|
157 | [[[px, x], [PatternWithMatcher.new(@matcher, *subpatterns), xs]], []]
158 | end
159 | end
160 | else
161 | return [] if tgt.empty?
162 |
163 | unconseds = @matcher.uncons(tgt).select { |x, _xs| px == x }
164 | unconseds.map do |_x, xs|
165 | [[[PatternWithMatcher.new(@matcher, *subpatterns), xs]], []]
166 | end
167 | end
168 | end
169 |
170 | def match_stream(tgt, _bindings, &block)
171 | if subpatterns.empty?
172 | return block.([[], []]) if tgt.empty?
173 | else
174 | subpatterns = @subpatterns.clone
175 | px = subpatterns.shift
176 | if px.is_a?(Pattern)
177 | if px.quantified
178 | return block.([[[px.pattern, tgt]], []]) if subpatterns.empty?
179 |
180 | @matcher.unjoin_stream(tgt) do |xs, ys|
181 | block.([[px.pattern, xs], [PatternWithMatcher.new(@matcher, *subpatterns), ys]], [])
182 | end
183 | else
184 | unless tgt.empty?
185 | @matcher.uncons_stream(tgt) do |x, xs|
186 | block.([[px, x], [PatternWithMatcher.new(@matcher, *subpatterns), xs]], [])
187 | end
188 | end
189 | end
190 | else
191 | unless tgt.empty?
192 | @matcher.uncons_stream(tgt) do |x, xs|
193 | if px == x
194 | block.([[PatternWithMatcher.new(@matcher, *subpatterns), xs]], [])
195 | end
196 | end
197 | end
198 | end
199 | end
200 | end
201 | end
202 |
203 | class OrPattern < PatternElement
204 | attr_reader :pats
205 |
206 | def initialize(*subpatterns)
207 | super()
208 | @pats = subpatterns
209 | end
210 |
211 | def match(tgt, _bindings)
212 | @pats.map { |pat| [[[pat, tgt]], []] }
213 | end
214 | end
215 |
216 | class AndPattern < PatternElement
217 | attr_reader :pats
218 |
219 | def initialize(*subpatterns)
220 | super()
221 | @pats = subpatterns
222 | end
223 |
224 | def match(tgt, _bindings)
225 | [[@pats.map { |pat| [pat, tgt] }, []]]
226 | end
227 | end
228 |
229 | class Wildcard < PatternElement
230 | def initialize
231 | super()
232 | end
233 |
234 | def match(_tgt, _bindings)
235 | [[[], []]]
236 | end
237 | end
238 |
239 | class PatternVariable < PatternElement
240 | attr_reader :name
241 |
242 | def initialize(name)
243 | super()
244 | @name = name
245 | end
246 |
247 | def match(tgt, _bindings)
248 | [[[], [[name, tgt]]]]
249 | end
250 | end
251 |
252 | class ValuePattern < PatternElement
253 | def initialize(ctx, expr)
254 | super()
255 | @ctx = ctx
256 | @expr = expr
257 | end
258 |
259 | def match(tgt, bindings)
260 | val = with_bindings(@ctx, bindings, expr: @expr) { eval expr }
261 | if val.__send__(:===, tgt)
262 | [[[], []]]
263 | else
264 | []
265 | end
266 | end
267 |
268 | class BindingModule < ::Module
269 | end
270 |
271 | def with_bindings(obj, bindings, ext_bindings, &block)
272 | binding_module(obj).module_eval do
273 | begin
274 | bindings.each do |name, val|
275 | define_method(name) { val }
276 | private name
277 | end
278 | ext_bindings.each do |name, val|
279 | define_method(name) { val }
280 | private name
281 | end
282 | obj.instance_eval(&block)
283 | ensure
284 | bindings.each do |name, _|
285 | remove_method(name)
286 | end
287 | ext_bindings.each do |name, _|
288 | remove_method(name)
289 | end
290 | end
291 | end
292 | end
293 |
294 | def binding_module(obj)
295 | m = obj.singleton_class.ancestors.find { |i| i.is_a?(BindingModule) }
296 | unless m
297 | m = BindingModule.new
298 | obj.singleton_class.class_eval do
299 | if respond_to?(:prepend, true)
300 | prepend m
301 | else
302 | include m
303 | end
304 | end
305 | end
306 | m
307 | end
308 | end
309 |
310 | class PatternCollection < Pattern
311 | attr_accessor :pattern
312 |
313 | def initialize(pat)
314 | super()
315 | @quantified = true
316 | @pattern = pat
317 | end
318 | end
319 |
320 | class Env < BasicObject
321 | def initialize(ctx, tgt)
322 | @ctx = ctx
323 | @tgt = tgt
324 | end
325 |
326 | private
327 |
328 | def with(pat, &block)
329 | mstack = MatchingStateStack.new(pat, @tgt)
330 | mstack.match
331 | mstack.results.map { |bindings| with_bindings(@ctx, bindings, &block) }
332 | rescue PatternNotMatch
333 | end
334 |
335 | def method_missing(name, *args)
336 | ::Kernel.raise ::ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
337 | if /^__/.match(name.to_s)
338 | ValuePattern.new(@ctx, name.to_s.gsub(/^__/, ""))
339 | elsif /^_/.match(name.to_s)
340 | PatternVariable.new(name.to_s.gsub(/^_/, "").to_sym)
341 | else
342 | undefined
343 | end
344 | end
345 |
346 | def _(*vals)
347 | case vals.length
348 | when 0
349 | uscore = Wildcard.new
350 | class << uscore
351 | def [](*args)
352 | Egison::List.call(*args)
353 | end
354 | end
355 | uscore
356 | else
357 | undefined
358 | end
359 | end
360 |
361 | def __(*vals)
362 | case vals.length
363 | when 0
364 | Wildcard.new
365 | when 1
366 | ValuePattern.new(@ctx, vals[0])
367 | else
368 | undefined
369 | end
370 | end
371 |
372 | def ___(*vals)
373 | case vals.length
374 | when 0
375 | Wildcard.new
376 | else
377 | undefined
378 | end
379 | end
380 |
381 | def Or(*subpatterns)
382 | OrPattern.new(*subpatterns)
383 | end
384 |
385 | def And(*subpatterns)
386 | AndPattern.new(*subpatterns)
387 | end
388 |
389 | class BindingModule < ::Module
390 | end
391 |
392 | def with_bindings(obj, bindings, &block)
393 | binding_module(obj).module_eval do
394 | begin
395 | bindings.each do |name, val|
396 | define_method(name) { val }
397 | private name
398 | end
399 | obj.instance_eval(&block)
400 | ensure
401 | bindings.each do |name, _|
402 | remove_method(name)
403 | end
404 | end
405 | end
406 | end
407 |
408 | def binding_module(obj)
409 | m = obj.singleton_class.ancestors.find { |i| i.is_a?(BindingModule) }
410 | unless m
411 | m = BindingModule.new
412 | obj.singleton_class.class_eval do
413 | if respond_to?(:prepend, true)
414 | prepend m
415 | else
416 | include m
417 | end
418 | end
419 | end
420 | m
421 | end
422 | end
423 |
424 | class Env2 < Env
425 | def with(pat, &block)
426 | if pat.is_a?(Pattern)
427 | mstack = MatchingStateStack.new(pat, @tgt)
428 | mstack.match
429 | unless mstack.results.empty?
430 | ret = with_bindings(@ctx, mstack.results.first, &block)
431 | ::Kernel.throw(:exit_match, ret)
432 | end
433 | else
434 | if pat == @tgt
435 | ret = with_bindings(@ctx, [], &block)
436 | ::Kernel.throw(:exit_match, ret)
437 | end
438 | end
439 | rescue PatternNotMatch
440 | end
441 | end
442 |
443 | class EnvE < Env
444 | def with(pat, &block)
445 | mstack = MatchingStateStream.new(pat, @tgt)
446 | ::Egison::LazyArray.new(::Enumerator.new{|y|
447 | mstack.match do |bindings|
448 | y << with_bindings(@ctx, bindings, &block)
449 | end
450 | })
451 | rescue PatternNotMatch
452 | end
453 | end
454 |
455 | class PatternNotMatch < Exception; end
456 | class PatternMatchError < StandardError; end
457 | class NoMatchingPatternError < PatternMatchError; end
458 | class MalformedPatternError < PatternMatchError; end
459 |
460 | # Make Pattern and its subclasses/Env private.
461 | if respond_to?(:private_constant)
462 | constants.each do |c|
463 | klass = const_get(c)
464 | next unless klass.is_a?(Class)
465 | if klass <= Pattern
466 | private_constant c
467 | end
468 | end
469 | private_constant :Env, :Env2, :EnvE
470 | end
471 | end
472 |
473 | module Egison
474 | extend self
475 |
476 | def match_all(tgt, &block)
477 | env = PatternMatch.const_get(:Env).new(self, tgt)
478 | env.instance_eval(&block)
479 | end
480 |
481 | def match_stream(tgt, &block)
482 | unless tgt.is_a?(EgisonArray)
483 | tgt = Egison::LazyArray.new(tgt)
484 | end
485 | env = PatternMatch.const_get(:EnvE).new(self, tgt)
486 | env.instance_eval(&block)
487 | end
488 |
489 | def match(tgt, &block)
490 | env = PatternMatch.const_get(:Env2).new(self, tgt)
491 | catch(:exit_match) do
492 | env.instance_eval(&block)
493 | end
494 | end
495 |
496 | alias match_single match
497 | end
498 |
--------------------------------------------------------------------------------
/lib/egison/lazyarray.rb:
--------------------------------------------------------------------------------
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.is_a?(::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 | fail 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.is_a?(::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.map(&:inspect).join(', ')}...]"}>"
154 | end
155 | end
156 |
157 | module EgisonArray; end
158 | LazyArray.send(:include, EgisonArray)
159 | ::Array.send(:include, EgisonArray)
160 | end
161 |
162 | class ::Array
163 | alias :org_plus_meth_esc_by_egison_lazyarray :+
164 | def +(other)
165 | if other.is_a?(Egison::LazyArray)
166 | return other.clone.unshift(*self)
167 | end
168 | org_plus_meth_esc_by_egison_lazyarray(other)
169 | end
170 | end
171 |
--------------------------------------------------------------------------------
/lib/egison/matcher-core.rb:
--------------------------------------------------------------------------------
1 | require 'egison/core'
2 | require 'egison/lazyarray'
3 | module Egison
4 | extend self
5 |
6 | module PatternConstructorBase
7 | include PatternMatch::Matchable
8 |
9 | def sep(val)
10 | val2 = val.clone
11 | [val2.shift, val2]
12 | end
13 |
14 | def sep!(val)
15 | [val.shift, val.clone]
16 | end
17 |
18 | def cln_emp_cln(val)
19 | [val.clone, [], val.clone]
20 | end
21 |
22 | def unnil(val)
23 | if val.empty?
24 | [[]]
25 | else
26 | []
27 | end
28 | end
29 |
30 | def uncons(val)
31 | fail NotImplementedError, "need to define `#{__method__}'"
32 | end
33 |
34 | def uncons_stream(val, &block)
35 | fail NotImplementedError, "need to define `#{__method__}'"
36 | end
37 |
38 | private
39 | def test_conv_lazy_array!(val)
40 | fail PatternMatch::PatternNotMatch unless val.respond_to?(:each)
41 | val = Egison::LazyArray.new(val) unless val.is_a?(EgisonArray)
42 | end
43 | end
44 |
45 | class << Struct
46 | include PatternConstructorBase
47 |
48 | def unnil(val)
49 | [[]]
50 | end
51 |
52 | def uncons(val)
53 | List.uncons(val)
54 | end
55 |
56 | def unjoin(val)
57 | List.unjoin(val)
58 | end
59 | end
60 |
61 | class List
62 | end
63 |
64 | class << List
65 | include PatternConstructorBase
66 |
67 | def uncons(val)
68 | [sep(val)]
69 | end
70 |
71 | def uncons_stream(val, &block)
72 | test_conv_lazy_array!(val)
73 | block.(sep(val))
74 | end
75 |
76 | def unjoin(val)
77 | val2, xs, ys = cln_emp_cln(val)
78 | rets = [[xs, ys]]
79 | until val2.empty?
80 | x, ys = sep!(val2)
81 | xs += [x]
82 | rets += [[xs, ys]]
83 | end
84 | rets
85 | end
86 |
87 | def unjoin_stream(val, &block)
88 | test_conv_lazy_array!(val)
89 | val2, xs, ys = cln_emp_cln(val)
90 | block.([xs, ys])
91 | until val2.empty?
92 | x, ys = sep!(val2)
93 | xs += [x]
94 | block.([xs, ys])
95 | end
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/lib/egison/matcher.rb:
--------------------------------------------------------------------------------
1 | require 'egison/core'
2 | require 'egison/lazyarray'
3 | require 'egison/matcher-core'
4 | require 'set'
5 |
6 | module Egison
7 | extend self
8 |
9 | class Multiset
10 | end
11 |
12 | class << Multiset
13 | include PatternConstructorBase
14 |
15 | def uncons(val)
16 | match_all(val) do
17 | with(List.(*_hs, _x, *_ts)) do
18 | [x, hs + ts]
19 | end
20 | end
21 | end
22 |
23 | def uncons_stream(val, &block)
24 | test_conv_lazy_array!(val)
25 | stream = match_stream(val) {
26 | with(List.(*_hs, _x, *_ts)) do
27 | [x, hs + ts]
28 | end
29 | }
30 | stream.each(&block)
31 | end
32 |
33 | def unjoin(val)
34 | val2, xs, ys = cln_emp_cln(val)
35 | rets = [[xs, ys]]
36 | unless val2.empty?
37 | x, ys = sep!(val2)
38 | rets2 = unjoin(ys)
39 | rets = (rets2.map { |xs2, ys2| [xs2, [x] + ys2] }) + (rets2.map { |xs2, ys2| [[x] + xs2, ys2] })
40 | end
41 | rets
42 | end
43 |
44 | def unjoin_stream(val, &block)
45 | test_conv_lazy_array!(val)
46 | val2, xs, ys = cln_emp_cln(val)
47 | block.([xs, ys])
48 | unless val2.empty?
49 | x, ys = sep!(val2)
50 | unjoin_stream(ys) do |xs2, ys2|
51 | block.([xs2, [x] + ys2]) unless xs2.empty?
52 | block.([[x] + xs2, ys2])
53 | end
54 | end
55 | end
56 | end
57 |
58 | class << Set
59 | include PatternConstructorBase
60 |
61 | def uncons(val)
62 | match_all(val) do
63 | with(List.(*_, _x, *_)) do
64 | [x, val]
65 | end
66 | end
67 | end
68 |
69 | def uncons_stream(val, &block)
70 | test_conv_lazy_array!(val)
71 | stream = match_stream(val) {
72 | with(List.(*_, _x, *_)) do
73 | [x, val]
74 | end
75 | }
76 | stream.each(&block)
77 | end
78 |
79 | def unjoin(val)
80 | val2, xs, ys = cln_emp_cln(val)
81 | rets = [[xs, ys]]
82 | unless val2.empty?
83 | x, ys2 = sep!(val2)
84 | rets2 = unjoin(ys2)
85 | rets = (rets2.map { |xs2, _| [xs2, ys] }) + (rets2.map { |xs2, _ys2| [[x] + xs2, ys] })
86 | rets
87 | end
88 | rets
89 | end
90 |
91 | def unjoin_stream(val, &block)
92 | test_conv_lazy_array!(val)
93 | val2, xs, ys = cln_emp_cln(val)
94 | block.([xs, ys])
95 | unless val2.empty?
96 | x, ys2 = sep!(val2)
97 | unjoin_stream(ys2) do |xs2, _|
98 | block.([xs2, ys]) unless xs2.empty?
99 | block.([[x] + xs2, ys])
100 | end
101 | end
102 | end
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/lib/egison/version.rb:
--------------------------------------------------------------------------------
1 | module Egison
2 | VERSION = "1.0.0"
3 | end
4 |
--------------------------------------------------------------------------------
/sample/combination.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | p(match_all([1, 2, 3, 4, 5]) do with(List.(*_, _x, *_, _y, *_)) { [x, y] } end)
6 |
7 | p(match_all([1, 2, 3, 4, 5]) do with(List.(*_, _x, *_, _y, *_, _z, *_)) { [x, y, z] } end)
8 |
--------------------------------------------------------------------------------
/sample/fib.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | def fib(n)
6 | match(n) do
7 | with(0) { 0 }
8 | with(1) { 1 }
9 | with(_) { fib(n - 1) + fib(n - 2) }
10 | end
11 | end
12 |
13 | p(fib(0))
14 | p(fib(1))
15 | p(fib(10))
16 |
--------------------------------------------------------------------------------
/sample/greet.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | class User < Struct.new(:name, :gender, :married, :doctor, :professor)
6 | def greet
7 | match(self) do
8 | with(User.(_name, _, _, _, true)) { "Hello, Prof. #{name}!" }
9 | with(User.(_name, _, _, true)) { "Hello, Dr. #{name}!" }
10 | with(User.(_name, :female, true)) { "Hello, Mrs. #{name}!" }
11 | with(User.(_name, :female)) { "Hello, Ms. #{name}!" }
12 | with(User.(_name, :male)) { "Hello, Mr. #{name}!" }
13 | with(User.(_name)) { "Hello, #{name}!" }
14 | end
15 | end
16 | end
17 |
18 | u1 = User.new("Egi", :male, true, false, false)
19 | p(u1.greet)
20 |
21 | u2 = User.new("Nanaka", :girl, false, false, false)
22 | p(u2.greet)
23 |
24 | u3 = User.new("Hirai", :male, true, true, false)
25 | p(u3.greet)
26 |
27 | u4 = User.new("Hagiya", :male, true, true, true)
28 | p(u4.greet)
29 |
--------------------------------------------------------------------------------
/sample/join.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | p(match_all([1, 2, 3, 4, 5]) do with(List.(*_hs, *_ts)) { [hs, ts] } end)
6 |
7 | p(match_all([1, 2, 3, 4, 5]) do with(Multiset.(*_hs, *_ts)) { [hs, ts] } end)
8 |
9 | p(match_all([1, 2, 3, 4, 5]) do with(Set.(*_hs, *_ts)) { [hs, ts] } end)
10 |
--------------------------------------------------------------------------------
/sample/poker_hands.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | def poker_hands(cs)
6 | match(cs) do
7 | with(Multiset.(_[_s, 10], _[__s, 11], _[__s, 12], _[__s, 13], _[__s, 1])) do
8 | "Royal Straight flush"
9 | end
10 | with(Multiset.(_[_s, _n], _[__s, __("n+1")], _[__s, __("n+2")], _[__s, __("n+3")], _[__s, __("n+4")])) do
11 | "Straight flush"
12 | end
13 | with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _[_, __n], _)) do
14 | "Four of kind"
15 | end
16 | with(Multiset.(_[_, _m], _[_, __m], _[_, __m], _[_, _n], _[_, __n])) do
17 | "Full house"
18 | end
19 | with(Multiset.(_[_s, _], _[__s, _], _[__s, _], _[__s, _], _[__s, _])) do
20 | "Flush"
21 | end
22 | with(Multiset.(_[_, _n], _[_, __("n+1")], _[_, __("n+2")], _[_, __("n+3")], _[_, __("n+4")])) do
23 | "Straight"
24 | end
25 | with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _, _)) do
26 | "Three of kind"
27 | end
28 | with(Multiset.(_[_, _m], _[_, __m], _[_, _n], _[_, __n], _)) do
29 | "Two pairs"
30 | end
31 | with(Multiset.(_[_, _n], _[_, __n], _, _, _)) do
32 | "One pair"
33 | end
34 | with(Multiset.(_, _, _, _, _)) do
35 | "Nothing"
36 | end
37 | end
38 | end
39 |
40 | p(poker_hands([["diamond", 1], ["diamond", 10], ["diamond", 13], ["diamond", 12], ["diamond", 11]])) #=> "Straight flush"
41 | p(poker_hands([["diamond", 1], ["diamond", 3], ["diamond", 5], ["diamond", 4], ["diamond", 2]])) #=> "Straight flush"
42 | p(poker_hands([["diamond", 1], ["club", 2], ["club", 1], ["heart", 1], ["diamond", 2]])) #=> "Full house"
43 | p(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Straight"
44 | p(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])) #=> "Nothing"
45 |
--------------------------------------------------------------------------------
/sample/primes.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 | require 'prime'
3 |
4 | include Egison
5 |
6 | twin_primes = match_stream(Prime) {
7 | with(List.(*_, _p, __("p + 2"), *_)) {
8 | [p, p + 2]
9 | }
10 | }
11 |
12 | p twin_primes.take(10)
13 |
14 | prime_triplets = match_stream(Prime) {
15 | with(List.(*_, _p, And(Or(__("p + 2"), __("p + 4")), _m), __("p + 6"), *_)) {
16 | [p, m, p + 6]
17 | }
18 | }
19 |
20 | p prime_triplets.take(10)
21 |
--------------------------------------------------------------------------------
/sample/set.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | p(match_all([1, 2, 3, 4, 5]) do with(Set.(_x, _y, *_)) { [x, y] } end)
6 |
--------------------------------------------------------------------------------
/sample/stream.rb:
--------------------------------------------------------------------------------
1 | require 'egison'
2 |
3 | include Egison
4 |
5 | p(match_stream(1..5){ with(List.(*_, _x, *_, _y, *_)) { [x, y] } }.to_a)
6 |
7 | p(match_stream(1..5){ with(List.(*_, 2, *_, _y, *_)) { [2, y] } }.to_a)
8 |
9 | def nats
10 | (1..Float::INFINITY)
11 | end
12 |
13 | p match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)
14 |
15 | p match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)
16 |
--------------------------------------------------------------------------------
/spec/lib/egison/core_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison/core'
3 |
4 |
--------------------------------------------------------------------------------
/spec/lib/egison/matcher_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison/core'
3 | require 'egison/matcher'
4 |
5 | describe "Constructor" do
6 | describe "List" do
7 | it "#uncons" do
8 | expect(
9 | List.uncons([1, 2, 3])
10 | ).to eq(
11 | [[1, [2, 3]]]
12 | )
13 | end
14 |
15 | it "#unjoin" do
16 | expect(
17 | List.unjoin([1, 2, 3])
18 | ).to eq(
19 | [[[], [1, 2, 3]], [[1], [2, 3]], [[1, 2], [3]], [[1, 2, 3], []]]
20 | )
21 | end
22 | end
23 |
24 | describe "Multiset" do
25 | it "#uncons" do
26 | expect(
27 | Multiset.uncons([1, 2, 3])
28 | ).to eq(
29 | [[1, [2, 3]], [2, [1, 3]], [3, [1, 2]]]
30 | )
31 | end
32 |
33 | it "#unjoin" do
34 | expect(
35 | Multiset.unjoin([1, 2, 3])
36 | ).to eq(
37 | [[[], [1, 2, 3]], [[3], [1, 2]], [[2], [1, 3]], [[2, 3], [1]], [[1], [2, 3]], [[1, 3], [2]], [[1, 2], [3]], [[1, 2, 3], []]]
38 | )
39 | end
40 | end
41 |
42 | describe "Set" do
43 | it "#uncons" do
44 | expect(
45 | Set.uncons([1, 2, 3])
46 | ).to eq(
47 | [[1, [1, 2, 3]], [2, [1, 2, 3]], [3, [1, 2, 3]]]
48 | )
49 | end
50 |
51 | it "#unjoin" do
52 | expect(
53 | Set.unjoin([1, 2, 3])
54 | ).to eq(
55 | [[[], [1, 2, 3]], [[3], [1, 2, 3]], [[2], [1, 2, 3]], [[2, 3], [1, 2, 3]], [[1], [1, 2, 3]], [[1, 3], [1, 2, 3]], [[1, 2], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]]
56 | )
57 | end
58 | end
59 |
60 | describe "Struct" do
61 | it "#unnil" do
62 | expect(
63 | Struct.unnil([1, 2, 3])
64 | ).to eq(
65 | [[]]
66 | )
67 | end
68 |
69 | it "#uncons" do
70 | expect(
71 | Struct.uncons([1, 2, 3])
72 | ).to eq(
73 | [[1, [2, 3]]]
74 | )
75 | end
76 |
77 | it "#unjoin" do
78 | expect(
79 | Struct.unjoin([1, 2, 3])
80 | ).to eq(
81 | [[[], [1, 2, 3]], [[1], [2, 3]], [[1, 2], [3]], [[1, 2, 3], []]]
82 | )
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/lib/egison_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | describe "egison" do
5 | describe "#match_all" do
6 | describe "List" do
7 | it "ex1" do
8 | expect(
9 | match_all([1, 2, 3]) do
10 | with(List.(_a, _b, *_)) do
11 | [a, b]
12 | end
13 | end
14 | ).to eq([[1, 2]])
15 | end
16 | end
17 |
18 | describe "Multiset" do
19 | it "ex1" do
20 | expect(
21 | match_all([1, 2, 3]) do
22 | with(Multiset.(_a, _b, *_)) do
23 | [a, b]
24 | end
25 | end
26 | ).to eq([[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]])
27 | end
28 | end
29 |
30 | describe "Set" do
31 | it "ex1" do
32 | expect(
33 | match_all([1, 2, 3]) do
34 | with(Set.(_a, _b, *_)) do
35 | [a, b]
36 | end
37 | end
38 | ).to eq([[1, 1],[1, 2],[1, 3],[2, 1],[2, 2],[2, 3],[3, 1],[3, 2],[3, 3]])
39 | end
40 | end
41 | end
42 |
43 | describe "#match" do
44 | describe "List" do
45 | it "ex1" do
46 | expect(
47 | match_single([1, 2, 3]) do
48 | with(List.(_a, _b, *_)) { true }
49 | end
50 | ).to eq(true)
51 | end
52 | end
53 |
54 | describe "Multiset" do
55 | it "ex1" do
56 | expect(
57 | match_single([1, 2, 3]) do
58 | with(Multiset.(_a, _b, *_)) { true }
59 | end
60 | ).to eq(true)
61 | end
62 | end
63 |
64 | describe "Set" do
65 | it "ex1" do
66 | expect(
67 | match_single([1, 2, 3]) do
68 | with(Set.(_a, _b, *_)) { true }
69 | end
70 | ).to eq(true)
71 | end
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/spec/sample/combination_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | describe "sample" do
7 | describe "combination.rb" do
8 | it %q{match_all([1,2,3,4,5]) do with(List.(*_, _x, *_, _y, *_)) { [x, y] } end} do
9 | expect(match_all([1, 2, 3, 4, 5]) do with(List.(*_, _x, *_, _y, *_)) { [x, y] } end).to eq \
10 | [[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
11 | end
12 | it %q{match_all([1,2,3,4,5]) do with(List.(*_, _x, *_, _y, *_, _z, *_)) { [x, y, z] } end} do
13 | expect(match_all([1, 2, 3, 4, 5]) do with(List.(*_, _x, *_, _y, *_, _z, *_)) { [x, y, z] } end).to eq \
14 | [[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]
15 | end
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/spec/sample/greet_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | class User < Struct.new(:name, :gender, :married, :doctor, :professor)
7 | def greet
8 | match(self) do
9 | with(_[_name, _, _, _, true]) { "Hello, Prof. #{name}!" }
10 | with(_[_name, _, _, true, _]) { "Hello, Dr. #{name}!" }
11 | with(_[_name, :male, _, _, _]) { "Hello, Mr. #{name}!" }
12 | with(_[_name, :female, true, _, _]) { "Hello, Mrs. #{name}!" }
13 | with(_[_name, :female, _, _, _]) { "Hello, Ms. #{name}!" }
14 | with(_[_name, _, _, _, _]) { "Hello, #{name}!" }
15 | end
16 | end
17 | end
18 |
19 | describe "sample" do
20 | describe "greet.rb" do
21 | it %q{User.new("Egi", :male, true, false, false).greet} do
22 | expect(User.new("Egi", :male, true, false, false).greet).to eq \
23 | "Hello, Mr. Egi!"
24 | end
25 | it %q{User.new("Nanaka", :girl, false, false, false).greet} do
26 | expect(User.new("Nanaka", :girl, false, false, false).greet).to eq \
27 | "Hello, Nanaka!"
28 | end
29 | it %q{User.new("Hirai", :male, true, true, false).greet} do
30 | expect(User.new("Hirai", :male, true, true, false).greet).to eq \
31 | "Hello, Dr. Hirai!"
32 | end
33 | it %q{User.new("Hagiya", :male, true, true, true).greet} do
34 | expect(User.new("Hagiya", :male, true, true, true).greet).to eq \
35 | "Hello, Prof. Hagiya!"
36 | end
37 | end
38 | end
39 |
40 |
--------------------------------------------------------------------------------
/spec/sample/join_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | describe "sample" do
7 | describe "join.rb" do
8 | it %q{match_all([1,2,3,4,5]) do with(List.(*_hs, *_ts)) { [hs, ts] } end} do
9 | expect(match_all([1, 2, 3, 4, 5]) do with(List.(*_hs, *_ts)) { [hs, ts] } end).to eq \
10 | [[[], [1, 2, 3, 4, 5]], [[1], [2, 3, 4, 5]], [[1, 2], [3, 4, 5]], [[1, 2, 3], [4, 5]], [[1, 2, 3, 4], [5]], [[1, 2, 3, 4, 5], []]]
11 | end
12 | it %q{match_all([1,2,3,4,5]) do with(Multiset.(*_hs, *_ts)) { [hs, ts] } end} do
13 | expect(match_all([1, 2, 3, 4, 5]) do with(Multiset.(*_hs, *_ts)) { [hs, ts] } end).to eq \
14 | [[[], [1, 2, 3, 4, 5]], [[5], [1, 2, 3, 4]], [[4], [1, 2, 3, 5]], [[4, 5], [1, 2, 3]], [[3], [1, 2, 4, 5]], [[3, 5], [1, 2, 4]], [[3, 4], [1, 2, 5]], [[3, 4, 5], [1, 2]], [[2], [1, 3, 4, 5]], [[2, 5], [1, 3, 4]], [[2, 4], [1, 3, 5]], [[2, 4, 5], [1, 3]], [[2, 3], [1, 4, 5]], [[2, 3, 5], [1, 4]], [[2, 3, 4], [1, 5]], [[2, 3, 4, 5], [1]], [[1], [2, 3, 4, 5]], [[1, 5], [2, 3, 4]], [[1, 4], [2, 3, 5]], [[1, 4, 5], [2, 3]], [[1, 3], [2, 4, 5]], [[1, 3, 5], [2, 4]], [[1, 3, 4], [2, 5]], [[1, 3, 4, 5], [2]], [[1, 2], [3, 4, 5]], [[1, 2, 5], [3, 4]], [[1, 2, 4], [3, 5]], [[1, 2, 4, 5], [3]], [[1, 2, 3], [4, 5]], [[1, 2, 3, 5], [4]], [[1, 2, 3, 4], [5]], [[1, 2, 3, 4, 5], []]]
15 | end
16 | it %q{match_all([1,2,3,4,5]) do with(Set.(*_hs, *_ts)) { [hs, ts] } end} do
17 | expect(match_all([1, 2, 3, 4, 5]) do with(Set.(*_hs, *_ts)) { [hs, ts] } end).to eq \
18 | [[[], [1, 2, 3, 4, 5]], [[5], [1, 2, 3, 4, 5]], [[4], [1, 2, 3, 4, 5]], [[4, 5], [1, 2, 3, 4, 5]], [[3], [1, 2, 3, 4, 5]], [[3, 5], [1, 2, 3, 4, 5]], [[3, 4], [1, 2, 3, 4, 5]], [[3, 4, 5], [1, 2, 3, 4, 5]], [[2], [1, 2, 3, 4, 5]], [[2, 5], [1, 2, 3, 4, 5]], [[2, 4], [1, 2, 3, 4, 5]], [[2, 4, 5], [1, 2, 3, 4, 5]], [[2, 3], [1, 2, 3, 4, 5]], [[2, 3, 5], [1, 2, 3, 4, 5]], [[2, 3, 4], [1, 2, 3, 4, 5]], [[2, 3, 4, 5], [1, 2, 3, 4, 5]], [[1], [1, 2, 3, 4, 5]], [[1, 5], [1, 2, 3, 4, 5]], [[1, 4], [1, 2, 3, 4, 5]], [[1, 4, 5], [1, 2, 3, 4, 5]], [[1, 3], [1, 2, 3, 4, 5]], [[1, 3, 5], [1, 2, 3, 4, 5]], [[1, 3, 4], [1, 2, 3, 4, 5]], [[1, 3, 4, 5], [1, 2, 3, 4, 5]], [[1, 2], [1, 2, 3, 4, 5]], [[1, 2, 5], [1, 2, 3, 4, 5]], [[1, 2, 4], [1, 2, 3, 4, 5]], [[1, 2, 4, 5], [1, 2, 3, 4, 5]], [[1, 2, 3], [1, 2, 3, 4, 5]], [[1, 2, 3, 5], [1, 2, 3, 4, 5]], [[1, 2, 3, 4], [1, 2, 3, 4, 5]], [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]]
19 | end
20 | end
21 | end
22 |
23 |
--------------------------------------------------------------------------------
/spec/sample/join_stream_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | def nats
7 | (1..Float::INFINITY)
8 | end
9 |
10 | describe "sample" do
11 | describe "join_stream.rb" do
12 | # (take 30 (match-all nats (list integer) [> [hs ts]]))
13 | # => {[{} {}] [{} {1}] [{1} {}] [{} {1 2}] [{1} {2}] [{1 2} {}] [{} {1 2 3}] [{1} {2 3}] [{1 2} {3}] [{1 2 3} {}] [{} {1 2 3 4}] [{1} {2 3 4}] [{1 2} {3 4}] [{1 2 3} {4}] [{1 2 3 4} {}] [{} {1 2 3 4 5}] [{1} {2 3 4 5}] [{1 2} {3 4 5}] [{1 2 3} {4 5}] [{1 2 3 4} {5}] [{1 2 3 4 5} {}] [{} {1 2 3 4 5 6}] [{1} {2 3 4 5 6}] [{1 2} {3 4 5 6}] [{1 2 3} {4 5 6}] [{1 2 3 4} {5 6}] [{1 2 3 4 5} {6}] [{1 2 3 4 5 6} {}] [{} {1 2 3 4 5 6 7}] [{1} {2 3 4 5 6 7}]}
14 | # v- same result as above
15 | it %q{match_stream(nats) { with(List.(*_hs, *_ts, *_)) { [hs, ts] } }.take(30)} do
16 | expect(match_stream(nats) { with(List.(*_hs, *_ts, *_)) { [hs, ts] } }.take(30)).to eq \
17 | [[[], []], [[], [1]], [[1], []], [[], [1, 2]], [[1], [2]], [[1, 2], []], [[], [1, 2, 3]], [[1], [2, 3]], [[1, 2], [3]], [[1, 2, 3], []], [[], [1, 2, 3, 4]], [[1], [2, 3, 4]], [[1, 2], [3, 4]], [[1, 2, 3], [4]], [[1, 2, 3, 4], []], [[], [1, 2, 3, 4, 5]], [[1], [2, 3, 4, 5]], [[1, 2], [3, 4, 5]], [[1, 2, 3], [4, 5]], [[1, 2, 3, 4], [5]], [[1, 2, 3, 4, 5], []], [[], [1, 2, 3, 4, 5, 6]], [[1], [2, 3, 4, 5, 6]], [[1, 2], [3, 4, 5, 6]], [[1, 2, 3], [4, 5, 6]], [[1, 2, 3, 4], [5, 6]], [[1, 2, 3, 4, 5], [6]], [[1, 2, 3, 4, 5, 6], []], [[], [1, 2, 3, 4, 5, 6, 7]], [[1], [2, 3, 4, 5, 6, 7]]]
18 | end
19 | # (take 30 (match-all nats (multiset integer) [> [hs ts]]))
20 | # => {[{} {}] [{} {1}] [{1} {}] [{} {2}] [{1} {2}] [{2} {}] [{} {3}] [{1} {3}] [{2} {1}] [{3} {}] [{} {4}] [{1} {4}] [{2} {3}] [{3} {1}] [{4} {}] [{} {5}] [{1} {5}] [{2} {4}] [{3} {2}] [{4} {1}] [{5} {}] [{} {6}] [{1} {6}] [{2} {5}] [{3} {4}] [{4} {2}] [{5} {1}] [{6} {}] [{} {7}] [{1} {7}]}
21 | # v- diffrent result as above, but natural
22 | it %q{match_stream(nats) { with(Multiset.(*_hs, *_ts, *_)) { [hs, ts] } }.take(30)} do
23 | expect(match_stream(nats) { with(Multiset.(*_hs, *_ts, *_)) { [hs, ts] } }.take(30)).to eq \
24 | [[[], []], [[], [1]], [[1], []], [[], [2]], [[1], [2]], [[2], []], [[], [1, 2]], [[1], [3]], [[2], [1]], [[1, 2], []], [[], [3]], [[1], [2, 3]], [[2], [3]], [[1, 2], [3]], [[3], []], [[], [1, 3]], [[1], [4]], [[2], [1, 3]], [[1, 2], [4]], [[3], [1]], [[1, 3], []], [[], [2, 3]], [[1], [2, 4]], [[2], [4]], [[1, 2], [3, 4]], [[3], [2]], [[1, 3], [2]], [[2, 3], []], [[], [1, 2, 3]], [[1], [3, 4]]]
25 | end
26 | # (take 30 (match-all nats (set integer) [> [hs ts]]))
27 | # => {[{} {}] [{} {1}] [{1} {}] [{} {2}] [{1} {1}] [{2} {}] [{} {3}] [{1} {2}] [{2} {1}] [{3} {}] [{} {1 2}] [{1} {3}] [{2} {2}] [{3} {1}] [{1 2} {}] [{} {4}] [{1} {1 2}] [{2} {3}] [{3} {2}] [{1 2} {1}] [{4} {}] [{} {1 3}] [{1} {4}] [{2} {1 2}] [{3} {3}] [{1 2} {2}] [{4} {1}] [{1 3} {}] [{} {2 1}] [{1} {1 3}]}
28 | # v- diffrent result as above, but natural
29 | it %q{match_stream(nats) { with(Set.(*_hs, *_ts, *_)) { [hs, ts] } }.take(30)} do
30 | expect(match_stream(nats) { with(Set.(*_hs, *_ts, *_)) { [hs, ts] } }.take(30)).to eq \
31 | [[[], []], [[], [1]], [[1], []], [[], [2]], [[1], [1]], [[2], []], [[], [1, 2]], [[1], [2]], [[2], [1]], [[1, 2], []], [[], [3]], [[1], [1, 2]], [[2], [2]], [[1, 2], [1]], [[3], []], [[], [1, 3]], [[1], [3]], [[2], [1, 2]], [[1, 2], [2]], [[3], [1]], [[1, 3], []], [[], [2, 3]], [[1], [1, 3]], [[2], [3]], [[1, 2], [1, 2]], [[3], [2]], [[1, 3], [1]], [[2, 3], []], [[], [1, 2, 3]], [[1], [2, 3]]]
32 | end
33 | end
34 | end
35 |
36 |
--------------------------------------------------------------------------------
/spec/sample/poker_hands_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | def poker_hands(cs)
7 | match_single(cs) do
8 | with(Multiset.(_[_s, _n], _[__s, __("n+1")], _[__s, __("n+2")], _[__s, __("n+3")], _[__s, __("n+4")])) do
9 | "Straight flush"
10 | end
11 | with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _[_, __n], _)) do
12 | "Four of kind"
13 | end
14 | with(Multiset.(_[_, _m], _[_, __m], _[_, __m], _[_, _n], _[_, __n])) do
15 | "Full house"
16 | end
17 | with(Multiset.(_[_s, _], _[__s, _], _[__s, _], _[__s, _], _[__s, _])) do
18 | "Flush"
19 | end
20 | with(Multiset.(_[_, _n], _[_, __("n+1")], _[_, __("n+2")], _[_, __("n+3")], _[_, __("n+4")])) do
21 | "Straight"
22 | end
23 | with(Multiset.(_[_, _n], _[_, __n], _[_, __n], _, _)) do
24 | "Three of kind"
25 | end
26 | with(Multiset.(_[_, _m], _[_, __m], _[_, _n], _[_, __n], _)) do
27 | "Two pairs"
28 | end
29 | with(Multiset.(_[_, _n], _[_, __n], _, _, _)) do
30 | "One pair"
31 | end
32 | with(Multiset.(_, _, _, _, _)) do
33 | "Nothing"
34 | end
35 | end
36 | end
37 |
38 | describe "sample" do
39 | describe "poker.rb" do
40 | it "Straight flush" do
41 | expect(poker_hands([["diamond", 1], ["diamond", 3], ["diamond", 5], ["diamond", 4], ["diamond", 2]])).to eq "Straight flush"
42 | end
43 | it "Full house" do
44 | expect(poker_hands([["diamond", 1], ["club", 2], ["club", 1], ["heart", 1], ["diamond", 2]])).to eq "Full house"
45 | end
46 | it "Straight" do
47 | expect(poker_hands([["diamond", 4], ["club", 2], ["club", 5], ["heart", 1], ["diamond", 3]])).to eq "Straight"
48 | end
49 | it "Nothing" do
50 | expect(poker_hands([["diamond", 4], ["club", 10], ["club", 5], ["heart", 1], ["diamond", 3]])).to eq "Nothing"
51 | end
52 | end
53 | end
54 |
55 |
--------------------------------------------------------------------------------
/spec/sample/prime_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 | require 'prime'
4 |
5 | include Egison
6 |
7 | def twin_primes
8 | match_stream(Prime) {
9 | with(List.(*_, _x, __("x + 2"), *_)) {
10 | [x, x + 2]
11 | }
12 | }
13 | end
14 |
15 | describe "sample" do
16 | describe "prime.rb" do
17 |
18 | it %q{twin_primes.take(10)} do
19 | expect(twin_primes.take(10)).to eq \
20 | [[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]
21 | end
22 |
23 | # assign to a variable, execute twice (or more), then obtain the same results. (for twin-primes)
24 | it %q{assign to a variable, execute twice (or more), then obtain the same results. (for twin-primes)} do
25 | tp = twin_primes
26 | tp.take(10) # discard
27 | expect(tp.take(10)).to eq \
28 | [[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/sample/set_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | describe "sample" do
7 | describe "set.rb" do
8 | it %q{match_all([1,2,3,4,5]) do with(Set.(_x,_y, *_)) { [x, y] } end} do
9 | expect(match_all([1, 2, 3, 4, 5]) do with(Set.(_x, _y, *_)) { [x, y] } end).to eq \
10 | [[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]]
11 | end
12 | end
13 | end
14 |
15 |
--------------------------------------------------------------------------------
/spec/sample/stream_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'egison'
3 |
4 | include Egison
5 |
6 | def nats
7 | (1..Float::INFINITY)
8 | end
9 |
10 | describe "sample" do
11 | describe "stream.rb" do
12 | it %q{match_stream(1..5){ with(List.(*_, _x, *_, _y, *_)) { [x, y] } }.to_a } do
13 | expect(match_stream(1..5){ with(List.(*_, _x, *_, _y, *_)) { [x, y] } }.to_a).to eq \
14 | [[1, 2], [1, 3], [2, 3], [1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [4, 5]]
15 | end
16 |
17 | # (take 10 (match-all nats (multiset integer) [> [m n]]))
18 | it %q{match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)} do
19 | expect(match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(10)).to eq \
20 | [[1, 2], [1, 3], [2, 1], [1, 4], [2, 3], [3, 1], [1, 5], [2, 4], [3, 2], [4, 1]]
21 | end
22 |
23 | # (take 20 (match-all nats (multiset integer) [> [m n]]))
24 | it %q{match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(20)} do
25 | expect(match_stream(nats){ with(Multiset.(_m, _n, *_)) { [m, n] } }.take(20)).to eq \
26 | [[1, 2], [1, 3], [2, 1], [1, 4], [2, 3], [3, 1], [1, 5], [2, 4], [3, 2], [4, 1],
27 | [1, 6], [2, 5], [3, 4], [4, 2], [5, 1], [1, 7], [2, 6], [3, 5], [4, 3], [5, 2]]
28 | end
29 |
30 | # (take 10 (match-all nats (set integer) [> [m n]]))
31 | it %q{match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)} do
32 | expect(match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(10)).to eq \
33 | [[1, 1], [1, 2], [2, 1], [1, 3], [2, 2], [3, 1], [1, 4], [2, 3], [3, 2], [4, 1]]
34 | end
35 |
36 | # (take 20 (match-all nats (set integer) [> [m n]]))
37 | it %q{match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(20)} do
38 | expect(match_stream(nats){ with(Set.(_m, _n, *_)) { [m, n] } }.take(20)).to eq \
39 | [[1, 1], [1, 2], [2, 1], [1, 3], [2, 2], [3, 1], [1, 4], [2, 3], [3, 2], [4, 1],
40 | [1, 5], [2, 4], [3, 3], [4, 2], [5, 1], [1, 6], [2, 5], [3, 4], [4, 3], [5, 2]]
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'simplecov'
2 |
3 | SimpleCov.start do
4 | add_filter '/spec/'
5 | end
6 |
7 | RSpec.configure do |config|
8 | end
9 |
--------------------------------------------------------------------------------