├── .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 | --------------------------------------------------------------------------------