class Ematcher < BlankSlate
class Placeholder
attr :name

def initialize(name); @name = name; end
end

module AnyMany
class Any; end
class Many; end

def _; Any; end
def __; Many; end
end

extend AnyMany
include AnyMany

def method_missing(sym, *args)
case args.length
when 0 then
case @mode
when :build then Placeholder.new(sym)
when :match then super
when :yield then match(sym)
else super
end
when 1 then
return super unless @mode == :yield && ((name = sym.to_s).last == "=")
name = name.chop.to_sym
return super unless @match.key?(name)
@match[name] = args[0]
else
super
end
end

private

def match(sym)
raise ArgumentError, "No match #{sym}" unless @match.key?(sym)
@match[sym]
end

def in_mode(mode, &block)
begin
@mode = mode
yield
ensure
@mode = :build
end
end

public

def on(pattern, &block)
return if @match

@match = in_mode(:match) do Ematcher.match(pattern, input) end
in_mode(:yield, &block) if @match
end

attr :input

def self.match(pattern, input, env = {})
return env if pattern == __
return env if pattern == _

if pattern.is_a?(Placeholder)
return nil if env.key?(pattern.name) && env[pattern.name] != input
return env.update(pattern.name => input)
end

if input.is_a?(Array) && pattern.is_a?(Array)
if pattern.length != input.length
return nil if pattern.length < input.length
return nil if pattern.length > input.length+1
return nil if pattern.last != __
end

return pattern.zip(input).inject(env) do |env, (p, i)|
env && match(p, i, env)
end
end

input == pattern && env
end

def initialize(input)
@input = input.dup.freeze
end

def matching(&block)
@mode = :build
Proc.new.bind(self).call
@match
end
end

def matching!(input, &block)
matching(input, &block) || raise(ArgumentError, "No match for #{input.inspect}")
end

def matching(input, &block)
Ematcher.new(input).matching(&block)
end

m = matching! [ 1, [ :a, 2 ], 3 ] do
on [ a ] do
puts [ "1", a ].inspect
end
on [ a, _, a ] do
puts [ "2", a ].inspect
end
on [ a, [ :a, 3 ], z ] do
puts [ "3", a, z ].inspect
end
on [ a, [ b, 2 ], z ] do
puts [ "4", a, b, z ].inspect
self.z = :z
end
on [ a, [ :a, 2 ] , z ] do
puts [ "5", a, z ].inspect
end
end

puts m.inspect