lib/mutant/ast/pattern.rb
# frozen_string_literal: true module Mutant class AST class Pattern include Adamantium def self.parse(syntax) Lexer.call(syntax) .lmap(&:display_message) .bind(&Parser.public_method(:call)) end class Node < self include Anima.new(:type, :attribute, :descendant, :variable) DEFAULTS = { attribute: nil, descendant: nil, variable: nil }.freeze def initialize(attributes) super(DEFAULTS.merge(attributes)) end class Attribute include Anima.new(:name, :value) class Value class Single < self include Adamantium, Anima.new(:value) def match?(input) input.eql?(value) end def syntax value end end class Group < self include Adamantium, Anima.new(:values) def match?(value) values.any? do |attribute_value| attribute_value.match?(value) end end def syntax "(#{values.map(&:syntax).join(',')})" end end # Group end # Value def match?(node) attribute = Structure.for(node.type).attribute(name) and value.match?(attribute.value(node)) end def syntax "#{name}=#{value.syntax}" end end # Attribute class Descendant include Anima.new(:name, :pattern) def match?(node) descendant = Structure.for(node.type).descendant(name).value(node) !descendant.nil? && pattern.match?(descendant) end def syntax "#{name}=#{pattern.syntax}" end end # Descendant def match?(node) fail NotImplementedError if variable node.type.eql?(type) \ && (!attribute || attribute.match?(node)) \ && (!descendant || descendant.match?(node)) end def syntax "#{type}#{pair_syntax}" end private def pair_syntax pairs = [*attribute&.syntax, *descendant&.syntax] return if pairs.empty? "{#{pairs.join(' ')}}" end end class Any < self def match?(_node) true end end class None < self def match?(_node) false end end class Deep < self include Anima.new(:pattern) def match?(node) Structure.for(node.type).each_node(node) do |child| return true if pattern.match?(child) end false end end end # Pattern end # AST end # Mutant