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