require 'corelib/pattern_matching/base' # A "userland" implementation of pattern matching for Opal class ::PatternMatching def self.call(from, pattern) pm = new(from, pattern) pm.match || (return nil) pm.returns end def initialize(from, pattern) @from, @pattern = from, pattern @returns = [] end attr_reader :returns def match(from = @from, pattern = @pattern) if pattern == :var @returns << from true else # Pattern is otherwise an Array type, *args = *pattern case type when :save # from => @returns << from match(from, args[0]) when :lit # 3, 4, :a, (1..), ... (but also ^a) args[0] === from when :any # a | b args.any? { |arg| match(from, arg) } when :all # Array(1) which works as Array & [1] (& doesn't exist though...) args.all? { |arg| match(from, arg) } when :array # [...] fixed_size, array_size, array_match = *args return false unless from.respond_to? :deconstruct a = from.deconstruct return false if fixed_size && a.length != array_size return false if a.length < array_size skip_elems = 0 skip_rests = 0 array_match.each_with_index.all? do |elem, i| type, *args = elem case type when :rest skip_elems = a.size - array_size skip_rests = 1 match(a[i...i + skip_elems], args[0]) if args[0] # :save? true else match(a[i + skip_elems - skip_rests], elem) end end when :find # [*, a, b, *] find_match, = *args first, *find_match, last = *find_match pattern_length = find_match.length return false unless from.respond_to? :deconstruct a = from.deconstruct a_length = a.length return false if a_length < pattern_length # We will save the backup of returns, to be restored # on each iteration to try again. returns_backup = @returns.dup # Extract the capture info from first and last. # Both are of a form [:rest], or [:rest, :var]. # So our new variables will be either :var, or nil. first, last = first[1], last[1] # Let's try to match each possibility... # [A, B, c, d], [a, B, C, d], [a, b, C, D] iterations = a_length - pattern_length + 1 iterations.times.any? do |skip| first_part = a[0, skip] content = a[skip, pattern_length] last_part = a[skip + pattern_length..-1] match(first_part, first) if first success = content.each_with_index.all? do |e, i| match(e, find_match[i]) end match(last_part, last) if last # Match failed. Let's not return anything. @returns = returns_backup.dup unless success success end when :hash # {...} any_size, hash_match = *args hash_match = hash_match.to_h return false unless from.respond_to? :deconstruct_keys if any_size && any_size != true # a => {a:, **other} a = from.deconstruct_keys(nil) # ^^^^^^^ else a = from.deconstruct_keys(hash_match.keys) end hash_match.all? do |k, v| return false unless a.key? k match(a[k], v) end || (return false) if any_size && any_size != true match(a.except(*hash_match.keys), args[0]) elsif !any_size return false unless a.except(*hash_match.keys).empty? end true end end end end