<commit_before>
require 'ludy/kernel/public_send'

module Ludy
  def defun name, *args, &fun
    p self
    Dispatcher.create self, name, *args, &fun
  end
  class ArgList
    def initialize *args
      @arg_list = args
    end
    def hash
      @arg_list.inject(0){ |r, i|
        r + (i.kind_of?(Class) ?
          i.object_id : i.class.object_id)
      }
    end
    def eql? rhs
      self.size == rhs.size &&
      self.hash == rhs.hash
    end
    alias_method :==, :eql?
    def method_missing msg, *args, &block
      @arg_list.public_send msg, *args, &block
    end
  end
  class Dispatcher
    def self.create actor, msg, *args, &fun
      if (@patchers ||= {})[[actor, msg]].nil?
        @patchers[[actor, msg]] = Dispatcher.new actor, msg, *args, &fun
      else
        @patchers[[actor, msg]].push ArgList.new(*args), &fun
      end
    end
    def initialize actor, msg, *args, &fun
      @actor = actor
      @msg = msg
      @map = {}
      self.push ArgList.new(*args), &fun
      @actor.instance_variable_set :@ludy_dispatcher_injection
    end
    def push arg_list, &fun
      @map[arg_list] = fun
      unless @actor.respond_to? @msg
        @actor.define_singleton_method @msg do |*args, &block|
          # cannot see @map...
          fun = @map[ArgList.new(*args)]
          if fun.nil?
            @actor.method_missing @msg, *args, &block
          else
            fun.call(*args, &block)
          end
        end
      end
    end
  end
end

# defun :fact, 0 do |n|
#   1
# end
# 
# defun :fact, Integer do |n|
#   n * fact(n-1)
# end<commit_msg> r858@godfat-mac-ver:  godfat | 2008-01-08 14:59:28 +0800  commit the chagnes so that i can move it to ludy/kernel/defun.rb,  and extract MessageDispatcher and PatternMatcher<commit_after>
require 'ludy/kernel/public_send'

module Ludy
  # you can use defun for overloading(ad-hoc polymorphism),
  # multi-method, pattern matching, and perhaps others?
  def defun name, *args, &fun
    MessageDispatcher.create self, name, *args, &fun
  end
  # someone who does the pattern matching thing
  class PatternMatcher
    # init for the first arg_list
    def initialize *args; @arg_list = [args]; end
    # find the first match of this arg_list. notice,
    # this is not the best match. maybe someday i could
    # implement the match policy accordingly or selectable.
    def first_match *args
      @arg_list.find{ |arg_list, fun|
        true
      }.last
    end
    # delegate all the rest message to @arg_list array
    def method_missing msg, *args, &block
      if @arg_list.respond_to? msg
        @arg_list.public_send msg, *args, &block
      else
        raise NoMethodError.new("PatternMatcher doesn't respond to #{msg}")
      end
    end
  end
  class MessageDispatcher
    # create a dispatcher or append arg_list to the existed dispatcher
    def self.create actor, msg, *args, &fun
      if (@patchers ||= {})[[actor, msg]].nil?
        @patchers[[actor, msg]] = MessageDispatcher.new actor, msg, *args, &fun
      else
        @patchers[[actor, msg]].listen *args, &fun
      end
    end
    # actor is the listener who listen to the message
    def initialize actor, msg, *args, &fun
      @actor = actor
      @msg = msg
      @matcher = PatternMatcher.new *args, &fun
      define_method_in_actor
    end
    # open your ear and listen, and call me when you hear
    def listen arg_list, &fun
      @matcher << [arg_list, fun]
    end
    def define_method_in_actor
      # if @actor.respond_to? @msg
        # raise RuntimeError.new("#{@actor.inspect}'s method \"#{@msg}\" already defined.")
      # else
        matcher = @matcher
        msg = @msg # make them close in the closure

        @actor.define_singleton_method msg do |*args, &block|
          fun = matcher.first_match *args
          if fun.nil?
            if self.respond_to? :method_missing
              self.method_missing msg, *args, &block
            else
              raise NoMethodError.new("#{@actor.inspect} doesn't respond to \"#{msg}\"")
            end
          else
            fun.call(*args, &block)
          end
        end
      # end
    end
  end
end

# defun :fact, 0 do |n|
#   1
# end
# 
# defun :fact, Integer do |n|
#   n * fact(n-1)
# end
{'fraction_non_alphanumeric': 0.10895769321673364, 'fraction_numerical': 0.006617820846135665, 'mean_word_length': 2.283165244375485, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6492887', 'n_tokens_mistral': 1401, 'n_tokens_neox': 1368, 'n_words': 434}