lib/mutest/expression/parser.rb
module Mutest
  class Expression
    class Parser
      include Concord.new(:types)

      class ParserError < RuntimeError
        include AbstractType
      end

      # Error raised on invalid expressions
      class InvalidExpressionError < ParserError; end

      # Error raised on ambiguous expressions
      class AmbiguousExpressionError < ParserError; end

      # Parse input into expression or raise
      #
      # @param [String] syntax
      #
      # @return [Expression]
      #   if expression is valid
      #
      # @raise [ParserError]
      #   otherwise
      def call(input)
        try_parse(input) or raise InvalidExpressionError, "Expression: #{input.inspect} is not valid"
      end

      # Try to parse input into expression
      #
      # @param [String] input
      #
      # @return [Expression]
      #   if expression is valid
      #
      # @return [nil]
      #   otherwise
      def try_parse(input)
        expressions = expressions(input)
        case expressions.length
        when 0 # rubocop:disable Lint/EmptyWhen
        when 1
          Util.one(expressions)
        else
          raise AmbiguousExpressionError, "Ambiguous expression: #{input.inspect}"
        end
      end

      private

      # Expressions parsed from input
      #
      # @param [String] input
      #
      # @return [Array<Expression>]
      #   if expressions can be parsed from input
      def expressions(input)
        types.each_with_object([]) do |type, aggregate|
          expression = type.try_parse(input)
          aggregate << expression if expression
        end
      end
    end
  end
end
