module PatternMatching # Domain Specific Language style methods for inside block module DSL_INSIDE #A pattern matches description inside block #=== Usage # seems as {some pattern...} do ... end def as(&block) block end #A pattern restriction, must boolean #=== Usage # seems as {PATTERN}, with {CONDITION} do ... end def with(&block) block end #A pattern matches anything #=== Usage # seems something do ... end def something proc {_} end end include DSL_INSIDE module DSL_MODULE #Define method as partial style #=== Usage # func(NAME).seems as {PATTERN_1} do ACTION_1 end # func(NAME).seems as {PATTERN_2} do ACTION_2 end # func(NAME).seems something do ACTION_DEFAULT end # #or # # func NAME do # seems as {PATTERN_1} do ACTION_1 end # seems as {PATTERN_2} do ACTION_2 end # seems something do ACTION_DEFAULT end # end # def func(name, &block) pattern_name = ("@_pattern_" + name.to_s ).to_sym unless method_defined?(name) patterns = [] instance_variable_set(pattern_name, patterns) define_method(name) do |target| MatchExec.exec_as(target, patterns, self) end end patterns = instance_variable_get(pattern_name) fragments = PatternFragments.new(patterns) if block fragments.instance_eval(&block) end fragments end end include DSL_MODULE # Domain Specific Language style methods for outside module DSL_OUTSIDE #Build structured data #=== Usage # build {[foo(bar, 100), foo(buzz, "abc")]} def build(&block) NodeBuilder.new.instance_eval(&block) end #Do pattern matching #===Usage # make TARGET do # seems as {PATTERN_1} do ACTION_1 end # seems as {PATTERN_2} do ACTION_2 end # seems something do ACTION_DEFAULT end # end def make(target, &block) patterns = [] PatternFragments.new(patterns).instance_eval(&block) MatchExec.exec_as(target, patterns, self) end end include DSL_OUTSIDE extend DSL_OUTSIDE #Class for structured data/patterns class Node def initialize(name, children) @name = name @children = children end attr :name def [](index) @children[index] end def size @children.size end end private #Private Exception for stop matching class NotMatched < Exception end #Private class for build structured data class NodeBuilder private def method_missing(name, *args) Node.new(name, args) end end #Private module for pattern matching and to collect var/val pairs module Collector def self.walk(source, target, list) case source when Symbol list[source] = target when Node walk_node(source, target, list) when Hash walk_hash(source, target, list) when Enumerable walk_enumerable(source, target, list) else raise NotMatched.new unless source === target end end def self.walk_node(source, target, list) return if source.name == :_ and source.size == 0 begin if source.size != target.size raise NotMatched.new end if source.name != :_ and source.name != target.name raise NotMatched.new end rescue raise NotMatched.new end source.size.times do |i| walk(source[i], target[i], list) end end def self.walk_enumerable(source, target, list) if source.size == 0 and not (target == nil or target.size == 0) raise NotMatched.new end source.size.times do |i| svalue = source[i] if i + 1 == source.size if svalue.class == Node and svalue.name == :_! and svalue.size == 1 tvalue = target[i .. -1] tvalue = if tvalue == nil then [] else tvalue end walk(svalue[0], tvalue, list) else raise NotMatched.new if source.size != target.size walk(svalue, target[i], list) end else raise NotMatched.new if target.size <= i walk(svalue, target[i], list) end end end def self.walk_hash(source, target, list) begin case target when Node raise NotMatched.new when Hash source.each do |key, svalue| walk(svalue, target[key], list) end else source.each do |key, svalue| walk(svalue, target.send(key), list) end end rescue raise NotMatched.new end end end #Private class to run pattern matching class MatchExec def self.exec_as(target, patterns, receiver) patterns.each do |tuple| pattern = tuple[0] action = tuple[1] condition = tuple[2] source = NodeBuilder.new.instance_eval(&pattern) args = {} begin Collector.walk(source, target, args) rescue NotMatched next end executer = ExecuteAs.new(args, receiver) next if condition != nil and not executer.instance_eval(&condition) return executer.instance_eval(&action) end nil end # Private class to access instance valiables of the receiver class InstanceVariableAccessor def initialize(receiver) @receiver = receiver end private def method_missing(name, *args) begin @receiver.send(name, *args) rescue NameError if name.to_s[-1,1] == "=" field = "@" + name.to_s[0...-1] @receiver.instance_variable_set(field, args[0]) else field = "@" + name.to_s @receiver.instance_variable_get(field, args[0]) end end end end #Private class enabling to use the name of symbols in patterns #like a local variables in action blocks class ExecuteAs def initialize(args, receiver) @receiver = receiver @wrapper = InstanceVariableAccessor.new receiver @args = args end def this @receiver end def the @wrapper end private def method_missing(name, *args) return @args[name] if @args.key?(name) @receiver.send(name, *args) end end end #Private class for collecting pattern/action fragments class PatternFragments include DSL_INSIDE def initialize(patterns) @patterns = patterns end def seems(pattern, condition = nil, &action) @patterns << [pattern, action, condition] self end end end