2014-05-30 21:02:15 -04:00
|
|
|
module Mutant
|
|
|
|
|
|
|
|
# Abstract base class for match expression
|
|
|
|
class Expression
|
|
|
|
include AbstractType, Adamantium, Concord::Public.new(:match)
|
|
|
|
|
|
|
|
include Equalizer.new(:syntax)
|
|
|
|
|
|
|
|
SCOPE_NAME_PATTERN = /[A-Za-z][A-Za-z\d_]*/.freeze
|
|
|
|
|
|
|
|
METHOD_NAME_PATTERN = Regexp.union(
|
|
|
|
/[A-Za-z_][A-Za-z\d_]*[!?=]?/,
|
2014-06-29 17:25:17 -04:00
|
|
|
*AST::Types::OPERATOR_METHODS.map(&:to_s)
|
2014-05-30 21:02:15 -04:00
|
|
|
).freeze
|
|
|
|
|
2014-07-12 12:18:01 -04:00
|
|
|
INSPECT_FORMAT = '<Mutant::Expression: %s>'.freeze
|
|
|
|
|
2014-06-08 14:12:16 -04:00
|
|
|
SCOPE_PATTERN = /#{SCOPE_NAME_PATTERN}(?:#{SCOPE_OPERATOR}#{SCOPE_NAME_PATTERN})*/.freeze
|
2014-05-30 21:02:15 -04:00
|
|
|
|
|
|
|
REGISTRY = {}
|
|
|
|
|
2014-07-06 10:30:28 -04:00
|
|
|
# Error raised on invalid expressions
|
2014-07-05 22:34:01 -04:00
|
|
|
class InvalidExpressionError < RuntimeError; end
|
2014-07-06 10:30:28 -04:00
|
|
|
|
2014-08-07 12:00:31 -04:00
|
|
|
# Error raised on ambiguous expressions
|
|
|
|
class AmbiguousExpressionError < RuntimeError; end
|
2014-07-05 22:34:01 -04:00
|
|
|
|
2014-05-30 21:02:15 -04:00
|
|
|
# Initialize expression
|
|
|
|
#
|
|
|
|
# @param [MatchData] match
|
2014-06-09 22:13:15 -04:00
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
2014-05-30 21:02:15 -04:00
|
|
|
def initialize(*)
|
|
|
|
super
|
|
|
|
@syntax = match.to_s
|
2014-07-12 12:18:01 -04:00
|
|
|
@inspect = format(INSPECT_FORMAT, syntax)
|
2014-05-30 21:02:15 -04:00
|
|
|
end
|
|
|
|
|
2014-07-12 12:18:01 -04:00
|
|
|
# Return inspection
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
|
|
|
attr_reader :inspect
|
|
|
|
|
2014-05-30 21:02:15 -04:00
|
|
|
# Return syntax
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
|
|
|
attr_reader :syntax
|
|
|
|
|
|
|
|
# Return match length for expression
|
|
|
|
#
|
2014-06-28 16:47:46 -04:00
|
|
|
# @param [Expression] other
|
2014-05-30 21:02:15 -04:00
|
|
|
#
|
|
|
|
# @return [Fixnum]
|
|
|
|
#
|
2014-06-09 22:13:15 -04:00
|
|
|
# @api private
|
|
|
|
#
|
2014-06-28 16:47:46 -04:00
|
|
|
def match_length(other)
|
|
|
|
if eql?(other)
|
2014-05-30 21:02:15 -04:00
|
|
|
syntax.length
|
|
|
|
else
|
|
|
|
0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-06-28 16:47:46 -04:00
|
|
|
# Test if expression is prefix
|
|
|
|
#
|
|
|
|
# @param [Expression] other
|
|
|
|
#
|
|
|
|
# @return [Boolean]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
|
|
|
def prefix?(other)
|
|
|
|
!match_length(other).zero?
|
|
|
|
end
|
|
|
|
|
2014-05-30 21:02:15 -04:00
|
|
|
# Register expression
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
|
|
|
def self.register(regexp)
|
|
|
|
REGISTRY[regexp] = self
|
|
|
|
end
|
|
|
|
private_class_method :register
|
|
|
|
|
2014-06-28 16:47:46 -04:00
|
|
|
# Parse input into expression or raise
|
|
|
|
#
|
|
|
|
# @param [String] syntax
|
|
|
|
#
|
|
|
|
# @return [Expression]
|
|
|
|
# if expression is valid
|
|
|
|
#
|
|
|
|
# @raise [RuntimeError]
|
|
|
|
# otherwise
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
2014-06-29 19:16:34 -04:00
|
|
|
def self.parse(input)
|
2014-07-05 22:34:01 -04:00
|
|
|
try_parse(input) or fail InvalidExpressionError, "Expression: #{input.inspect} is not valid"
|
2014-06-28 16:47:46 -04:00
|
|
|
end
|
|
|
|
|
2014-05-30 21:02:15 -04:00
|
|
|
# Parse input into expression
|
|
|
|
#
|
2014-06-28 16:47:46 -04:00
|
|
|
# @param [String] input
|
2014-05-30 21:02:15 -04:00
|
|
|
#
|
|
|
|
# @return [Expression]
|
|
|
|
# if expression is valid
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
# otherwise
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
2014-06-29 19:16:34 -04:00
|
|
|
def self.try_parse(input)
|
2014-06-28 16:47:46 -04:00
|
|
|
expressions = expressions(input)
|
2014-05-30 21:02:15 -04:00
|
|
|
case expressions.length
|
|
|
|
when 0
|
|
|
|
when 1
|
|
|
|
expressions.first
|
|
|
|
else
|
2014-08-07 12:00:31 -04:00
|
|
|
fail AmbiguousExpressionError, "Ambiguous expression: #{input.inspect}"
|
2014-05-30 21:02:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Return expressions for input
|
|
|
|
#
|
|
|
|
# @param [String] input
|
|
|
|
#
|
|
|
|
# @return [Classifier]
|
|
|
|
# if classifier can be found
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
# otherwise
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
#
|
|
|
|
def self.expressions(input)
|
|
|
|
REGISTRY.each_with_object([]) do |(regexp, klass), expressions|
|
|
|
|
match = regexp.match(input)
|
2014-06-08 09:01:26 -04:00
|
|
|
next unless match
|
|
|
|
expressions << klass.new(match)
|
2014-05-30 21:02:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
private_class_method :expressions
|
|
|
|
|
|
|
|
end # Expression
|
|
|
|
end # Mutant
|