class RGen::Util::PatternMatcher
A PatternMatcher can be used to find, insert and remove patterns on a given model.
A pattern is specified by means of a block passed to the #add_pattern method. The block must take an Environment as first parameter and at least one model element as connection point as further parameter. The pattern matches if it can be found in a given environment and connected to the given connection point elements.
Constants
- CheckLater
- Match
- TempEnv
Attributes
debug[RW]
Public Class Methods
new()
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 17 def initialize @patterns = {} @insert_mode = false @debug = false end
Public Instance Methods
add_pattern(name, &block)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 23 def add_pattern(name, &block) raise "a pattern needs at least 2 block parameters: " + "an RGen environment and a model element as connection point" \ unless block.arity >= 2 @patterns[name] = block end
find_pattern(env, name, *connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 30 def find_pattern(env, name, *connection_points) match = find_pattern_internal(env, name, *connection_points) end
insert_pattern(env, name, *connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 34 def insert_pattern(env, name, *connection_points) @insert_mode = true root = evaluate_pattern(name, env, connection_points) @insert_mode = false root end
lazy(&block)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 54 def lazy(&block) if @insert_mode block.call else Lazy.new(&block) end end
remove_pattern(env, name, *connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 41 def remove_pattern(env, name, *connection_points) match = find_pattern_internal(env, name, *connection_points) if match match.elements.each do |e| disconnect_element(e) env.delete(e) end match else nil end end
Private Instance Methods
all_structural_features(element)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 317 def all_structural_features(element) @all_structural_features ||= {} return @all_structural_features[element.class] if @all_structural_features[element.class] @all_structural_features[element.class] = element.class.ecore.eAllStructuralFeatures.reject{|f| f.derived} end
both_nil_or_match(pv, tv, visited, check_later)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 285 def both_nil_or_match(pv, tv, visited, check_later) (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later)) end
candidates_via_connection_points(pattern_root, connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 160 def candidates_via_connection_points(pattern_root, connection_points) @candidates_via_connection_points_refs ||= {} refs = (@candidates_via_connection_points_refs[pattern_root.class] ||= pattern_root.class.ecore.eAllReferences.reject{|r| r.derived || r.many || !r.eOpposite}) candidates = nil refs.each do |r| t = pattern_root.getGeneric(r.name) cp = t.is_a?(Proxy) && connection_points.find{|cp| cp.object_id == t._target.object_id} if cp elements = cp.getGenericAsArray(r.eOpposite.name) candidates = elements if candidates.nil? || elements.size < candidates.size end end candidates end
create_bindables(pattern_name, connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 156 def create_bindables(pattern_name, connection_points) (1..(num_pattern_variables(pattern_name) - connection_points.size)).collect{|i| Bindable.new} end
disconnect_element(element)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 306 def disconnect_element(element) return if element.nil? all_structural_features(element).each do |f| if f.many element.setGeneric(f.name, []) else element.setGeneric(f.name, nil) end end end
evaluate_pattern(name, env, connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 298 def evaluate_pattern(name, env, connection_points) prok = @patterns[name] raise "unknown pattern #{name}" unless prok raise "wrong number of arguments, expected #{prok.arity-1} connection points)" \ unless connection_points.size == prok.arity-1 prok.call(env, *connection_points) end
find_pattern_internal(env, name, *connection_points)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 139 def find_pattern_internal(env, name, *connection_points) proxied_args = connection_points.collect{|a| a.is_a?(RGen::MetamodelBuilder::MMBase) ? Proxy.new(a) : a } bindables = create_bindables(name, connection_points) pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables) candidates = candidates_via_connection_points(pattern_root, connection_points) candidates ||= env.find(:class => pattern_root.class) candidates.each do |e| # create new bindables for every try, otherwise they can could be bound to old values bindables = create_bindables(name, connection_points) pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables) matched = match(pattern_root, e) return Match.new(e, matched, bindables.collect{|b| b._value}) if matched end nil end
match(pat_element, test_element)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 176 def match(pat_element, test_element) visited = {} check_later = [] return false unless match_internal(pat_element, test_element, visited, check_later) while cl = check_later.shift pv, tv = cl.lazy._eval, cl.value if cl.feature.is_a?(RGen::ECore::EAttribute) unless pv == tv match_failed(cl.feature, "wrong attribute value (lazy): #{pv} vs. #{tv}") return false end else if pv.is_a?(Proxy) unless pv._target.object_id == tv.object_id match_failed(f, "wrong target object") return false end else unless (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later)) return false end end end end visited.keys end
match_failed(f, msg)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 289 def match_failed(f, msg) puts "match failed #{f&&f.eContainingClass.name}##{f&&f.name}: #{msg}" if @debug end
match_internal(pat_element, test_element, visited, check_later)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 204 def match_internal(pat_element, test_element, visited, check_later) return true if visited[test_element] visited[test_element] = true unless pat_element.class == test_element.class match_failed(nil, "wrong class: #{pat_element.class} vs #{test_element.class}") return false end all_structural_features(pat_element).each do |f| pat_values = pat_element.getGeneric(f.name) # nil values must be kept to support size check with Bindables pat_values = [ pat_values ] unless pat_values.is_a?(Array) test_values = test_element.getGeneric(f.name) test_values = [ test_values] unless test_values.is_a?(Array) if pat_values.size == 1 && pat_values.first.is_a?(Bindable) && pat_values.first._many? unless match_many_bindable(pat_values.first, test_values) return false end else unless pat_values.size == test_values.size match_failed(f, "wrong size #{pat_values.size} vs. #{test_values.size}") return false end pat_values.each_with_index do |pv,i| tv = test_values[i] if pv.is_a?(Lazy) check_later << CheckLater.new(f, pv, tv) elsif pv.is_a?(Bindable) if pv._bound? unless pv._value == tv match_failed(f, "value does not match bound value #{pv._value.class}:#{pv._value.object_id} vs. #{tv.class}:#{tv.object_id}") return false end else pv._bind(tv) end else if f.is_a?(RGen::ECore::EAttribute) unless pv == tv match_failed(f, "wrong attribute value") return false end else if pv.is_a?(Proxy) unless pv._target.object_id == tv.object_id match_failed(f, "wrong target object") return false end else unless both_nil_or_match(pv, tv, visited, check_later) return false end end end end end end end true end
match_many_bindable(bindable, test_values)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 264 def match_many_bindable(bindable, test_values) if bindable._bound? bindable._value.each_with_index do |pv,i| tv = test_values[i] if f.is_a?(RGen::ECore::EAttribute) unless pv == tv match_failed(f, "wrong attribute value") return false end else unless both_nil_or_match(pv, tv, visited, check_later) return false end end end else bindable._bind(test_values.dup) end true end
num_pattern_variables(name)
click to toggle source
# File lib/rgen/util/pattern_matcher.rb, line 293 def num_pattern_variables(name) prok = @patterns[name] prok.arity - 1 end