joshwlewis/unitwise

View on GitHub
lib/unitwise/expression/decomposer.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Unitwise
  module Expression
    # The decomposer is used to turn string expressions into collections of
    # terms. It is responsible for executing the parsing and transformation
    # of a string, as well as caching the results.
    class Decomposer

      MODES = [:primary_code, :secondary_code, :names, :slugs, :symbol].freeze

      class << self

        # Parse an expression to an array of terms and cache the results
        def parse(expression)
          expression = expression.to_s
          if cache.key?(expression)
            cache[expression]
          elsif decomposer = new(expression)
            cache[expression] = decomposer
          end
        end

        def parsers
          @parsers ||= MODES.reduce({}) do |hash, mode|
            hash[mode] = Parser.new(mode); hash
          end
        end

        def transformer
          @transformer = Transformer.new
        end

        private

        # A simple cache to prevent re-decomposing the same units
        # api private
        def cache
          @cache ||= {}
        end

        # Reset memoized data. Allows rebuilding of parsers, transformers, and
        # the cache after list of atoms has been modified.
        def reset
          @parsers = nil
          @transformer = nil
          @cache = nil
        end
      end

      attr_reader :expression, :mode

      def initialize(expression)
        @expression = expression.to_s
        if expression.empty? || terms.nil? || terms.empty?
          fail(ExpressionError, "Could not evaluate '#{ expression }'.")
        end
      end

      def parse
        self.class.parsers.reduce(nil) do |_, (mode, parser)|
          parsed = parser.parse(expression) rescue next
          @mode = mode
          break parsed
        end
      end

      def transform
        @transform ||= self.class.transformer.apply(parse, :mode => mode)
      end

      def terms
        @terms ||= if transform.respond_to?(:terms)
          transform.terms
        else
          Array(transform)
        end
      end

    end
  end
end