├── index ├── name ├── version ├── created ├── title ├── organization ├── summary ├── copyrights ├── repositories ├── requirements ├── authors ├── description └── resources ├── demo ├── 01_intro.md ├── applique │ └── env.rb ├── 02_typecast.md ├── 03_type.md └── 04_overload.md ├── work ├── deprecated │ ├── meta │ │ ├── license │ │ ├── name │ │ ├── version │ │ ├── created │ │ ├── collection │ │ ├── released │ │ ├── authors │ │ ├── homepage │ │ ├── summary │ │ ├── repository │ │ ├── contact │ │ └── description │ ├── test_typecast.rb │ ├── tyepcast.rb │ └── type.rb ├── overload-0.6 │ ├── MANIFEST │ ├── ChangeLog │ ├── overload.rb │ └── test_overload.rb ├── overload.rb ├── test_overload.rb ├── eutype.rb ├── multidispatch0.rb └── multidispatch.rb ├── Gemfile ├── .yardopts ├── .gitignore ├── lib ├── platypus │ ├── version.rb │ ├── core_ext.rb │ ├── typecast.rb │ ├── overload.rb │ └── type.rb └── platypus.rb ├── .travis.yml ├── MANIFEST ├── HISTORY.md ├── Assembly ├── NOTES.md ├── .index ├── LICENSE.txt ├── DEMO.md ├── test └── test_overload.rb ├── README.md └── .gemspec /index/name: -------------------------------------------------------------------------------- 1 | platypus -------------------------------------------------------------------------------- /index/version: -------------------------------------------------------------------------------- 1 | 1.0.2 -------------------------------------------------------------------------------- /index/created: -------------------------------------------------------------------------------- 1 | 2004-01-01 -------------------------------------------------------------------------------- /index/title: -------------------------------------------------------------------------------- 1 | Platypus -------------------------------------------------------------------------------- /index/organization: -------------------------------------------------------------------------------- 1 | RubyWorks -------------------------------------------------------------------------------- /demo/01_intro.md: -------------------------------------------------------------------------------- 1 | # Platypus 2 | 3 | -------------------------------------------------------------------------------- /demo/applique/env.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /index/summary: -------------------------------------------------------------------------------- 1 | Riding on Types with Ruby -------------------------------------------------------------------------------- /work/deprecated/meta/license: -------------------------------------------------------------------------------- 1 | MIT 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/name: -------------------------------------------------------------------------------- 1 | typecast 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/version: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/created: -------------------------------------------------------------------------------- 1 | 2004-01-01 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/collection: -------------------------------------------------------------------------------- 1 | rubyworks 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/released: -------------------------------------------------------------------------------- 1 | 2009-10-23 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /work/deprecated/meta/authors: -------------------------------------------------------------------------------- 1 | Jonas Pfenniger 2 | Thomas Sawyer 3 | -------------------------------------------------------------------------------- /work/deprecated/meta/homepage: -------------------------------------------------------------------------------- 1 | http://rubyworks.github.com/typecast 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/summary: -------------------------------------------------------------------------------- 1 | Double-dispatch Type Conversion System 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --private 2 | --readme README.rdoc 3 | lib 4 | - 5 | [A-Z]*.* 6 | -------------------------------------------------------------------------------- /index/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - Copyright (c) 2004 Thomas Sawyer (BSD-2-Clause) 3 | -------------------------------------------------------------------------------- /work/deprecated/meta/repository: -------------------------------------------------------------------------------- 1 | git://github.com/rubyworks/typecast.git 2 | -------------------------------------------------------------------------------- /work/overload-0.6/MANIFEST: -------------------------------------------------------------------------------- 1 | ChangeLog 2 | overload.rb 3 | test_overload.rb 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | .yardoc 3 | doc/ 4 | log/ 5 | pkg/ 6 | tmp/ 7 | web/ 8 | 9 | -------------------------------------------------------------------------------- /index/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: http://rubyworks.github.com/platypus.git 3 | -------------------------------------------------------------------------------- /index/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - qed (test) 3 | - ae (test) 4 | #- detroit (build) 5 | -------------------------------------------------------------------------------- /work/deprecated/meta/contact: -------------------------------------------------------------------------------- 1 | http://googlegroups/group/rubyworks-mailinglist 2 | -------------------------------------------------------------------------------- /index/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Thomas Sawyer 3 | - Jonas Pfenniger 4 | -------------------------------------------------------------------------------- /lib/platypus/version.rb: -------------------------------------------------------------------------------- 1 | module Platypus 2 | VERSION = "1.0.2" #:erb: VERSION = "<%= version %>" 3 | end 4 | -------------------------------------------------------------------------------- /index/description: -------------------------------------------------------------------------------- 1 | Provides a complete double-dispatch type conversion system, method overloadability and psuedo-classes. -------------------------------------------------------------------------------- /lib/platypus.rb: -------------------------------------------------------------------------------- 1 | require 'platypus/version' 2 | require 'platypus/typecast' 3 | require 'platypus/overload' 4 | require 'platypus/type' 5 | 6 | -------------------------------------------------------------------------------- /work/deprecated/meta/description: -------------------------------------------------------------------------------- 1 | Provides a complete double-dispatch type conversion system. 2 | All the ruby core conversions are available by default. 3 | -------------------------------------------------------------------------------- /index/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/platypus 3 | code: http://github.com/rubyworks/platypus 4 | mail: http://googlegroups/group/rubyworks-mailinglist 5 | -------------------------------------------------------------------------------- /lib/platypus/core_ext.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | 3 | # Dor all matchers === this object. 4 | def case?(*matchers) 5 | matchers.all?{ |m| m === self } 6 | end 7 | 8 | end 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec qed" 4 | rvm: 5 | - 1.8.7 6 | - 1.9.2 7 | - 1.9.3 8 | - rbx 9 | - rbx-2 10 | - jruby 11 | matrix: 12 | allow_failures: 13 | - rvm: rbx 14 | - rvm: rbx-2 15 | cache: bundler 16 | 17 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .index .yardopts bin demo lib man spec test *.md *.txt 2 | .index 3 | .yardopts 4 | demo/01_intro.rdoc 5 | demo/02_typecast.rdoc 6 | demo/03_type.rdoc 7 | demo/04_overload.rdoc 8 | lib/platypus/core_ext.rb 9 | lib/platypus/overload.rb 10 | lib/platypus/type.rb 11 | lib/platypus/typecast.rb 12 | lib/platypus/version.rb 13 | lib/platypus.rb 14 | test/test_overload.rb 15 | NOTES.md 16 | README.md 17 | HISTORY.md 18 | LICENSE.txt 19 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | ## 1.0.2 / 2011-10-22 4 | 5 | This release fixes a bug that preventd Kernel#case? from being defined. 6 | 7 | Changes: 8 | 9 | * Remove Kernel#case? def condition [bug]. 10 | 11 | 12 | ## 1.0.1 / 2011-10-19 13 | 14 | This release simply refreshes the project build script. 15 | Functionality hasn't changed at all. 16 | 17 | Changes: 18 | 19 | * Administrative details only. 20 | 21 | 22 | ## 1.0.0 / 2009-07-07 23 | 24 | This is the initial stand-alone release of Platypus. 25 | 26 | Changes: 27 | 28 | * Happy Birthday! 29 | -------------------------------------------------------------------------------- /demo/02_typecast.md: -------------------------------------------------------------------------------- 1 | ## Type Casting 2 | 3 | Require the casting library. 4 | 5 | require 'platypus/typecast' 6 | 7 | Define a couple of typecasts. 8 | 9 | class ::String 10 | typecast ::Regexp do |string| 11 | /#{string}/ 12 | end 13 | typecast ::Regexp, :multiline do |string| 14 | /#{string}/m 15 | end 16 | end 17 | 18 | ::String.typecast ::Integer do |string| 19 | Integer(string) 20 | end 21 | 22 | See that they work. 23 | 24 | ::Regexp.from("ABC").assert == /ABC/ 25 | 26 | And again. 27 | 28 | "123".to(::Integer).assert == 123 29 | 30 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | gh_pages: web 4 | 5 | gem: 6 | active : true 7 | 8 | dnote: 9 | title: Developer's Notes 10 | labels: ~ 11 | output: log/notes.html 12 | 13 | qed: 14 | files: demo/*.rdoc 15 | 16 | qedoc: 17 | files: demo 18 | title: Platypus Overview 19 | output: DEMO.rdoc 20 | 21 | locat: 22 | output: log/locat.html 23 | active: false 24 | 25 | email: 26 | file : ~ 27 | subject : ~ 28 | mailto : 29 | - ruby-talk@ruby-lang.org 30 | - rubyworks-mailinglist@googlegroups.com 31 | 32 | vclog: 33 | output: 34 | - log/history.html 35 | - log/changes.html 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/03_type.md: -------------------------------------------------------------------------------- 1 | ## Pseudo-Types 2 | 3 | Require the library. 4 | 5 | require 'platypus/type' 6 | 7 | Now we can create types which are psuedo-classes. 8 | 9 | class KiloType < Type 10 | condition do |x| 11 | x.case? Integer 12 | x.kind_of?(Integer) 13 | x.respond_to?(:succ) 14 | x > 1000 15 | end 16 | end 17 | 18 | KiloType.assert === 2000 19 | KiloType.refute === 999 20 | 21 | Using the convenience #x method. 22 | 23 | class MegaType < Type 24 | x.case? Integer 25 | x.kind_of?(Integer) 26 | x.respond_to?(:succ) 27 | x > 1000000 28 | end 29 | 30 | MegaType.refute === 999999 31 | MegaType.assert === 20000000 32 | 33 | 34 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Developer's Notes 2 | 3 | ## 2011-10-19 Name of Project 4 | 5 | This project used to be call Typecast. Maybe that was better than the "silly" 6 | Platypus name? But, then again, "typecast" doesn't quite cover everything this 7 | library does. 8 | 9 | ## 2010-05-27 Alias for Overloadable#overload 10 | 11 | I am not sure if I like `con`, `sig` or `over` better as a short alias for `overload`. I thought of +con+ becuase of it's double meaning as Latin for 'with' and as an abbreviation for 'conditional'. But it seems esoteric in practice, perhaps b/c it would be too general a term if we were programming in Spanish. While +over+ probably makes the most sense in terms of being an abbreviated form of `overload`, `sig` is the same number of characters as `def` and conveys some additional semantics which applies to method overloading --the *signature*. 12 | 13 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - index 6 | authors: 7 | - name: Thomas Sawyer 8 | email: transfire@gmail.com 9 | - name: Jonas Pfenniger 10 | organizations: [] 11 | requirements: 12 | - groups: 13 | - test 14 | development: true 15 | name: qed 16 | - groups: 17 | - test 18 | development: true 19 | name: ae 20 | conflicts: [] 21 | alternatives: [] 22 | resources: 23 | - type: home 24 | uri: http://rubyworks.github.com/platypus 25 | label: Homepage 26 | - type: code 27 | uri: http://github.com/rubyworks/platypus 28 | label: Source Code 29 | - type: mail 30 | uri: http://googlegroups/group/rubyworks-mailinglist 31 | label: Mailing List 32 | repositories: 33 | - name: upstream 34 | scm: git 35 | uri: http://rubyworks.github.com/platypus.git 36 | categories: [] 37 | copyrights: 38 | - holder: Thomas Sawyer 39 | year: '2004' 40 | license: BSD-2-Clause 41 | customs: [] 42 | paths: 43 | lib: 44 | - lib 45 | name: platypus 46 | title: Platypus 47 | summary: Riding on Types with Ruby 48 | created: '2004-01-01' 49 | description: Provides a complete double-dispatch type conversion system, method overloadability 50 | and psuedo-classes. 51 | version: 1.0.2 52 | date: '2014-07-22' 53 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | License:: BSD-2-Clause 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 15 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /demo/04_overload.md: -------------------------------------------------------------------------------- 1 | ## Overloadable 2 | 3 | The Overloadable mixin provides a means for overloading 4 | methods based in method signature using an elegant syntax. 5 | To demonstrate, we first need to load the library. 6 | 7 | require 'platypus/overload' 8 | 9 | Now we can define a class that utilizes it. 10 | 11 | class X 12 | include Overloadable 13 | 14 | sig String, String 15 | 16 | def x(str1, str2) 17 | str1.assert.is_a?(String) 18 | str2.assert.is_a?(String) 19 | end 20 | 21 | sig Integer, Integer 22 | 23 | def x(int1, int2) 24 | int1.assert.is_a?(Integer) 25 | int2.assert.is_a?(Integer) 26 | end 27 | end 28 | 29 | As you can see will placed assertions directly into our methods 30 | definitions. We simply need to run an exmaple of each definition to 31 | see that it worked. 32 | 33 | x = X.new 34 | 35 | x.x("Hello", "World") 36 | 37 | x.x(100, 200) 38 | 39 | But what happens if the signiture is not matched? Either an ArgumentError will 40 | be raised. 41 | 42 | expect ArgumentError do 43 | x.x("Hello", 200) 44 | end 45 | 46 | Or it will fallback to a non-signiature definition, if one is defined. 47 | 48 | 49 | class X 50 | def x(obj1, obj2) 51 | obj1.refute.is_a?(Integer) 52 | obj2.refute.is_a?(String) 53 | end 54 | end 55 | 56 | x.x("Hello", 200) 57 | 58 | -------------------------------------------------------------------------------- /lib/platypus/typecast.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | class Class 4 | 5 | def typecasts 6 | @typecasts ||= {} 7 | end 8 | 9 | # Define a type cast. 10 | def typecast(target_class, *specifics, &block) 11 | set = (specifics.empty? ? nil : Set.new(specifics)) 12 | typecasts[target_class] ||= {} 13 | typecasts[target_class][set] = block 14 | end 15 | 16 | # Convert +source+ to an instance of the class. 17 | def from(source, specifics={}) 18 | set = (specifics.empty? ? nil : Set.new(specifics.keys)) 19 | base = ancestors.find{ |anc| source.class.typecasts.key?(anc) } 20 | if base 21 | cast = source.class.typecasts[base] 22 | if block = cast[set] 23 | if block.arity == 1 24 | return block.call(source) 25 | else 26 | return block.call(source, specifics) 27 | end 28 | end 29 | end 30 | raise TypeError 31 | end 32 | 33 | end 34 | 35 | 36 | class Object 37 | # Convert an object to an instance of given +target_class+. 38 | def to(target_class, specifics={}) 39 | target_class.from(self, specifics) 40 | end 41 | end 42 | 43 | 44 | class String #:nodoc: 45 | typecast Integer do |string| 46 | Integer(string) 47 | end 48 | end 49 | 50 | class Time #:nodoc: 51 | # This method will require the 'time.rb' Time extensions. 52 | typecast String do 53 | require 'time' 54 | parse(string) 55 | end 56 | end 57 | 58 | -------------------------------------------------------------------------------- /work/deprecated/test_typecast.rb: -------------------------------------------------------------------------------- 1 | require 'typecast/cast' 2 | require 'test/unit' 3 | 4 | class TC_TypeCast < Test::Unit::TestCase 5 | 6 | class TestClass 7 | attr_accessor :my_var 8 | def initialize(my_var); @my_var = my_var; end 9 | 10 | def to_string(options={}) 11 | @my_var 12 | end 13 | 14 | class << self 15 | def from_string(string, options={}) 16 | self.new( string ) 17 | end 18 | end 19 | end 20 | 21 | 22 | def setup 23 | @test_string = "this is a test" 24 | @test_class = TestClass.new(@test_string) 25 | end 26 | 27 | def test_to_string 28 | assert_equal( '1234', 1234.cast_to(String) ) 29 | end 30 | 31 | def test_custom_to_string 32 | assert_equal( @test_string, @test_class.cast_to(String) ) 33 | end 34 | 35 | def test_custom_from_string 36 | assert_equal( @test_class.my_var, @test_string.cast_to(TestClass).my_var ) 37 | end 38 | 39 | def test_string_to_class 40 | assert_equal( Test::Unit::TestCase, "Test::Unit::TestCase".cast_to(Class) ) 41 | end 42 | 43 | def test_string_to_time 44 | assert_equal( "Mon Oct 10 00:00:00 2005", "2005-10-10".cast_to(Time).localtime.strftime("%a %b %d %H:%M:%S %Y") ) 45 | end 46 | 47 | def test_no_converter 48 | "sfddsf".cast_to( ::Regexp ) 49 | assert(1+1==3, 'should not get here') 50 | rescue Exception => ex 51 | assert_equal(TypeError, ex.class) 52 | end 53 | end 54 | 55 | -------------------------------------------------------------------------------- /work/overload.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | 3 | # Returns a hash of overloaded methods. 4 | def method_overloads 5 | @method_overloads ||= {} 6 | end 7 | 8 | # Overload methods. 9 | # 10 | # class X 11 | # def x 12 | # "hello" 13 | # end 14 | # 15 | # overload :x, Integer do |i| 16 | # i 17 | # end 18 | # 19 | # overload :x, String, String do |s1, s2| 20 | # [s1, s2] 21 | # end 22 | # end 23 | # 24 | def overload( name, *signiture, &block ) 25 | 26 | raise ArgumentError unless signiture.all?{|s| s.instance_of?(Class)} 27 | 28 | name = name.to_sym 29 | 30 | if method_overloads.key?( name ) 31 | method_overloads[name][signiture] = block 32 | 33 | else 34 | method_overloads[name] = {} 35 | method_overloads[name][signiture] = block 36 | 37 | if method_defined?( name ) 38 | #method_overloads[name][nil] = instance_method( name ) #true 39 | alias_method( "#{name}Generic", name ) 40 | has_generic = true 41 | else 42 | has_generic = false 43 | end 44 | 45 | define_method( name ) do |*args| 46 | ovr = self.class.method_overloads["#{name}".to_sym] 47 | sig = args.collect{ |a| a.class } 48 | hit = nil 49 | faces = ovr.keys #.sort { |a,b| b.size <=> a.size } 50 | faces.each do |cmp| 51 | next unless cmp.size == sig.size 52 | if (0...cmp.size).all?{ |i| cmp[i] >= sig[i] } 53 | break hit = cmp 54 | end 55 | end 56 | if hit 57 | ovr[hit].call(*args) 58 | else 59 | if has_generic #ovr[nil] 60 | send( "#{name}Generic", *args ) 61 | #ovr[nil].bind(self).call(*args) 62 | else 63 | raise NoMethodError 64 | end 65 | end 66 | end 67 | end 68 | 69 | end #def 70 | 71 | end #class Module 72 | 73 | -------------------------------------------------------------------------------- /work/overload-0.6/ChangeLog: -------------------------------------------------------------------------------- 1 | Fri Jan 3 03:51:54 2003 Nobuyoshi Nakada 2 | 3 | * overload.rb: use Object#class instead of Object#type. 4 | 5 | Tue Feb 5 16:44:32 2002 Nobuyoshi Nakada 6 | 7 | * test_overload.rb: 1.6 support. 8 | 9 | * overload.rb: ditto. 10 | 11 | * overload.rb (Method): made methods private. no longer extend 12 | Method::Overload. 13 | 14 | * overload.rb (Module): made methods private. 15 | 16 | Tue Dec 18 04:27:12 2001 Nobuyoshi Nakada 17 | 18 | * test_overload.rb (TestOverload#do_dispatch): added test for 19 | pattern match. 20 | 21 | * test_overload.rb (TestOverload#do_signature): ditto. 22 | 23 | * test_overload.rb (TestOverload#do_overload): ditto. 24 | 25 | * test_overload.rb (TestOverload#do_test_match): ditto. 26 | 27 | * test_overload.rb (TestOverload#do_test_nomatch): error message 28 | format changed. 29 | 30 | * test_overload.rb (TestOverload#test_cvar): methods are 4. 31 | 32 | * test_overload.rb (TestOverload#test_*_match): added. 33 | 34 | * test_overload.rb (TestOverload#test_overload_nomatch): exception 35 | changed. 36 | 37 | Tue Dec 18 03:56:42 2001 Nobuyoshi Nakada 38 | 39 | * overload.rb (Method::Signature#===): return false when 40 | StarndardError occurred. 41 | 42 | Mon Dec 17 14:41:31 2001 Nobuyoshi Nakada 43 | 44 | * MANIFEST: added ChangeLog 45 | 46 | * overload.rb (Method::Signature#nomatch): added. 47 | 48 | * overload.rb (Method::Signature#to_str): added. 49 | 50 | * overload.rb (Method::Signature#inspect): separate to_str. 51 | 52 | * overload.rb (Method::Signature#to_s): ditto. 53 | 54 | * overload.rb (Method::Overload.dispatch): use Signature#nomatch. 55 | 56 | * test_overload.rb (TestOverload::do_test_nomatch): 57 | Signature#inspect changed. 58 | 59 | Mon Dec 13 01:56:12 2001 Nobuyoshi Nakada 60 | 61 | * overload.rb (Method::Signature#invalid): improve error message. 62 | 63 | * overload.rb (Method::Overload.dispatch): ditto. 64 | 65 | * overload.rb (Method::Signature#inspect): use Array#inspect. 66 | -------------------------------------------------------------------------------- /lib/platypus/overload.rb: -------------------------------------------------------------------------------- 1 | # The Obverloadable mixin allows you to easily 2 | # overload methods based on method signitures. 3 | # 4 | module Overloadable 5 | 6 | # 7 | def self.append_features(base) 8 | if Module==base 9 | super(base) 10 | else 11 | base.extend(self) 12 | end 13 | end 14 | 15 | # Setup an overload state. 16 | def overload(*signature) 17 | (@overload_stack ||= []) << signature 18 | end 19 | 20 | # Short alias for +overload+. 21 | alias_method :sig, :overload 22 | 23 | # 24 | def method_added(name) 25 | return if $skip 26 | 27 | @overload_stack ||= [] 28 | @overload_method ||= {} 29 | 30 | signature = @overload_stack.pop 31 | 32 | if !method_defined?("#{name}:origin") 33 | $skip = true 34 | if signature 35 | define_method("#{name}:origin"){|*a| raise ArgumentError } 36 | else 37 | alias_method("#{name}:origin", name) 38 | end 39 | $skip = false 40 | end 41 | 42 | if signature 43 | @overload_module ||= Module.new 44 | 45 | include @overload_module 46 | 47 | signature = Signature[*signature] 48 | @overload_method[name] ||= [] 49 | @overload_method[name] << signature 50 | 51 | signame = "#{name}:#{signature.key}" 52 | 53 | alias_method(signame, name) 54 | 55 | sigs = @overload_method[name] 56 | $skip = true 57 | define_method(name) do |*args| 58 | #sigs.sort.each do |sig| 59 | s = sigs.find{ |sig| sig.match?(args) } 60 | if s 61 | __send__("#{name}:#{s.key}", *args) 62 | else 63 | __send__("#{name}:origin", *args) 64 | end 65 | end 66 | $skip = false 67 | end 68 | 69 | end 70 | 71 | # 72 | class Signature < Array 73 | def key 74 | hash #Marshal.dump(self) 75 | end 76 | 77 | # 78 | def match?(args) 79 | return false unless size == args.size 80 | size.times do |i| 81 | return false unless self[i] === args[i] 82 | end 83 | true 84 | end 85 | 86 | # 87 | def <=>(other) 88 | cmp = (size <=> other.size) 89 | return cmp if cmp && cmp != 0 90 | size.times do |i| 91 | cmp = (self[i] <=> other[i]) 92 | return cmp if cmp && cmp != 0 93 | end 94 | 0 95 | end 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /DEMO.md: -------------------------------------------------------------------------------- 1 | # Platypus 2 | 3 | ## Type Casting 4 | 5 | Require the casting library. 6 | 7 | require 'platypus/typecast' 8 | 9 | Define a couple of typecasts. 10 | 11 | class ::String 12 | typecast ::Regexp do |string| 13 | /#{string}/ 14 | end 15 | typecast ::Regexp, :multiline do |string| 16 | /#{string}/m 17 | end 18 | end 19 | 20 | ::String.typecast ::Integer do |string| 21 | Integer(string) 22 | end 23 | 24 | See that they work. 25 | 26 | ::Regexp.from("ABC").assert == /ABC/ 27 | 28 | And again. 29 | 30 | "123".to(::Integer).assert == 123 31 | 32 | 33 | ## Pseudo-Types 34 | 35 | Require the library. 36 | 37 | require 'platypus/type' 38 | 39 | Now we can create types which are psuedo-classes. 40 | 41 | class KiloType < Type 42 | condition do |x| 43 | x.case? Integer 44 | x.kind_of?(Integer) 45 | x.respond_to?(:succ) 46 | x > 1000 47 | end 48 | end 49 | 50 | KiloType.assert === 2000 51 | KiloType.refute === 999 52 | 53 | Using the convenience #x method. 54 | 55 | class MegaType < Type 56 | x.case? Integer 57 | x.kind_of?(Integer) 58 | x.respond_to?(:succ) 59 | x > 1000000 60 | end 61 | 62 | MegaType.refute === 999999 63 | MegaType.assert === 20000000 64 | 65 | 66 | ## Overloadable 67 | 68 | The Overloadable mixin provides a means for overloading 69 | methods based in method signature using an elegant syntax. 70 | To demonstrate, we first need to load the library. 71 | 72 | require 'platypus/overload' 73 | 74 | Now we can define a class that utilizes it. 75 | 76 | class X 77 | include Overloadable 78 | 79 | sig String, String 80 | 81 | def x(str1, str2) 82 | str1.assert.is_a?(String) 83 | str2.assert.is_a?(String) 84 | end 85 | 86 | sig Integer, Integer 87 | 88 | def x(int1, int2) 89 | int1.assert.is_a?(Integer) 90 | int2.assert.is_a?(Integer) 91 | end 92 | end 93 | 94 | As you can see will placed assertions directly into our methods 95 | definitions. We simply need to run an exmaple of each definition to 96 | see that it worked. 97 | 98 | x = X.new 99 | 100 | x.x("Hello", "World") 101 | 102 | x.x(100, 200) 103 | 104 | But what happens if the signiture is not matched? Either an `ArgumentError` will 105 | be raised. 106 | 107 | expect ArgumentError do 108 | x.x("Hello", 200) 109 | end 110 | 111 | Or it will fallback to a non-signiature definition, if one is defined. 112 | 113 | class X 114 | def x(obj1, obj2) 115 | obj1.refute.is_a?(Integer) 116 | obj2.refute.is_a?(String) 117 | end 118 | end 119 | 120 | x.x("Hello", 200) 121 | 122 | 123 | -------------------------------------------------------------------------------- /work/test_overload.rb: -------------------------------------------------------------------------------- 1 | require 'platypus/overload' 2 | require 'test/unit' 3 | 4 | # 5 | class TC_Overload_01 < Test::Unit::TestCase 6 | 7 | class X 8 | 9 | def x 10 | "hello" 11 | end 12 | 13 | overload :x, Array do |x| 14 | [Array, x] 15 | end 16 | 17 | overload :x, Symbol do |x| 18 | [Symbol, x] 19 | end 20 | 21 | end 22 | 23 | def setup 24 | @x = X.new 25 | end 26 | 27 | def test_x 28 | assert_equal( "hello", @x.x ) 29 | end 30 | 31 | def test_a 32 | assert_equal( [Array, [1]], @x.x([1]) ) 33 | end 34 | 35 | def test_s 36 | assert_equal( [Symbol, :a], @x.x(:a) ) 37 | end 38 | 39 | end 40 | 41 | # 42 | # 43 | class TC_Overload_02 < Test::Unit::TestCase 44 | 45 | class X 46 | 47 | def x 48 | "hello" 49 | end 50 | 51 | overload :x, Integer do |i| 52 | i 53 | end 54 | 55 | overload :x, String, String do |s1, s2| 56 | [s1, s2] 57 | end 58 | 59 | end 60 | 61 | def setup 62 | @x = X.new 63 | end 64 | 65 | def test_x 66 | assert_equal( "hello", @x.x ) 67 | end 68 | 69 | def test_i 70 | assert_equal( 1, @x.x(1) ) 71 | end 72 | 73 | def test_s 74 | assert_equal( ["a","b"], @x.x("a","b") ) 75 | end 76 | 77 | end 78 | 79 | # 80 | # 81 | class TC_Overload_03 < Test::Unit::TestCase 82 | 83 | class SubArray < Array 84 | end 85 | 86 | class SubSubArray < SubArray 87 | end 88 | 89 | 90 | class X 91 | 92 | def x 93 | "hello" 94 | end 95 | 96 | overload :x, Integer do |i| 97 | i 98 | end 99 | 100 | overload :x, Symbol do |s| 101 | s 102 | end 103 | 104 | overload :x, String, String do |s1, s2| 105 | [s1, s2] 106 | end 107 | 108 | overload :x, Symbol, String do |s1, s2| 109 | [s1, s2] 110 | end 111 | 112 | overload :x, Array do |a| 113 | "array" 114 | end 115 | 116 | end 117 | 118 | def setup 119 | @x = X.new 120 | end 121 | 122 | def test_x 123 | assert_equal( "hello", @x.x ) 124 | end 125 | 126 | def test_i 127 | assert_equal( 1, @x.x(1) ) 128 | end 129 | 130 | def test_strings 131 | assert_equal( ["a","b"], @x.x("a","b") ) 132 | end 133 | 134 | def test_symbol_string 135 | assert_equal( [:a,"b"], @x.x(:a,"b") ) 136 | end 137 | 138 | def test_sym 139 | assert_equal( :sym, @x.x(:sym) ) 140 | end 141 | 142 | def test_subarray 143 | assert_equal("array", @x.x([])) 144 | assert_equal("array", @x.x(SubArray.new)) 145 | assert_equal("array", @x.x(SubSubArray.new)) 146 | end 147 | 148 | def test_raise 149 | assert_raise ArgumentError do 150 | X.module_eval do 151 | overload :x, 42 do end 152 | end 153 | end 154 | end 155 | end 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /test/test_overload.rb: -------------------------------------------------------------------------------- 1 | require 'platypus/overload' 2 | 3 | # 4 | class TC_Overload_01 < Test::Unit::TestCase 5 | 6 | class X 7 | include Overloadable 8 | 9 | def x 10 | "hello" 11 | end 12 | 13 | overload Array 14 | 15 | def x(x) 16 | [Array, x] 17 | end 18 | 19 | overload Symbol 20 | 21 | def x(x) 22 | [Symbol, x] 23 | end 24 | end 25 | 26 | def setup 27 | @x = X.new 28 | end 29 | 30 | def test_x 31 | assert_equal( "hello", @x.x ) 32 | end 33 | 34 | def test_a 35 | assert_equal( [Array, [1]], @x.x([1]) ) 36 | end 37 | 38 | def test_s 39 | assert_equal( [Symbol, :a], @x.x(:a) ) 40 | end 41 | 42 | end 43 | 44 | # 45 | class TC_Overload_02 < Test::Unit::TestCase 46 | 47 | class X 48 | include Overloadable 49 | 50 | def x 51 | "hello" 52 | end 53 | 54 | overload Integer 55 | 56 | def x(i) 57 | i 58 | end 59 | 60 | overload String, String 61 | 62 | def x(s1, s2) 63 | [s1, s2] 64 | end 65 | 66 | end 67 | 68 | def setup 69 | @x = X.new 70 | end 71 | 72 | def test_x 73 | assert_equal( "hello", @x.x ) 74 | end 75 | 76 | def test_i 77 | assert_equal( 1, @x.x(1) ) 78 | end 79 | 80 | def test_s 81 | assert_equal( ["a","b"], @x.x("a","b") ) 82 | end 83 | 84 | end 85 | 86 | # 87 | class TC_Overload_03 < Test::Unit::TestCase 88 | 89 | class SubArray < Array 90 | end 91 | 92 | class SubSubArray < SubArray 93 | end 94 | 95 | class X 96 | include Overloadable 97 | 98 | def x 99 | "hello" 100 | end 101 | 102 | overload Integer 103 | 104 | def x(i) 105 | i 106 | end 107 | 108 | overload Symbol 109 | 110 | def x(s) 111 | s 112 | end 113 | 114 | overload String, String 115 | 116 | def x(s1, s2) 117 | [s1, s2] 118 | end 119 | 120 | overload Symbol, String 121 | 122 | def x(s1, s2) 123 | [s1, s2] 124 | end 125 | 126 | overload Array 127 | 128 | def x(a) 129 | "array" 130 | end 131 | 132 | end 133 | 134 | def setup 135 | @x = X.new 136 | end 137 | 138 | def test_x 139 | assert_equal( "hello", @x.x ) 140 | end 141 | 142 | def test_i 143 | assert_equal( 1, @x.x(1) ) 144 | end 145 | 146 | def test_strings 147 | assert_equal( ["a","b"], @x.x("a","b") ) 148 | end 149 | 150 | def test_symbol_string 151 | assert_equal( [:a,"b"], @x.x(:a,"b") ) 152 | end 153 | 154 | def test_sym 155 | assert_equal( :sym, @x.x(:sym) ) 156 | end 157 | 158 | def test_subarray 159 | assert_equal("array", @x.x([])) 160 | assert_equal("array", @x.x(SubArray.new)) 161 | assert_equal("array", @x.x(SubSubArray.new)) 162 | end 163 | 164 | #def test_raise 165 | # assert_raise ArgumentError do 166 | # X.module_eval do 167 | # overload 42 168 | # end 169 | # end 170 | #end 171 | 172 | end 173 | 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Platypus 2 | 3 | [![Gem Version](http://img.shields.io/gem/v/platypus.svg?style=flat)](http://rubygems.org/gem/platypus) 4 | [![Build Status](http://img.shields.io/travis/rubyworks/platypus.svg?style=flat)](http://travis-ci.org/rubyworks/platypus) 5 | [![Fork Me](http://img.shields.io/badge/scm-github-blue.svg?style=flat)](http://github.com/rubyworks/platypus) 6 | [![Report Issue](http://img.shields.io/github/issues/rubyworks/platypus.svg?style=flat)](http://github.com/rubyworks/platypus/issues) 7 | [![Gittip](http://img.shields.io/badge/gittip-$1-green.svg?style=flat)](https://www.gittip.com/on/github/rubyworks/) 8 | 9 | [Homepage](http://rubyworks.github.com/platypus) · 10 | [Development](http://github.com/rubyworks/platypus) · 11 | [Report Issue](http://github.com/rubyworks/platypus/issues) 12 | 13 | Platypus provides a generalized type conversion system, 14 | method overloading and psuedo-classes for the Ruby programming 15 | language. 16 | 17 | 18 | ## Overview 19 | 20 | Type conversion work like a rational duck might expect. 21 | 22 | ```ruby 23 | "1234".to(Float) => 1234.0 (Float) 24 | 25 | Time.from("6:30") => 1234.0 (Time) 26 | ``` 27 | 28 | You can of course define your own. 29 | 30 | ```ruby 31 | class X 32 | typecast String do |x| 33 | "#{x}" 34 | end 35 | end 36 | ``` 37 | 38 | To overload a method, mixin the Overloadable module and use the #overload (or #sig) 39 | method to define new functionality based on a specified type interface. 40 | 41 | ```ruby 42 | class X 43 | include Overloadable 44 | 45 | def f 46 | "f" 47 | end 48 | 49 | sig Integer 50 | def f(i) 51 | "f#{i}" 52 | end 53 | 54 | sig String, String 55 | def f(s1, s2) 56 | [s1, s2].join('+') 57 | end 58 | end 59 | 60 | x = X.new 61 | 62 | x.f #=> "f" 63 | x.f(1) #=> "f1" 64 | x.f("A","B") #=> "A+B" 65 | ``` 66 | 67 | Finally, the Platypus gives you the Type superclass (aka pseudo-classes). 68 | 69 | ```ruby 70 | class KiloType < Type 71 | x % 1000 == 0 72 | end 73 | 74 | KiloType === 1000 75 | KiloType === 2000 76 | ``` 77 | 78 | To learn more about using Platypus see the [Demonstrundum](http://rubyworks.github.com/platypus/docs/demo). 79 | 80 | 81 | ## Installation 82 | 83 | To install with RubyGems simply open a console and type: 84 | 85 | $ gem install platypus 86 | 87 | Or add it as a dependency to your Gemfile. 88 | 89 | gem "platypus" 90 | 91 | Old school site installation can be achieved with Setup.rb (gem install setup), 92 | then download the tarball package and type: 93 | 94 | $ tar -xvzf platypus-1.0.0.tgz 95 | $ cd platypus-1.0.0 96 | $ setup.rb all 97 | 98 | Windows users use 'ruby setup.rb all'. 99 | 100 | 101 | ## Authors 102 | 103 | * Thomas Sawyer (trans) 104 | * Jonas Pfenniger 105 | 106 | 107 | ## Copying 108 | 109 | Copyright (c) 2010 Rubyworks 110 | 111 | This program is ditributed unser the terms of the *FreeBSD* license. 112 | 113 | See LICENSE.txt file for details. 114 | 115 | 116 |

117 | 118 | _ ___ 119 | / \ / \ 120 | \. |: cc| .---------. 121 | (.|:,---, < Feel the \ 122 | (.|: \ c| \ POWER!!! / 123 | (. y-' '--------' 124 | \ _ / 125 | m m 126 | 127 | -------------------------------------------------------------------------------- /lib/platypus/type.rb: -------------------------------------------------------------------------------- 1 | require 'platypus/core_ext' 2 | 3 | # Base class for Euphoria-like Types. 4 | # 5 | # class KiloType < Type 6 | # condition do |x| 7 | # x.case? Integer 8 | # x.kind_of?(Integer) 9 | # x.respond_to?(:succ) 10 | # x > 1000 11 | # end 12 | # end 13 | # 14 | # Becuase the +x.something?+ is so common, TypeCast provides 15 | # special "magic-dot" method to make defining these 16 | # conditions more concise. 17 | # 18 | # class KiloType < Type 19 | # x.case? Integer 20 | # x.kind_of?(Integer) 21 | # x.respond_to?(:succ) 22 | # x > 1000 23 | # end 24 | # 25 | # While TypeCasts are not actual types in the sense they 26 | # are not actual classes. They can be used for conversion 27 | # by defining a "from_" class method. In doing so 28 | # you should make sure the result of the conversion conforms 29 | # to the typecast. You can use the TypeCast.validate method 30 | # to make that a bit easier. For instance: 31 | # 32 | # class KiloType 33 | # def from_string(str) 34 | # validate(str.to_i) 35 | # end 36 | # end 37 | # 38 | # TypeCast also provides a helper DSL method that handles 39 | # this for you. 40 | # 41 | # class KiloType 42 | # conversion String do |str| 43 | # str.to_i 44 | # end 45 | # end 46 | # 47 | # This will define a method equivalent to the prior example. 48 | 49 | class Type 50 | 51 | # Activeate/Deactivate type-checking globally (NOT USED YET). 52 | def self.check(on_or_off=nil) 53 | @check = on_or_off unless on_or_off.nil? 54 | @check 55 | end 56 | 57 | def self.validate(obj) 58 | raise TypeError unless self === obj 59 | return obj 60 | end 61 | 62 | # 63 | def self.condition(&block) 64 | @index ||= index 65 | @index = @index.succ 66 | define_method("condition_#{@index}", &block) 67 | #@conditions << block 68 | end 69 | 70 | # 71 | def self.conversion(klass, &block) 72 | name = klass.name.downcase.gsub('::', '_') 73 | (class << self; self; end).class_eval do 74 | define_method("from_#{name}") do |from| 75 | r = block.call(from) 76 | validate(r) 77 | end 78 | end 79 | end 80 | 81 | # 82 | def self.x 83 | @x ||= Conditions.new(self) 84 | end 85 | 86 | # 87 | class Conditions 88 | instance_methods.each{ |x| private x unless x.to_s =~ /^__/ } 89 | 90 | def initialize(type) 91 | @type = type 92 | end 93 | 94 | #def __conditions__ 95 | # @__conditions__ ||= [] 96 | #end 97 | 98 | def method_missing(s, *a, &b) 99 | @type.condition do |x| 100 | x.__send__(s, *a, &b) 101 | end 102 | #__conditions__ << [s, a, b] 103 | end 104 | end 105 | 106 | # 107 | #def initialize(*matchers, &validate) 108 | # @matchers = matchers 109 | # @validate = validate 110 | #end 111 | 112 | # 113 | def self.index 114 | methods = instance_methods.select{ |m| m.to_s =~ /^condition_/ } 115 | indexes = methods.map{ |m| m.split('_')[1].to_i } 116 | indexes.max || 0 117 | end 118 | 119 | def self.conditions 120 | #x.__conditions__ + (@conditions || []) + (defined?(super) ? super : []) 121 | instance_methods.select{ |m| m.to_s =~ /^condition_/ } 122 | end 123 | 124 | # 125 | def self.===(obj) 126 | #conditions.all? do |s, a, b| 127 | # obj.__send__(s, *a, &b) 128 | #end 129 | instance = new 130 | conditions.all? do |method| 131 | instance.__send__(method, obj) 132 | end 133 | end 134 | 135 | # 136 | #def match?(argument) 137 | # @matchers.all? do |matcher| 138 | # case matcher 139 | # when Symbol 140 | # argument.send(:respond_to?, matcher) } 141 | # else 142 | # matcher === argument 143 | # end 144 | # end 145 | #end 146 | 147 | # 148 | #def valid?(obj) 149 | # @validate[obj] if @validate 150 | #end 151 | 152 | end 153 | 154 | -------------------------------------------------------------------------------- /work/overload-0.6/overload.rb: -------------------------------------------------------------------------------- 1 | class Method 2 | module Overload 3 | Version = %w$Revision: 0.6 $[1] # -*- package version -*- 4 | end 5 | 6 | class Signature < Array 7 | defined?(NoMethodError) or NoMethodError = NameError 8 | 9 | def invalid(func = nil) 10 | msg = "Invalid signature: " 11 | msg << func.to_s if func 12 | TypeError.new(msg << to_s) 13 | end 14 | 15 | def nomatch(recv, func, level = 1) 16 | raise NoMethodError, "no method match to `#{func}#{self}' for #{recv.class}", caller(level) 17 | end 18 | 19 | def varargs? 20 | !(empty? or last) 21 | end 22 | 23 | def arity 24 | varargs? ? -length : length 25 | end 26 | 27 | def to_str 28 | sig = Array[*self] 29 | sig.pop if v = varargs? 30 | sig = sig.inspect 31 | sig[-1, 0] = ', ...' if v 32 | sig 33 | end 34 | 35 | def inspect 36 | self.class.name+self 37 | end 38 | 39 | def to_s 40 | sig = to_str 41 | sig[0] = ?( 42 | sig[-1] = ?) 43 | sig 44 | end 45 | 46 | def ===(args) 47 | if varargs? 48 | (l = length-1) <= args.length or return false 49 | else 50 | (l = length) == args.length or return false 51 | end 52 | l.times {|i| self[i] === args[i] or return false} 53 | true 54 | rescue StandardError 55 | false 56 | end 57 | 58 | def >(sig) 59 | v1, l1 = varargs?, length 60 | v2, l2 = sig.varargs?, sig.length 61 | l1 -= 1 if v1 62 | l2 -= 1 if v2 63 | begin 64 | [l1, l2].min.times do |i| 65 | return false if sig[i] > self[i] 66 | end 67 | rescue TypeError 68 | return false 69 | end 70 | if v1 71 | return false if l1 > l2 72 | elsif v2 73 | return false 74 | else 75 | return false unless l1 == l2 76 | end 77 | true 78 | end 79 | 80 | def <(sig) 81 | sig > self 82 | end 83 | 84 | def <=>(sig) 85 | if self > sig 86 | 1 87 | elsif sig > self 88 | -1 89 | else 90 | 0 91 | end 92 | end 93 | 94 | def dispatch(*types, &block) 95 | throw :dispatched, yield(*self) if self.class[*types] === self 96 | end 97 | end 98 | 99 | module Overload 100 | Signature = Signature 101 | 102 | private 103 | 104 | def overload(args) 105 | args = Signature[*args] 106 | catch(:dispatched) do 107 | yield args 108 | raise args.invalid 109 | end 110 | end 111 | end 112 | 113 | def self.dispatch(recv, func, args, block) 114 | klass = tbl = sig = meth = nil 115 | recv.class.ancestors.find do |klass| 116 | tbl = klass.instance_eval {defined?(@overload) and @overload} or next 117 | tbl = tbl[func] or next 118 | sig, meth = tbl.find {|sig, meth| sig === args} 119 | meth 120 | end 121 | meth or Signature[*args].nomatch(recv, func, 3) 122 | begin 123 | recv.__send__(meth, *args, &block) 124 | rescue Exception 125 | n = -caller.size 126 | $@[n-2, 3] = nil 127 | $@[n].sub!(/\`#{func}\'\z/, "\`#{meth}\'") 128 | raise 129 | end 130 | end 131 | end 132 | 133 | class Module 134 | private 135 | 136 | def overload(func, *types) 137 | func = func.intern if func.respond_to? :intern 138 | sig = Method::Signature[*types] 139 | meth = instance_method(func) 140 | if meth.arity < 0 141 | sig << nil unless sig.varargs? 142 | elsif meth.arity > sig.length 143 | sig.concat(Array.new(meth.arity - sig.length, Object)) 144 | elsif meth.arity < sig.length 145 | raise ArgumentError, "too many arguments for #{func}(#{sig.length} for #{meth.arity})" 146 | end 147 | alias_method(meth = "#{func}#{sig}".intern, func) 148 | tbl = @overload ||= {} 149 | (tbl[func] ||= []) << [sig, meth] 150 | module_eval "def #{func}(*a, &b) Method.dispatch(self, :#{func}, a, b) end", __FILE__, __LINE__ 151 | if $VERBOSE 152 | dummy = "!" 153 | undef_method(dummy) if method_defined?(dummy) 154 | alias_method(dummy, func) 155 | undef_method(dummy) 156 | end 157 | end 158 | 159 | def unoverload(func, *types) 160 | func = func.intern if func.respond_to? :intern 161 | sig = Method::Signature[*types] 162 | remove_method("#{func}#{sig}".intern) 163 | tbl = @overload[func] and tbl.delete(sig) if @overload 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /work/overload-0.6/test_overload.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | require 'overload' 4 | 5 | =begin 6 | = Examples 7 | == Base 8 | =end 9 | class TestOverload < Test::Unit::TestCase 10 | include Method::Overload 11 | end 12 | class TestSuper < TestOverload 13 | end 14 | 15 | defined?(NoMethodError) or NoMethodError = NameError 16 | 17 | =begin 18 | (1) dispatch in overload block 19 | =end 20 | class TestOverload 21 | def do_dispatch(*args) 22 | overload(args) do |args| 23 | args.dispatch(Fixnum, String, Float) do |i, s, f| 24 | [i, s, f] 25 | end 26 | args.dispatch(/foo/, Fixnum) do |s, i| 27 | [s.upcase, i+1] 28 | end 29 | args.dispatch(String, Fixnum) do |s, i| 30 | return [s, i] 31 | end 32 | args.dispatch(String, Fixnum, ()) do |s, i, *a| 33 | return [s, i, *a] 34 | end 35 | end 36 | end 37 | end 38 | class TestSuper 39 | def do_dispatch(*args) 40 | overload(args) do |args| 41 | args.dispatch(String, String) do |f, s| 42 | [f, s] 43 | end 44 | return super 45 | end 46 | end 47 | end 48 | 49 | =begin 50 | (2) signature case 51 | =end 52 | class TestOverload 53 | def do_signature(*args) 54 | case args = Signature[*args] 55 | when Signature[Fixnum, String, Float] 56 | i, s, f = args 57 | [i, s, f] 58 | when Signature[/foo/, Fixnum] 59 | s, i = args 60 | [s.upcase, i+1] 61 | when Signature[String, Fixnum] 62 | s, i = args 63 | return s, i 64 | when Signature[String, Fixnum, ()] 65 | s, i, *a = args 66 | return s, i, *a 67 | else 68 | raise args.invalid 69 | end 70 | end 71 | end 72 | class TestSuper 73 | def do_signature(*args) 74 | case args = Signature[*args] 75 | when Signature[String, String] 76 | f, s = args 77 | [f, s] 78 | else 79 | super 80 | end 81 | end 82 | end 83 | 84 | =begin 85 | (3) separated methods 86 | =end 87 | class TestOverload 88 | def do_overload(i, s, f) 89 | assert_kind_of(TestOverload, self) 90 | [i, s, f] 91 | end 92 | overload(:do_overload, Fixnum, String, Float) 93 | 94 | def do_overload(s, i) 95 | assert_kind_of(TestOverload, self) 96 | assert_match(/foo/, s) 97 | [s.upcase, i+1] 98 | end 99 | overload(:do_overload, /foo/, Fixnum) 100 | 101 | def do_overload(s, i) 102 | assert_kind_of(TestOverload, self) 103 | return [s, i] 104 | end 105 | overload(:do_overload, String, Fixnum) 106 | 107 | def do_overload(s, i, *a) 108 | assert_kind_of(TestOverload, self) 109 | [s, i, *a] 110 | end 111 | overload(:do_overload, String, Fixnum) 112 | end 113 | class TestSuper 114 | def do_overload(f, s) 115 | assert_kind_of(TestSuper, self) 116 | [f, s] 117 | end 118 | overload(:do_overload, String, String) 119 | end 120 | 121 | 122 | # test suit 123 | class TestOverload 124 | def do_test_cvar 125 | assert_kind_of(Hash, o = self.class.instance_eval {@overload}) 126 | assert_kind_of(Array, o = o[:do_overload]) 127 | o.each do |sig, meth| 128 | assert_kind_of(Method::Signature, sig) 129 | assert_kind_of(Symbol, meth) 130 | end 131 | o 132 | end 133 | 134 | def test_cvar 135 | assert_equal(4, do_test_cvar.length) 136 | end 137 | 138 | def do_test_result(f) 139 | assert_equal([123, "hello", 123.456], __send__(f, 123, "hello", 123.456)) 140 | end 141 | def do_test_return(f) 142 | assert_equal(["hello", 123], __send__(f, "hello", 123)) 143 | end 144 | def do_test_rest(f) 145 | assert_equal(["hello", 123, "abc", "def"], __send__(f, "hello", 123, "abc", "def")) 146 | end 147 | def do_test_match(f) 148 | assert_equal(["FOO", 124], __send__(f, "foo", 123)) 149 | end 150 | def do_test_nomatch(f) 151 | e = assert_raise(TypeError) {__send__(f, "abc", "def")} 152 | assert_match(/\(\"abc\",\s*\"def\"\)\z/, e.message) 153 | end 154 | 155 | %w[dispatch overload signature].each do |t| 156 | %w[result return rest match].each do |f| 157 | eval "def test_#{t}_#{f}; do_test_#{f}(:do_#{t}); end", nil, __FILE__, __LINE__ 158 | end 159 | end 160 | %w[dispatch signature].each do |t| 161 | eval "def test_#{t}_nomatch; do_test_nomatch(:do_#{t}); end", nil, __FILE__, __LINE__ 162 | end 163 | def test_overload_nomatch 164 | e = assert_raise(NoMethodError) {do_overload("abc", "def")} 165 | assert_match(/\`do_overload\(\"abc\",\s*\"def\"\)\'/, e.message) 166 | end 167 | end 168 | 169 | class TestSuper 170 | def test_cvar 171 | assert_equal(1, do_test_cvar.length) 172 | end 173 | 174 | def do_test_super(f) 175 | assert_equal(["abc", "def"], __send__(f, "abc", "def")) 176 | end 177 | 178 | %w[dispatch overload signature].each do |t| 179 | undef_method "test_#{t}_nomatch" 180 | %w[super].each do |f| 181 | eval "def test_#{t}_#{f}; do_test_#{f}(:do_#{t}); end", nil, __FILE__, __LINE__ 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /work/deprecated/tyepcast.rb: -------------------------------------------------------------------------------- 1 | # = Typecast 2 | # 3 | # Provides a generic type conversion utility. All the ruby core 4 | # conversions are available by default. 5 | # 6 | # "1234".cast_to Float => 1234.0 (Float) 7 | # Time.cast_from("6:30") => 1234.0 (Time) 8 | # 9 | # To implement a new type conversion, you have two choices, take: 10 | # 11 | # class CustomType 12 | # def initialize(my_var) 13 | # @my_var = my_var 14 | # end 15 | # end 16 | # 17 | # Define a to_class_name instance method 18 | # 19 | # class CustomType 20 | # def to_string 21 | # my_var.to_s 22 | # end 23 | # end 24 | # 25 | # c = CustomType.new 1234 26 | # s.cast_to String => "1234" (String) 27 | # 28 | # Define a from_class_name class method 29 | # 30 | # class CustomType 31 | # def self.from_string(str) 32 | # self.new(str) 33 | # end 34 | # end 35 | # 36 | # "1234".cast_to CustomType => # 37 | # 38 | # Those two methods are equivalent in the result. It was coded like that to 39 | # avoid the pollution of core classes with tons of to_* methods. 40 | # 41 | # The standard methods to_s, to_f, to_i, to_a and to_sym are also used by 42 | # this system if available. 43 | # 44 | # == Faq 45 | # 46 | # Q. Why didn't you name the `cast_to` method to `to` ? 47 | # 48 | # A. Even if it would make the syntax more friendly, I suspect it could cause 49 | # a lot of collisions with already existing code. The goal is that each 50 | # time you call cast_to, you either get your result, or a TypeError. 51 | 52 | require 'facets/string/methodize' 53 | require 'facets/string/camelcase' #modulize' 54 | require 'set' 55 | 56 | class Class 57 | 58 | def typecasts 59 | @typecasts ||= {} 60 | end 61 | 62 | def typecast(target_class, specifics=nil) 63 | if specifics 64 | typecasts[target_class] ||= {} 65 | typecasts[target_class][Set.new(*specifics)] = self 66 | else 67 | typecasts[target_class][nil] = self 68 | end 69 | end 70 | 71 | def from(source_class, specifics={}) 72 | if cast = source_class.typecasts[self] 73 | if spec = cast[Set.new(*specifics.keys)] 74 | spec.call(specifics) 75 | else 76 | cast[nil].call(specifics) 77 | end 78 | else 79 | raise TypeError 80 | end 81 | end 82 | 83 | end 84 | 85 | 86 | class Object #:nodoc: 87 | # Cast an object to another 88 | # 89 | # 1234.cast_to(String) => "1234" 90 | # 91 | def cast_to(klass) 92 | klass.cast_from(self) 93 | end 94 | end 95 | 96 | class Class #:nodoc: 97 | # Cast on object from another. 98 | # 99 | # String.cast_from(1234) => "1234" 100 | # 101 | def cast_from(object) 102 | method_to = "to_#{self.name.methodize}".to_sym 103 | if object.respond_to? method_to 104 | retval = object.send(method_to) 105 | return retval 106 | end 107 | method_from = "from_#{object.class.name.methodize}".to_sym 108 | if respond_to? method_from 109 | retval = send(method_from, object) 110 | return retval 111 | end 112 | raise TypeError, "Type casting from #{object.class.name} to #{self.name} not supported" 113 | end 114 | 115 | # "string".cast_to Class #=> String 116 | def self.from_string(string) 117 | string = string.to_s.camelcase #modulize 118 | base = string.sub!(/^::/, '') ? Object : (self.kind_of?(Module) ? self : self.class ) 119 | klass = string.split(/::/).inject(base){ |mod, name| mod.const_get(name) } 120 | return klass if klass.kind_of? Class 121 | nil 122 | rescue 123 | nil 124 | end 125 | 126 | class << self 127 | alias_method :from_symbol, :from_string 128 | end 129 | end 130 | 131 | class Array #:nodoc: 132 | def self.cast_from(object) 133 | return super 134 | rescue TypeError 135 | return object.to_a if object.respond_to? :to_a 136 | raise 137 | end 138 | end 139 | 140 | class Float #:nodoc: 141 | def self.cast_from(object) 142 | return super 143 | rescue TypeError 144 | return object.to_f if object.respond_to? :to_f 145 | raise 146 | end 147 | end 148 | 149 | class Integer #:nodoc: 150 | def self.cast_from(object) 151 | return super 152 | rescue TypeError 153 | return object.to_i if object.respond_to? :to_i 154 | raise 155 | end 156 | end 157 | 158 | class String #:nodoc: 159 | def self.cast_from(object) 160 | return super 161 | rescue TypeError 162 | return object.to_s if object.respond_to? :to_s 163 | raise 164 | end 165 | end 166 | 167 | class Symbol #:nodoc: 168 | def self.cast_from(object) 169 | return super 170 | rescue TypeError 171 | return object.to_sym if object.respond_to? :to_sym 172 | raise 173 | end 174 | end 175 | 176 | class Time #:nodoc: 177 | # This method will require the 'time.rb' Time extensions. 178 | def self.from_string(string, options={}) 179 | require 'time' 180 | parse(string) 181 | rescue 182 | nil 183 | end 184 | end 185 | 186 | -------------------------------------------------------------------------------- /work/eutype.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | bill/type.rb 4 | 5 | $Author: transami $ 6 | $Date: 2003/12/06 13:56:57 $ 7 | 8 | Copyright (C) 2002 T. Onoma 9 | 10 | This program is free software. 11 | You can distribute/modify this program under 12 | the terms of the Ruby Distribute License. 13 | 14 | =end 15 | =begin 16 | 17 | = Description 18 | 19 | = Usage 20 | 21 | == Using Cast Attribute Accessors 22 | 23 | # simple type casting 24 | attr_reader :a => :to_s 25 | 26 | # fancy type casting 27 | attr_reader :a => 'to_s.capitalize' 28 | 29 | # a powerful reader! 30 | attr_reader :a => fn{ |x| eval x } 31 | 32 | # a writer that type checks 33 | attr_writer :a => fn{ |x| big4 x } 34 | 35 | # backwards compatible, just put cast attributes at end of list 36 | attr_accessor :x, :y, :a => :to_s 37 | 38 | == Using Types 39 | 40 | A type is defined with a name, a "duck" list of methods that the 41 | object must respond to, and an optional validation block 42 | 43 | type :typename, [:methods, :respond_to?, ...] { |x| validation } 44 | 45 | type :big4, [:to_str, :succ] do |x| 46 | x > 4 47 | end 48 | 49 | def ameth(x) 50 | big4 x 51 | puts x 52 | end 53 | 54 | typecheck = true 55 | 56 | t.ameth(5) # => 5 57 | t.ameth(2) # TypeError 58 | 59 | === A More Elegant Future? 60 | 61 | type big4(x) 62 | :to_str, :succ 63 | unless 64 | x > 4 65 | end 66 | 67 | def ameth(big4 x) 68 | puts x 69 | end 70 | 71 | # IDEA 72 | # 73 | # def test(a,b) 74 | # check a=>String, b=>Integer 75 | # end 76 | 77 | =end 78 | 79 | $typecheck = true # typechecking defualts to true 80 | 81 | T = true 82 | F = false 83 | 84 | # One things that we do is be sure to add 85 | # self referential to_{type} methods to class' 86 | # of that type. Thus String should have a to_s method 87 | # and to forth. 88 | 89 | 90 | class Type 91 | class TypeRepositoryError < TypeError 92 | end 93 | class TypeSignitureError < TypeError 94 | end 95 | class TypeValidationError < TypeError 96 | end 97 | #def Type.repository 98 | @@repository = {} 99 | #end 100 | def Type.add(t) 101 | raise TypeRepositoryError if !t.kind_of?(Type) 102 | @@repository[t.name] = t 103 | end 104 | def Type.[](name) 105 | @@repository[name] 106 | end 107 | attr_reader :name, :responders, :validator 108 | def initialize(name, responders, &validator) 109 | @name = name 110 | @responders = responders 111 | @validator = validator 112 | Type.add(self) 113 | end 114 | def ensign?(obj) 115 | @responders.all? { |r| @r = r; obj.send(:respond_to?, r) } 116 | end 117 | def ensigniate(obj) 118 | unless ensign?(obj) 119 | raise TypeSignitureError, "#{self.name} #{obj} (#{@r})" 120 | end 121 | end 122 | def valid?(obj) 123 | @validator ? @validator.call(obj) : true 124 | end 125 | def validate(obj) 126 | unless valid?(obj) 127 | raise TypeValidationError, "#{self.name} #{obj}" 128 | end 129 | end 130 | def check?(obj) 131 | ensign?(obj) && valid?(obj) 132 | end 133 | def check(obj) 134 | ensigniate(obj) 135 | validate(obj) 136 | end 137 | end 138 | 139 | # **REMOVE** **ALL?** 140 | # NilClass, unlike other classes, is a representation 141 | # of "nothingness", so #to_{t} returns the empty 142 | # form of that type, with the exception of to_b 143 | # which returns false. The standard NilClass already 144 | # does this for some of the fundemtal classes, but 145 | # has left a few out which we add here. 146 | # TODO: Add a switch for the other methods (?) 147 | #class NilClass 148 | # def to_b; false; end 149 | # def to_f; 0.0; end 150 | # def to_h; {}; end 151 | # are these a good idea (some but not all?) 152 | # def empty?; true; end 153 | # def include?(*args); return nil; end 154 | # def [](arg); return nil; end 155 | # alias size to_i 156 | # alias length to_i 157 | #end 158 | 159 | # Add to_b which returns true 160 | #class TrueClass 161 | # def to_b; true; end 162 | #end 163 | 164 | # Add to_b which returns false 165 | #class FalseClass 166 | # def to_b; false; end 167 | #end 168 | 169 | #class Object 170 | # def to_b 171 | # self ? true : false 172 | # end 173 | #end 174 | 175 | class Symbol 176 | def intern 177 | self 178 | end 179 | end 180 | 181 | # The kernal method typecheck globally turns type checking on or off 182 | # TODO: Perhpas this should only apply to the current scopes? Possible? 183 | #module Kernel 184 | # alias fn lambda 185 | # def typecheck(x=true) 186 | # $_tc = (x ? true : false) 187 | # end 188 | # def typecheck=(x) 189 | # $_tc = (x ? true : false) 190 | # end 191 | # def typecheck? 192 | # return $_tc 193 | # end 194 | #end 195 | 196 | # Add the type method for defining new types 197 | # Syntax: 198 | # type :name, [:duck_method, ...] { |x| validation code on x } 199 | class Module 200 | def type(name, responders, &validator) 201 | Type.new(name, responders, &validator) 202 | define_method(name) { |*vars| 203 | if $typecheck 204 | vars.all? { |v| Type[name].check(v) } 205 | return *vars 206 | end 207 | } 208 | end 209 | end 210 | 211 | #class Type 212 | # @@conversion_registry = {} 213 | # def self.define_conversion(from_class, to_type, &caster) 214 | # @@conversion_registry[to_type] ||= {} 215 | # @@conversion_registry[to_type][from_type] = &caster 216 | # end 217 | # def caster(to_type) 218 | # @@conversion_registry[to_type] 219 | # end 220 | #end 221 | 222 | class Object 223 | def self.conversion(to_type) 224 | @conversion_registry[to_type] 225 | end 226 | def self.define_conversion(to_type, &block) 227 | @conversion_registry[to_type] 228 | end 229 | def to(to_type, *args) 230 | @conversion_registry[to_type].call(*args) 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /work/deprecated/type.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | bill/type.rb 4 | 5 | $Author: transami $ 6 | $Date: 2003/12/06 13:56:57 $ 7 | 8 | Copyright (C) 2002 T. Onoma 9 | 10 | This program is free software. 11 | You can distribute/modify this program under 12 | the terms of the Ruby Distribute License. 13 | 14 | =end 15 | =begin 16 | 17 | = Description 18 | 19 | = Usage 20 | 21 | == Using Cast Attribute Accessors 22 | 23 | # simple type casting 24 | attr_reader :a => :to_s 25 | 26 | # fancy type casting 27 | attr_reader :a => 'to_s.capitalize' 28 | 29 | # a powerful reader! 30 | attr_reader :a => fn{ |x| eval x } 31 | 32 | # a writer that type checks 33 | attr_writer :a => fn{ |x| big4 x } 34 | 35 | # backwards compatible, just put cast attributes at end of list 36 | attr_accessor :x, :y, :a => :to_s 37 | 38 | == Using Types 39 | 40 | A type is defined with a name, a "duck" list of methods that the 41 | object must respond to, and an optional validation block 42 | 43 | type :typename, [:methods, :respond_to?, ...] { |x| validation } 44 | 45 | type :big4, [:to_str, :succ] do |x| 46 | x > 4 47 | end 48 | 49 | def ameth(x) 50 | big4 x 51 | puts x 52 | end 53 | 54 | typecheck = true 55 | 56 | t.ameth(5) # => 5 57 | t.ameth(2) # TypeError 58 | 59 | === A More Elegant Future? 60 | 61 | type big4(x) 62 | :to_str, :succ 63 | unless 64 | x > 4 65 | end 66 | 67 | def ameth(big4 x) 68 | puts x 69 | end 70 | 71 | # IDEA 72 | # 73 | # def test(a,b) 74 | # check a=>String, b=>Integer 75 | # end 76 | 77 | =end 78 | 79 | $typecheck = true # typechecking defualts to true 80 | 81 | T = true 82 | F = false 83 | 84 | # One things that we do is be sure to add 85 | # self referential to_{type} methods to class' 86 | # of that type. Thus String should have a to_s method 87 | # and to forth. 88 | 89 | 90 | class Type 91 | class TypeRepositoryError < TypeError 92 | end 93 | class TypeSignitureError < TypeError 94 | end 95 | class TypeValidationError < TypeError 96 | end 97 | #def Type.repository 98 | @@repository = {} 99 | #end 100 | def Type.add(t) 101 | raise TypeRepositoryError if !t.kind_of?(Type) 102 | @@repository[t.name] = t 103 | end 104 | def Type.[](name) 105 | @@repository[name] 106 | end 107 | attr_reader :name, :responders, :validator 108 | def initialize(name, responders, &validator) 109 | @name = name 110 | @responders = responders 111 | @validator = validator 112 | Type.add(self) 113 | end 114 | def ensign?(obj) 115 | @responders.all? { |r| @r = r; obj.send(:respond_to?, r) } 116 | end 117 | def ensigniate(obj) 118 | unless ensign?(obj) 119 | raise TypeSignitureError, "#{self.name} #{obj} (#{@r})" 120 | end 121 | end 122 | def valid?(obj) 123 | @validator ? @validator.call(obj) : true 124 | end 125 | def validate(obj) 126 | unless valid?(obj) 127 | raise TypeValidationError, "#{self.name} #{obj}" 128 | end 129 | end 130 | def check?(obj) 131 | ensign?(obj) && valid?(obj) 132 | end 133 | def check(obj) 134 | ensigniate(obj) 135 | validate(obj) 136 | end 137 | end 138 | 139 | # **REMOVE** **ALL?** 140 | # NilClass, unlike other classes, is a representation 141 | # of "nothingness", so #to_{t} returns the empty 142 | # form of that type, with the exception of to_b 143 | # which returns false. The standard NilClass already 144 | # does this for some of the fundemtal classes, but 145 | # has left a few out which we add here. 146 | # TODO: Add a switch for the other methods (?) 147 | #class NilClass 148 | # def to_b; false; end 149 | # def to_f; 0.0; end 150 | # def to_h; {}; end 151 | # are these a good idea (some but not all?) 152 | # def empty?; true; end 153 | # def include?(*args); return nil; end 154 | # def [](arg); return nil; end 155 | # alias size to_i 156 | # alias length to_i 157 | #end 158 | 159 | # Add to_b which returns true 160 | #class TrueClass 161 | # def to_b; true; end 162 | #end 163 | 164 | # Add to_b which returns false 165 | #class FalseClass 166 | # def to_b; false; end 167 | #end 168 | 169 | #class Object 170 | # def to_b 171 | # self ? true : false 172 | # end 173 | #end 174 | 175 | class Symbol 176 | def intern 177 | self 178 | end 179 | end 180 | 181 | # The kernal method typecheck globally turns type checking on or off 182 | # TODO: Perhpas this should only apply to the current scopes? Possible? 183 | #module Kernel 184 | # alias fn lambda 185 | # def typecheck(x=true) 186 | # $_tc = (x ? true : false) 187 | # end 188 | # def typecheck=(x) 189 | # $_tc = (x ? true : false) 190 | # end 191 | # def typecheck? 192 | # return $_tc 193 | # end 194 | #end 195 | 196 | # Add the type method for defining new types 197 | # Syntax: 198 | # type :name, [:duck_method, ...] { |x| validation code on x } 199 | class Module 200 | def type(name, responders, &validator) 201 | Type.new(name, responders, &validator) 202 | define_method(name) { |*vars| 203 | if $typecheck 204 | vars.all? { |v| Type[name].check(v) } 205 | return *vars 206 | end 207 | } 208 | end 209 | end 210 | 211 | #class Type 212 | # @@conversion_registry = {} 213 | # def self.define_conversion(from_class, to_type, &caster) 214 | # @@conversion_registry[to_type] ||= {} 215 | # @@conversion_registry[to_type][from_type] = &caster 216 | # end 217 | # def caster(to_type) 218 | # @@conversion_registry[to_type] 219 | # end 220 | #end 221 | 222 | class Object 223 | def self.conversion(to_type) 224 | @conversion_registry[to_type] 225 | end 226 | def self.define_conversion(to_type, &block) 227 | @conversion_registry[to_type] 228 | end 229 | def to(to_type, *args) 230 | @conversion_registry[to_type].call(*args) 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /work/multidispatch0.rb: -------------------------------------------------------------------------------- 1 | require 'facets/core/kernel/instance_exec' 2 | 3 | module MultipleDispatch 4 | 5 | class Dispatcher 6 | 7 | def initialize(name, lists) 8 | @name = name 9 | @lists = lists 10 | end 11 | 12 | def run(you, args) 13 | @lists.each do |list| 14 | catch(:mismatch) do 15 | return list.match(you, args) 16 | end 17 | end 18 | begin 19 | raise NoMethodError.new("undefined method `#{@name}' for #{you.inspect}:#{you.class}") 20 | rescue NoMethodError => e 21 | e.set_backtrace(e.backtrace[2..-1]) 22 | raise e 23 | end 24 | end 25 | 26 | end 27 | 28 | class ArgumentList 29 | 30 | def initialize(required, optional, defaults, catch_all, catch_all_type, &action) 31 | @required = required 32 | @optional = optional 33 | @defaults = defaults 34 | @catch_all = catch_all 35 | @catch_all_type = catch_all_type 36 | @action = action 37 | calc_arity 38 | end 39 | 40 | def calc_arity 41 | @min_arity = @required.length 42 | @max_arity = (@min_arity + @optional.length unless @catch_all) 43 | end 44 | 45 | def match(you, args) 46 | throw :mismatch if args.length < @min_arity 47 | throw :mismatch if @max_arity && @max_arity < args.length 48 | (0...@required.length).each do |i| 49 | throw :mismatch unless @required[i] === args[i] 50 | end 51 | (@required.length...[args.length, @required.length + @optional.length].min).each do |i| 52 | throw :mismatch unless @optional[i - @required.length] === args[i] 53 | end 54 | (args.length - @required.length...@optional.length).each do |i| 55 | args << @defaults[i] 56 | end 57 | if @catch_all 58 | (@required.length + @optional.length...args.length).each do |i| 59 | throw :mismatch unless @catch_all_type === args[i] 60 | end 61 | you.instance_exec(*(args[0, @required.length + @optional.length] << args[@required.length + @optional.length..-1]), &@action) 62 | else 63 | you.instance_exec(*args, &@action) 64 | end 65 | end 66 | 67 | end 68 | 69 | module ArgumentChanger 70 | 71 | end 72 | 73 | class PropertyDispatch 74 | 75 | def initialize(&blk) 76 | @blk = blk 77 | end 78 | 79 | def ===(arg) 80 | @blk.call(arg) 81 | end 82 | 83 | end 84 | 85 | class CastDispatch 86 | 87 | include ArgumentChanger 88 | 89 | def initialize(type) 90 | @type = :"to_#{type}" 91 | end 92 | 93 | def ===(arg) 94 | arg.send(@type) rescue nil 95 | end 96 | 97 | def changes_argument? 98 | true 99 | end 100 | 101 | end 102 | 103 | class AndDispatch 104 | 105 | def initialize(subterms) 106 | @subterms = subterms 107 | end 108 | 109 | def ===(arg) 110 | @subterms.each do |t| 111 | return false unless t === arg 112 | end 113 | true 114 | end 115 | 116 | end 117 | 118 | class OrDispatch 119 | 120 | def initialize(subterms) 121 | @subterms = subterms 122 | end 123 | 124 | def ===(arg) 125 | @subterms.each do |t| 126 | return true if t === arg 127 | end 128 | false 129 | end 130 | 131 | end 132 | 133 | class DontCareDispatch 134 | 135 | def initialize 136 | 137 | end 138 | 139 | def ===(arg) 140 | true 141 | end 142 | 143 | end 144 | 145 | end 146 | 147 | if __FILE__ == $0 148 | 149 | include MultipleDispatch 150 | 151 | class A 152 | 153 | def m(*args, &blk) 154 | # Argument list 1 155 | arg1 = ArgumentList.new([String, String], [], [], false, nil) do |s1, s2| 156 | yield [:two_strings, s1, s2] 157 | end 158 | # Argument list 2 159 | arg2 = ArgumentList.new([String, Integer], [], [], false, nil) do |s, i| 160 | yield [:string_and_integer, s, i] 161 | end 162 | # Argument list 3 163 | arg3 = ArgumentList.new([Integer, Integer, Integer], [], [], false, nil) do |i1, i2, i3| 164 | yield [:three_integers, i1, i2, i3] 165 | end 166 | # Argument list 4 167 | arg4 = ArgumentList.new([:do1, Integer], [], [], false, nil) do |_, i| 168 | yield [:do1, i] 169 | end 170 | # Argument list 5 171 | arg5 = ArgumentList.new([:do2, Integer], [], [], false, nil) do |_, i| 172 | yield [:do2, i] 173 | end 174 | # Argument list 6 175 | arg6 = ArgumentList.new([:do3, Integer], [Integer, String], [11, "extra"], false, nil) do |_, i1, i2, s| 176 | yield [:do3, i1, i2, s] 177 | end 178 | # Argument list 7 179 | arg7 = ArgumentList.new([:do4], [], [], true, Integer) do |_, args| 180 | yield [:do4, args] 181 | end 182 | # Argument list 8 183 | arg8 = ArgumentList.new([:do4], [], [], true, String) do |_, args| 184 | yield [:do4, args] 185 | end 186 | # Argument list 9 187 | arg9 = ArgumentList.new([PropertyDispatch.new { |a| :do5 === a }], [], [], false, nil) do |_| 188 | yield [:do5] 189 | end 190 | # Argument list 10 191 | arg10 = ArgumentList.new([/run/, DontCareDispatch.new], [], [], false, nil) do |i, dc| 192 | yield [:run, i, dc] 193 | end 194 | # Argument list 11 195 | arg11 = ArgumentList.new([:do6, CastDispatch.new(:sym)], [], [], false, nil) do |_, s| 196 | yield [:do6, s] 197 | end 198 | 199 | # Dispatcher 200 | d = Dispatcher.new(:'test', [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]) 201 | 202 | d.run(self, args) 203 | end 204 | 205 | end 206 | 207 | A.new.m("hello", "world") { |a| p a } 208 | A.new.m("world", 1) { |a| p a } 209 | A.new.m(1, 2, 3) { |a| p a } 210 | A.new.m(:do1, 1) { |a| p a } 211 | A.new.m(:do2, 2) { |a| p a } 212 | A.new.m(:do3, 3, 4, "a1") { |a| p a } 213 | A.new.m(:do3, 3, 4) { |a| p a } 214 | A.new.m(:do3, 3) { |a| p a } 215 | A.new.m(:do4) { |a| p a } 216 | A.new.m(:do4, 2) { |a| p a } 217 | A.new.m(:do4, 2, 3) { |a| p a } 218 | A.new.m(:do4, "2", "3") { |a| p a } 219 | A.new.m(:do5) { |a| p a } 220 | A.new.m("grunt", 1) { |a| p a } 221 | A.new.m("grunt", :a) { |a| p a } 222 | A.new.m(:do6, "hi") { |a| p a } 223 | 224 | end 225 | -------------------------------------------------------------------------------- /work/multidispatch.rb: -------------------------------------------------------------------------------- 1 | require 'facets/core/kernel/instance_exec' 2 | 3 | module MultipleDispatch 4 | 5 | class Dispatcher 6 | 7 | def initialize(name, lists) 8 | @name = name 9 | @lists = lists 10 | end 11 | 12 | def run(you, args) 13 | @lists.each do |list| 14 | catch(:mismatch) do 15 | return list.match(you, args) 16 | end 17 | end 18 | begin 19 | raise NoMethodError.new("undefined method `#{@name}' for #{you.inspect}:#{you.class}") 20 | rescue NoMethodError => e 21 | e.set_backtrace(e.backtrace[2..-1]) 22 | raise e 23 | end 24 | end 25 | 26 | end 27 | 28 | class ArgumentList 29 | 30 | def initialize(required, optional, defaults, catch_all, catch_all_type, &action) 31 | @required = required 32 | @optional = optional 33 | @defaults = defaults 34 | @catch_all = catch_all 35 | @catch_all_type = catch_all_type 36 | @action = action 37 | calc_arity 38 | end 39 | 40 | def calc_arity 41 | @min_arity = @required.length 42 | @max_arity = (@min_arity + @optional.length unless @catch_all) 43 | end 44 | 45 | def match(you, args) 46 | throw :mismatch if args.length < @min_arity 47 | throw :mismatch if @max_arity && @max_arity < args.length 48 | (0...@required.length).each do |i| 49 | if ArgumentChanger === @required[i] 50 | args[i] = @required[i] === args[i] 51 | else 52 | throw :mismatch unless @required[i] === args[i] 53 | end 54 | end 55 | (@required.length...[args.length, @required.length + @optional.length].min).each do |i| 56 | if ArgumentChanger === @optional[i - @required.length] 57 | args[i] = @optional[i - @required.length] === args[i] 58 | else 59 | throw :mismatch unless @optional[i - @required.length] === args[i] 60 | end 61 | end 62 | (args.length - @required.length...@optional.length).each do |i| 63 | args << @defaults[i] 64 | end 65 | if @catch_all 66 | (@required.length + @optional.length...args.length).each do |i| 67 | if ArgumentChanger === @catch_all_type 68 | args[i] = @catch_all_type === args[i] 69 | else 70 | throw :mismatch unless @catch_all_type === args[i] 71 | end 72 | end 73 | you.instance_exec(*(args[0, @required.length + @optional.length] << args[@required.length + @optional.length..-1]), &@action) 74 | else 75 | you.instance_exec(*args, &@action) 76 | end 77 | end 78 | 79 | end 80 | 81 | module ArgumentChanger 82 | 83 | end 84 | 85 | class PropertyDispatch 86 | 87 | def initialize(&blk) 88 | @blk = blk 89 | end 90 | 91 | def ===(arg) 92 | @blk.call(arg) 93 | end 94 | 95 | end 96 | 97 | class CastDispatch 98 | 99 | include ArgumentChanger 100 | 101 | def initialize(type) 102 | @type = :"to_#{type}" 103 | end 104 | 105 | def ===(arg) 106 | arg.send(@type) rescue throw :mismatch 107 | end 108 | 109 | def changes_argument? 110 | true 111 | end 112 | 113 | end 114 | 115 | class AndDispatch 116 | 117 | def initialize(subterms) 118 | @subterms = subterms 119 | end 120 | 121 | def ===(arg) 122 | @subterms.each do |t| 123 | return false unless t === arg 124 | end 125 | true 126 | end 127 | 128 | end 129 | 130 | class OrDispatch 131 | 132 | def initialize(subterms) 133 | @subterms = subterms 134 | end 135 | 136 | def ===(arg) 137 | @subterms.each do |t| 138 | return true if t === arg 139 | end 140 | false 141 | end 142 | 143 | end 144 | 145 | class DontCareDispatch 146 | 147 | def initialize 148 | 149 | end 150 | 151 | def ===(arg) 152 | true 153 | end 154 | 155 | end 156 | 157 | end 158 | 159 | if __FILE__ == $0 160 | 161 | include MultipleDispatch 162 | 163 | class A 164 | 165 | def m(*args, &blk) 166 | # Argument list 1 167 | arg1 = ArgumentList.new([String, String], [], [], false, nil) do |s1, s2| 168 | yield [:two_strings, s1, s2] 169 | end 170 | # Argument list 2 171 | arg2 = ArgumentList.new([String, Integer], [], [], false, nil) do |s, i| 172 | yield [:string_and_integer, s, i] 173 | end 174 | # Argument list 3 175 | arg3 = ArgumentList.new([Integer, Integer, Integer], [], [], false, nil) do |i1, i2, i3| 176 | yield [:three_integers, i1, i2, i3] 177 | end 178 | # Argument list 4 179 | arg4 = ArgumentList.new([:do1, Integer], [], [], false, nil) do |_, i| 180 | yield [:do1, i] 181 | end 182 | # Argument list 5 183 | arg5 = ArgumentList.new([:do2, Integer], [], [], false, nil) do |_, i| 184 | yield [:do2, i] 185 | end 186 | # Argument list 6 187 | arg6 = ArgumentList.new([:do3, Integer], [Integer, String], [11, "extra"], false, nil) do |_, i1, i2, s| 188 | yield [:do3, i1, i2, s] 189 | end 190 | # Argument list 7 191 | arg7 = ArgumentList.new([:do4], [], [], true, Integer) do |_, args| 192 | yield [:do4, args] 193 | end 194 | # Argument list 8 195 | arg8 = ArgumentList.new([:do4], [], [], true, String) do |_, args| 196 | yield [:do4, args] 197 | end 198 | # Argument list 9 199 | arg9 = ArgumentList.new([PropertyDispatch.new { |a| :do5 === a }], [], [], false, nil) do |_| 200 | yield [:do5] 201 | end 202 | # Argument list 10 203 | arg10 = ArgumentList.new([/run/, DontCareDispatch.new], [], [], false, nil) do |i, dc| 204 | yield [:run, i, dc] 205 | end 206 | # Argument list 11 207 | arg11 = ArgumentList.new([:do6, CastDispatch.new(:sym)], [], [], false, nil) do |_, s| 208 | yield [:do6, s] 209 | end 210 | 211 | # Dispatcher 212 | d = Dispatcher.new(:'test', [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]) 213 | 214 | d.run(self, args) 215 | end 216 | 217 | end 218 | 219 | A.new.m("hello", "world") { |a| p a } 220 | A.new.m("world", 1) { |a| p a } 221 | A.new.m(1, 2, 3) { |a| p a } 222 | A.new.m(:do1, 1) { |a| p a } 223 | A.new.m(:do2, 2) { |a| p a } 224 | A.new.m(:do3, 3, 4, "a1") { |a| p a } 225 | A.new.m(:do3, 3, 4) { |a| p a } 226 | A.new.m(:do3, 3) { |a| p a } 227 | A.new.m(:do4) { |a| p a } 228 | A.new.m(:do4, 2) { |a| p a } 229 | A.new.m(:do4, 2, 3) { |a| p a } 230 | A.new.m(:do4, "2", "3") { |a| p a } 231 | A.new.m(:do5) { |a| p a } 232 | A.new.m("grunt", 1) { |a| p a } 233 | A.new.m("grunt", :a) { |a| p a } 234 | A.new.m(:do6, "hi") { |a| p a } 235 | 236 | end 237 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec --------------------------------------------------------------------------------