rkh/mustermann

View on GitHub
mustermann/lib/mustermann/ast/node.rb

Summary

Maintainability
A
45 mins
Test Coverage
module Mustermann
  # @see Mustermann::AST::Pattern
  module AST
    # @!visibility private
    class Node
      # @!visibility private
      attr_accessor :payload, :start, :stop

      # @!visibility private
      # @param [Symbol] name of the node
      # @return [Class] factory for the node
      def self.[](name)
        @names       ||= {}
        @names[name] ||= begin
          const_name = constant_name(name)
          Object.const_get(const_name) if Object.const_defined?(const_name)
        end
      end

      # Turns a class name into a node identifier.
      # @!visibility private
      def self.type
        name[/[^:]+$/].split(/(?<=.)(?=[A-Z])/).map(&:downcase).join(?_).to_sym
      end

      # @!visibility private
      # @param [Symbol] name of the node
      # @return [String] qualified name of factory for the node
      def self.constant_name(name)
        return self.name if name.to_sym == :node
        name = name.to_s.split(?_).map(&:capitalize).join
        "#{self.name}::#{name}"
      end

      # Helper for creating a new instance and calling #parse on it.
      # @return [Mustermann::AST::Node]
      # @!visibility private
      def self.parse(*args, &block)
        new(*args).tap { |n| n.parse(&block) }
      end

      # @!visibility private
      def initialize(payload = nil, **options)
        options.each { |key, value| public_send("#{key}=", value) }
        self.payload = payload
      end

      # @!visibility private
      def is_a?(type)
        type = Node[type] if type.is_a? Symbol
        super(type)
      end

      # Double dispatch helper for reading from the buffer into the payload.
      # @!visibility private
      def parse
        self.payload ||= []
        while element = yield
          payload << element
        end
      end

      # Loop through all nodes that don't have child nodes.
      # @!visibility private
      def each_leaf(&block)
        return enum_for(__method__) unless block_given?
        called = false
        Array(payload).each do |entry|
          next unless entry.respond_to? :each_leaf
          entry.each_leaf(&block)
          called = true
        end
        yield(self) unless called
      end

      # @return [Integer] length of the substring
      # @!visibility private
      def length
        stop - start if start and stop
      end

      # @return [Integer] minimum size for a node
      # @!visibility private
      def min_size
        0
      end

      # Turns a class name into a node identifier.
      # @!visibility private
      def type
        self.class.type
      end

      # @!visibility private
      class Capture < Node
        # @see Mustermann::AST::Compiler::Capture#default
        # @!visibility private
        attr_accessor :constraint

        # @see Mustermann::AST::Compiler::Capture#qualified
        # @!visibility private
        attr_accessor :qualifier

        # @see Mustermann::AST::Pattern#map_param
        # @!visibility private
        attr_accessor :convert

        # @see Mustermann::AST::Node#parse
        # @!visibility private
        def parse
          self.payload ||= ""
          super
        end

        # @!visibility private
        alias_method :name, :payload
      end

      # @!visibility private
      class Char < Node
        # @return [Integer] minimum size for a node
        # @!visibility private
        def min_size
          1
        end
      end

      # AST node for template expressions.
      # @!visibility private
      class Expression < Node
        # @!visibility private
        attr_accessor :operator
      end

      # @!visibility private
      class Composition < Node
        # @!visibility private
        def initialize(payload = nil, **options)
          super(Array(payload), **options)
        end
      end

      # @!visibility private
      class Group < Composition
      end

      # @!visibility private
      class Union < Composition
      end

      # @!visibility private
      class Optional < Node
      end

      # @!visibility private
      class Or < Node
      end

      # @!visibility private
      class Root < Node
        # @!visibility private
        attr_accessor :pattern

        # Will trigger transform.
        #
        # @see Mustermann::AST::Node.parse
        # @!visibility private
        def self.parse(string, &block)
          root         = new
          root.pattern = string
          root.parse(&block)
          root
        end
      end

      # @!visibility private
      class Separator < Node
        # @return [Integer] minimum size for a node
        # @!visibility private
        def min_size
          1
        end
      end

      # @!visibility private
      class Splat < Capture
        # @see Mustermann::AST::Node::Capture#name
        # @!visibility private
        def name
          "splat"
        end
      end

      # @!visibility private
      class NamedSplat < Splat
        # @see Mustermann::AST::Node::Capture#name
        # @!visibility private
        alias_method :name, :payload
      end

      # AST node for template variables.
      # @!visibility private
      class Variable < Capture
        # @!visibility private
        attr_accessor :prefix, :explode
      end

      # @!visibility private
      class WithLookAhead < Node
        # @!visibility private
        attr_accessor :head, :at_end

        # @!visibility private
        def initialize(payload, at_end, **options)
          super(**options)
          self.head, *self.payload = Array(payload)
          self.at_end = at_end
        end
      end
    end
  end
end