Merge pull request #565 from mbj/feature/regexp_parser

Mutate regexp body
This commit is contained in:
Markus Schirp 2016-05-23 06:09:31 +00:00
commit 2b96c184d8
30 changed files with 1769 additions and 52 deletions

View file

@ -1,3 +1,3 @@
---
threshold: 16
total_score: 1174
total_score: 1280

View file

@ -14,6 +14,7 @@ require 'parallel'
require 'parser'
require 'parser/current'
require 'pathname'
require 'regexp_parser'
require 'set'
require 'stringio'
require 'unparser'
@ -42,12 +43,23 @@ end # Mutant
require 'mutant/version'
require 'mutant/env'
require 'mutant/env/bootstrap'
require 'mutant/registry'
require 'mutant/ast'
require 'mutant/ast/sexp'
require 'mutant/ast/types'
require 'mutant/ast/nodes'
require 'mutant/ast/named_children'
require 'mutant/ast/node_predicates'
require 'mutant/ast/regexp'
require 'mutant/ast/regexp/transformer'
require 'mutant/ast/regexp/transformer/direct'
require 'mutant/ast/regexp/transformer/text'
require 'mutant/ast/regexp/transformer/recursive'
require 'mutant/ast/regexp/transformer/quantifier'
require 'mutant/ast/regexp/transformer/options_group'
require 'mutant/ast/regexp/transformer/character_set'
require 'mutant/ast/regexp/transformer/root'
require 'mutant/ast/regexp/transformer/alternative'
require 'mutant/ast/meta'
require 'mutant/ast/meta/send'
require 'mutant/ast/meta/const'
@ -71,13 +83,13 @@ require 'mutant/parallel/source'
require 'mutant/warning_filter'
require 'mutant/require_highjack'
require 'mutant/mutation'
require 'mutant/registry'
require 'mutant/mutator'
require 'mutant/mutator/util'
require 'mutant/mutator/util/array'
require 'mutant/mutator/util/symbol'
require 'mutant/mutator/node'
require 'mutant/mutator/node/generic'
require 'mutant/mutator/node/regexp'
require 'mutant/mutator/node/literal'
require 'mutant/mutator/node/literal/boolean'
require 'mutant/mutator/node/literal/range'

40
lib/mutant/ast/regexp.rb Normal file
View file

@ -0,0 +1,40 @@
module Mutant
module AST
# Regexp source mapper
module Regexp
UNSUPPORTED_EXPRESSION_TYPE = :conditional
private_constant(*constants(false))
# Check if expression is supported by mapper
#
# @param expression [Regexp::Expression]
#
# @return [Boolean]
def self.supported?(expression)
expression.terminal? || expression.all? do |subexp|
!subexp.type.equal?(UNSUPPORTED_EXPRESSION_TYPE) && supported?(subexp)
end
end
# Convert expression into ast node
#
# @param expression [Regexp::Expression]
#
# @return [Parser::AST::Node]
def self.to_ast(expression)
ast_type = :"regexp_#{expression.token}_#{expression.type}"
Transformer.lookup(ast_type).to_ast(expression)
end
# Convert node into expression
#
# @param node [Parser::AST::Node]
#
# @return [Regexp::Expression]
def self.to_expression(node)
Transformer.lookup(node.type).to_expression(node)
end
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,185 @@
module Mutant
module AST
module Regexp
# Regexp bijective mapper
#
# Transforms parsed regular expression representation from
# `Regexp::Expression` instances (provided by `regexp_parser`) into
# equivalent representations using `Parser::AST::Node`
class Transformer
include AbstractType
REGISTRY = Registry.new
# Lookup transformer class for regular expression node type
#
# @param type [Symbol]
#
# @return [Class<Transformer>]
def self.lookup(type)
REGISTRY.lookup(type)
end
# Register transformer class as responsible for handling node type
#
# @param type [Symbol]
#
# @return [undefined]
def self.register(type)
REGISTRY.register(type, self)
end
private_class_method :register
# Transform expression
#
# @param expression [Regexp::Expression]
#
# @return [Parser::AST::Node]
def self.to_ast(expression)
self::ExpressionToAST.call(expression)
end
# Transform node
#
# @param node [Parser::AST::Node]
#
# @return [Regexp::Expression]
def self.to_expression(node)
self::ASTToExpression.call(node)
end
# Abstract expression transformer
class ExpressionToAST
PREFIX = :regexp
include Concord.new(:expression), Procto.call, AST::Sexp, AbstractType, Adamantium
private
# Node with provided children using node type constructed in `type`
#
# @param [Object,Parser::AST::Node] child of node
#
# @return [Parser::AST::Node]
def ast(*children)
s(type, *children)
end
# Wrap provided node in a quantifier
#
# @param node [Parser::AST::Node]
#
# @return [Parser::AST::Node]
# quantifier node wrapping provided node if expression is quantified
#
# @return [Parser::AST::Node]
# original node otherwise
def quantify(node)
return node unless expression.quantified?
Quantifier.to_ast(expression.quantifier).append(node)
end
# Transformed children of expression
#
# @return [Array<Parser::AST::Node>]
def children
expression.expressions.map(&Regexp.method(:to_ast))
end
# Node type constructed from token and type of `Regexp::Expression`
#
# @return [Symbol]
def type
:"#{PREFIX}_#{expression.token}_#{expression.type}"
end
end # ExpressionToAST
# Abstract node transformer
class ASTToExpression
include Concord.new(:node), Procto.call, AbstractType, Adamantium
# Call generic transform method and freeze result
#
# @return [Regexp::Expression]
def call
transform.freeze
end
private
# Transformation of ast into expression
#
# @return [Regexp::Expression]
abstract_method :transform
# Transformed children of node
#
# @return [Array<Regexp::Expression>]
def subexpressions
node.children.map(&Regexp.public_method(:to_expression))
end
end # ASTToExpression
# Mixin for node transformers
#
# Helps construct a mapping from Parser::AST::Node domain to
# Regexp::Expression domain
module LookupTable
Mapping = Class.new.include(Concord::Public.new(:token, :regexp_class))
# Table mapping ast types to object information for regexp domain
class Table
# Coerce array of mapping information into structured table
#
# @param [Array(Symbol, Array, Class<Regexp::Expression>)]
#
# @return [Table]
def self.create(*rows)
table = rows.map do |ast_type, token, klass|
[ast_type, Mapping.new(::Regexp::Token.new(*token), klass)]
end.to_h
new(table)
end
include Concord.new(:table), Adamantium
# Types defined by the table
#
# @return [Array<Symbol>]
def types
table.keys
end
# Lookup mapping information given an ast node type
#
# @param type [Symbol]
#
# @return [Mapping]
def lookup(type)
table.fetch(type)
end
end # Table
private
# Lookup expression token given node type
#
# @return [Regexp::Token]
def expression_token
self.class::TABLE.lookup(node.type).token
end
# Lookup regexp class given node type
#
# @return [Class<Regexp::Expression>]
def expression_class
self.class::TABLE.lookup(node.type).regexp_class
end
end # LookupTable
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,39 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for Regexp `alternative` nodes
#
# This transformer is very similar to the generic recursive mapper
# except for the fact that the `Regexp::Expression` class for
# `alternative` nodes has a unique constructor
class Alternative < self
register :regexp_sequence_expression
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
ExpressionToAST = Class.new(Recursive::ExpressionToAST)
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
# Alternative instance with dummy values for `level`, `set_level`,
# and `conditional_level`. These values do not affect unparsing
ALTERNATIVE = IceNine.deep_freeze(
::Regexp::Expression::Alternative.new(0, 0, 0)
)
private
# Transform ast into expression
#
# @return [Regexp::Expression::Alternative]
def transform
ALTERNATIVE.dup.tap do |alt|
alt.expressions = subexpressions
end
end
end # ASTToExpression
end # Alternative
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,46 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for character sets
#
# The `Regexp::Expression` representation of a character set
# is unique due to its usage of the `#members` attribute which
# is why it gets its own transformer
class CharacterSet < self
register :regexp_character_set
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
class ExpressionToAST < Transformer::ExpressionToAST
# Transform character set expression into node
#
# @return [Parser::AST::Node]
def call
quantify(ast(*expression.members))
end
end # ExpressionToAST
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
CHARACTER_SET = IceNine.deep_freeze(
::Regexp::Expression::CharacterSet.new(
::Regexp::Token.new(:set, :character, '[')
)
)
private
# Transform node into expression
#
# @return [Regexp::Expression]
def transform
CHARACTER_SET.dup.tap do |expression|
expression.members = node.children
end
end
end # ASTToExpression
end # CharacterSet
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,97 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for nodes which map directly to other domain
#
# A node maps "directly" to another domain if the node never
# has children or text which needs to be preserved for a mapping
#
# @example direct mapping
#
# input = /\d/
# expression = Regexp::Parser.parse(input).first
# node = Transformer::Direct.to_ast(expression)
#
# # the digit type always has the same text and no children
# expression.text # => "\\d"
# expression.terminal? # => true
#
# # therefore the `Parser::AST::Node` is always the same
# node # => s(:regexp_digit_type)
class Direct < self
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
class ExpressionToAST < Transformer::ExpressionToAST
# Transform expression into node
#
# @return [Parser::AST::Node]
def call
quantify(ast)
end
end # ExpressionToAST
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
include LookupTable
# rubocop:disable LineLength
TABLE = Table.create(
[:regexp_one_or_more_escape, [:escape, :one_or_more, '\+'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_zero_or_one_escape, [:escape, :zero_or_one, '\?'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_alternation_escape, [:escape, :alternation, '\|'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_group_open_escape, [:escape, :group_open, '\('], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_group_close_escape, [:escape, :group_close, '\)'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_interval_open_escape, [:escape, :interval_open, '\{'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_interval_close_escape, [:escape, :interval_close, '\}'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_newline_escape, [:escape, :newline, '\n'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_zero_or_more_escape, [:escape, :zero_or_more, '\*'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_carriage_escape, [:escape, :carriage, '\r'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_dot_escape, [:escape, :dot, '\.'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_set_open_escape, [:escape, :set_open, '\['], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_set_close_escape, [:escape, :set_close, '\]'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_eol_escape, [:escape, :eol, '\$'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_bell_escape, [:escape, :bell, '\a'], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_escape_escape, [:escape, :escape, '\e'], ::Regexp::Expression::EscapeSequence::AsciiEscape],
[:regexp_form_feed_escape, [:escape, :form_feed, '\f'], ::Regexp::Expression::EscapeSequence::FormFeed],
[:regexp_vertical_tab_escape, [:escape, :vertical_tab, '\v'], ::Regexp::Expression::EscapeSequence::VerticalTab],
[:regexp_mark_keep, [:keep, :mark, '\K'], ::Regexp::Expression::Keep::Mark],
[:regexp_bos_anchor, [:anchor, :bos, '\\A'], ::Regexp::Expression::Anchor::BeginningOfString],
[:regexp_match_start_anchor, [:anchor, :match_start, '\\G'], ::Regexp::Expression::Anchor::MatchStart],
[:regexp_word_boundary_anchor, [:anchor, :word_boundary, '\b'], ::Regexp::Expression::Anchor::WordBoundary],
[:regexp_eos_ob_eol_anchor, [:anchor, :eos_ob_eol, '\\Z'], ::Regexp::Expression::Anchor::EndOfStringOrBeforeEndOfLine],
[:regexp_eos_anchor, [:anchor, :eos, '\\z'], ::Regexp::Expression::Anchor::EndOfString],
[:regexp_bol_anchor, [:anchor, :bol, '^'], ::Regexp::Expression::Anchor::BeginningOfLine],
[:regexp_eol_anchor, [:anchor, :eol, '$'], ::Regexp::Expression::Anchor::EndOfLine],
[:regexp_nonword_boundary_anchor, [:anchor, :nonword_boundary, '\\B'], ::Regexp::Expression::Anchor::NonWordBoundary],
[:regexp_alpha_property, [:property, :alpha, '\p{Alpha}'], ::Regexp::Expression::UnicodeProperty::Alpha],
[:regexp_script_arabic_property, [:property, :script_arabic, '\p{Arabic}'], ::Regexp::Expression::UnicodeProperty::Script],
[:regexp_script_hangul_property, [:property, :script_hangul, '\p{Hangul}'], ::Regexp::Expression::UnicodeProperty::Script],
[:regexp_script_han_property, [:property, :script_han, '\p{Han}'], ::Regexp::Expression::UnicodeProperty::Script],
[:regexp_script_hiragana_property, [:property, :script_hiragana, '\p{Hiragana}'], ::Regexp::Expression::UnicodeProperty::Script],
[:regexp_script_katakana_property, [:property, :script_katakana, '\p{Katakana}'], ::Regexp::Expression::UnicodeProperty::Script],
[:regexp_letter_any_property, [:property, :letter_any, '\p{L}'], ::Regexp::Expression::UnicodeProperty::Letter::Any],
[:regexp_digit_type, [:type, :digit, '\d'], ::Regexp::Expression::CharacterType::Digit],
[:regexp_space_type, [:type, :space, '\s'], ::Regexp::Expression::CharacterType::Space],
[:regexp_word_type, [:type, :word, '\w'], ::Regexp::Expression::CharacterType::Word],
[:regexp_nondigit_type, [:type, :nondigit, '\D'], ::Regexp::Expression::CharacterType::NonDigit],
[:regexp_nonspace_type, [:type, :nonspace, '\S'], ::Regexp::Expression::CharacterType::NonSpace],
[:regexp_nonword_type, [:type, :nonword, '\W'], ::Regexp::Expression::CharacterType::NonWord],
[:regexp_dot_meta, [:meta, :dot, '.'], ::Regexp::Expression::CharacterType::Any]
)
private
# Transform ast into expression
#
# @return [Regexp::Expression]
def transform
expression_class.new(expression_token)
end
end # ASTToExpression
ASTToExpression::TABLE.types.each(&method(:register))
end # Direct
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,66 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for option groups
class OptionsGroup < self
register :regexp_options_group
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
class ExpressionToAST < Transformer::ExpressionToAST
# Transform options group into node
#
# @return [Parser::AST::Node]
def call
quantify(ast(expression.options, *children))
end
end # ExpressionToAST
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
include NamedChildren
children :options
private
# Covnert node into expression
#
# @return [Regexp::Expression::Group::Options]
def transform
options_group.tap do |expression|
expression.expressions = subexpressions
end
end
# Recursive mapping of children
#
# @return [Array<Regexp::Expression>]
def subexpressions
remaining_children.map(&Regexp.public_method(:to_expression))
end
# Options group instance constructed from options text
#
# @return [Regexp::Expression::Group::Options]
def options_group
::Regexp::Expression::Group::Options.new(
::Regexp::Token.new(:group, :options, text)
)
end
# Flag text constructed from enabled options
#
# @return [String]
def text
flags = options.map { |key, value| key if value }.join
"(?#{flags}-:"
end
end # ASTToExpression
end # OptionsGroup
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,112 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for regexp quantifiers
class Quantifier < self
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
class ExpressionToAST < Transformer::ExpressionToAST
# Transform quantifier into node
#
# @return [Parser::AST::Node]
def call
ast(expression.min, expression.max)
end
private
# Custom `type` for quantifiers which use `mode` instead of `type`
#
# @return [Symbol]
def type
:"regexp_#{expression.mode}_#{expression.token}"
end
end # ExpressionToAST
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
include NamedChildren
children :min, :max, :subject
Quantifier = Class.new.include(Concord::Public.new(:type, :suffix, :mode))
QUANTIFIER_MAP = IceNine.deep_freeze({
regexp_greedy_zero_or_more: [:zero_or_more, '*', :greedy],
regexp_greedy_one_or_more: [:one_or_more, '+', :greedy],
regexp_greedy_zero_or_one: [:zero_or_one, '?', :greedy],
regexp_possessive_zero_or_one: [:zero_or_one, '?+', :possessive],
regexp_reluctant_zero_or_more: [:zero_or_more, '*?', :reluctant],
regexp_reluctant_one_or_more: [:one_or_more, '+?', :reluctant],
regexp_possessive_zero_or_more: [:zero_or_more, '*+', :possessive],
regexp_possessive_one_or_more: [:one_or_more, '++', :possessive],
regexp_greedy_interval: [:interval, '', :greedy],
regexp_reluctant_interval: [:interval, '?', :reluctant],
regexp_possessive_interval: [:interval, '+', :possessive]
}.map { |ast_type, arguments| [ast_type, Quantifier.new(*arguments)] }.to_h)
private
# Transform ast into quantifier attached to expression
#
# @return [Regexp::Expression]
def transform
Regexp.to_expression(subject).dup.tap do |expression|
expression.quantify(type, text, min, max, mode)
end
end
# Quantifier text
#
# @return [String]
def text
if type.equal?(:interval)
interval_text + suffix
else
suffix
end
end
# Type of quantifier
#
# @return [:zero_or_more,:one_or_more,:interval]
def type
quantifier.type
end
# Regexp symbols used to specify quantifier
#
# @return [String]
def suffix
quantifier.suffix
end
# The quantifier "mode"
#
# @return [:greedy,:possessive,:reluctant]
def mode
quantifier.mode
end
# Quantifier mapping information for current node
#
# @return [Quantifier]
def quantifier
QUANTIFIER_MAP.fetch(node.type)
end
# Interval text constructed from min and max
#
# @return [String]
def interval_text
interval = [min, max].map { |num| num if num > 0 }.uniq
"{#{interval.join(',')}}"
end
end # ASTToExpression
ASTToExpression::QUANTIFIER_MAP.keys.each(&method(:register))
end # Quantifier
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,50 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for nodes with children
class Recursive < self
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
class ExpressionToAST < Transformer::ExpressionToAST
# Transform expression and children into nodes
#
# @return [Parser::AST::Node]
def call
quantify(ast(*children))
end
end # ExpressionToAST
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
include LookupTable
# rubocop:disable LineLength
TABLE = Table.create(
[:regexp_alternation_meta, [:meta, :alternation, '|'], ::Regexp::Expression::Alternation],
[:regexp_nlookahead_assertion, [:assertion, :nlookahead, '(?!'], ::Regexp::Expression::Assertion::NegativeLookahead],
[:regexp_passive_group, [:group, :passive, '(?:'], ::Regexp::Expression::Group::Passive],
[:regexp_nlookbehind_assertion, [:assertion, :nlookbehind, '(?<!'], ::Regexp::Expression::Assertion::NegativeLookbehind],
[:regexp_lookbehind_assertion, [:assertion, :lookbehind, '(?<='], ::Regexp::Expression::Assertion::Lookbehind],
[:regexp_lookahead_assertion, [:assertion, :lookahead, '(?='], ::Regexp::Expression::Assertion::Lookahead],
[:regexp_atomic_group, [:group, :atomic, '(?>'], ::Regexp::Expression::Group::Atomic],
[:regexp_capture_group, [:group, :capture, '('], ::Regexp::Expression::Group::Capture]
)
private
# Transform nodes and their children into expressions
#
# @return [Regexp::Expression]
def transform
expression_class.new(expression_token).tap do |expression|
expression.expressions = subexpressions
end
end
end # ASTToExpression
ASTToExpression::TABLE.types.each(&method(:register))
end # Recursive
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,29 @@
module Mutant
module AST
module Regexp
class Transformer
# Transformer for root nodes
class Root < self
register :regexp_root_expression
ExpressionToAST = Class.new(Recursive::ExpressionToAST)
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
private
# Transform node into root expression
#
# @return [Regexp::Expression::Root]
def transform
::Regexp::Expression::Root.new.tap do |root|
root.expressions = subexpressions
end
end
end # ASTToExpression
end # Root
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -0,0 +1,56 @@
module Mutant
module AST
module Regexp
class Transformer
# Regexp AST transformer for nodes that encode a text value
class Text < self
# Mapper from `Regexp::Expression` to `Parser::AST::Node`
class ExpressionToAST < Transformer::ExpressionToAST
# Transform expression into node preserving text value
#
# @return [Parser::AST::Node]
def call
quantify(ast(expression.text))
end
end # ExpressionToAST
# Mapper from `Parser::AST::Node` to `Regexp::Expression`
class ASTToExpression < Transformer::ASTToExpression
include LookupTable
# rubocop:disable LineLength
TABLE = Table.create(
[:regexp_literal_literal, %i[literal literal], ::Regexp::Expression::Literal],
[:regexp_comment_group, %i[group comment], ::Regexp::Expression::Group::Comment],
[:regexp_named_group, %i[group named], ::Regexp::Expression::Group::Named],
[:regexp_number_backref, %i[backref number], ::Regexp::Expression::Backreference::Number],
[:regexp_name_call_backref, %i[backref name_call], ::Regexp::Expression::Backreference::NameCall],
[:regexp_whitespace_free_space, %i[free_space whitespace], ::Regexp::Expression::WhiteSpace],
[:regexp_comment_free_space, %i[free_space comment], ::Regexp::Expression::WhiteSpace],
[:regexp_hex_escape, %i[escape hex], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_literal_escape, %i[escape literal], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_backslash_escape, %i[escape backslash], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_tab_escape, %i[escape tab], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_codepoint_list_escape, %i[escape codepoint_list], ::Regexp::Expression::EscapeSequence::Literal],
[:regexp_control_escape, %i[escape control], ::Regexp::Expression::EscapeSequence::Control],
[:regexp_meta_sequence_escape, %i[escape meta_sequence], ::Regexp::Expression::EscapeSequence::Control]
)
private
# Transform node to expression with text value
#
# @return [Regexp::Expression]
def transform
token = expression_token.dup
token.text = node.children.first
expression_class.new(token)
end
end # ASTToExpression
ASTToExpression::TABLE.types.each(&method(:register))
end # Text
end # Transformer
end # Regexp
end # AST
end # Mutant

View file

@ -1,7 +1,7 @@
module Mutant
module AST
# Groups of node types
module Types
module Types # rubocop:disable Metrics/ModuleLength
symbolset = ->(strings) { strings.map(&:to_sym).to_set.freeze }
ASSIGNABLE_VARIABLES = symbolset.(%w[ivasgn lvasgn cvasgn gvasgn])
@ -38,8 +38,94 @@ module Mutant
#
BLACKLIST = symbolset.(%w[not])
# Nodes generated by regular expression body parsing
REGEXP = symbolset.(%w[
regexp_alpha_property
regexp_alternation_escape
regexp_alternation_meta
regexp_atomic_group
regexp_backslash_escape
regexp_bell_escape
regexp_bol_anchor
regexp_bos_anchor
regexp_capture_group
regexp_carriage_escape
regexp_character_set
regexp_character_set
regexp_codepoint_list_escape
regexp_comment_free_space
regexp_comment_group
regexp_control_escape
regexp_digit_type
regexp_dot_escape
regexp_dot_meta
regexp_eol_anchor
regexp_eol_escape
regexp_eos_anchor
regexp_eos_ob_eol_anchor
regexp_escape_escape
regexp_form_feed_escape
regexp_greedy_interval
regexp_greedy_one_or_more
regexp_greedy_zero_or_more
regexp_greedy_zero_or_one
regexp_group_close_escape
regexp_group_open_escape
regexp_hex_escape
regexp_interval_close_escape
regexp_interval_open_escape
regexp_letter_any_property
regexp_literal_escape
regexp_literal_literal
regexp_lookahead_assertion
regexp_lookbehind_assertion
regexp_mark_keep
regexp_match_start_anchor
regexp_meta_sequence_escape
regexp_name_call_backref
regexp_named_group
regexp_newline_escape
regexp_nlookahead_assertion
regexp_nlookbehind_assertion
regexp_nondigit_type
regexp_nonspace_type
regexp_nonword_boundary_anchor
regexp_nonword_type
regexp_number_backref
regexp_one_or_more_escape
regexp_open_conditional
regexp_options_group
regexp_passive_group
regexp_possessive_interval
regexp_possessive_one_or_more
regexp_possessive_zero_or_more
regexp_possessive_zero_or_one
regexp_reluctant_interval
regexp_reluctant_one_or_more
regexp_reluctant_zero_or_more
regexp_root_expression
regexp_script_arabic_property
regexp_script_han_property
regexp_script_hangul_property
regexp_script_hiragana_property
regexp_script_katakana_property
regexp_sequence_expression
regexp_set_close_escape
regexp_set_open_escape
regexp_space_type
regexp_tab_escape
regexp_vertical_tab_escape
regexp_whitespace_free_space
regexp_word_boundary_anchor
regexp_word_type
regexp_zero_or_more_escape
regexp_zero_or_one_escape
])
# Nodes that are NOT generated by parser but used by mutant / unparser.
EXTRA = symbolset.(%w[empty])
GENERATED = symbolset.(%w[empty])
EXTRA = symbolset.(GENERATED + REGEXP)
# All node types mutant handles
ALL = symbolset.((Parser::Meta::NODE_TYPES + EXTRA) - BLACKLIST)

View file

@ -23,6 +23,7 @@ module Mutant
#
# @return [undefined]
def dispatch
mutate_body
emit_singletons unless parent_node
children.each_with_index do |child, index|
mutate_child(index) unless n_str?(child)
@ -31,6 +32,45 @@ module Mutant
emit_type(s(:str, NULL_REGEXP_SOURCE), options)
end
# Mutate regexp body
#
# @note will only mutate parts of regexp body if the
# body is composed of only strings. Regular expressions
# with interpolation are skipped
#
# @return [undefined]
def mutate_body
return unless body.all?(&method(:n_str?))
return unless AST::Regexp.supported?(body_expression)
Mutator.mutate(body_ast).each do |mutation|
source = AST::Regexp.to_expression(mutation).to_s
emit_type(s(:str, source), options)
end
end
# AST representation of regexp body
#
# @return [Parser::AST::Node]
def body_ast
AST::Regexp.to_ast(body_expression)
end
# Expression representation of regexp body
#
# @return [Regexp::Expression]
def body_expression
::Regexp::Parser.parse(body.map(&:children).join)
end
memoize :body_expression
# Children of regexp node which compose regular expression source
#
# @return [Array<Parser::AST::Node>]
def body
children.slice(0...-1)
end
end # Regex
end # Literal
end # Node

View file

@ -16,7 +16,7 @@ module Mutant
}
MAP = IceNine.deep_freeze(
Hash[map.map { |type, prefix| [type, [prefix, /^#{Regexp.escape(prefix)}/]] }]
Hash[map.map { |type, prefix| [type, [prefix, /^#{::Regexp.escape(prefix)}/]] }]
)
handle(*MAP.keys)

View file

@ -0,0 +1,44 @@
module Mutant
class Mutator
class Node
module Regexp
# Generic regexp mutator
class Generic < Node
handle(*(AST::Types::REGEXP - %i[regexp_root_expression regexp_bol_anchor]))
# Noop dispatch
#
# @return [undefined]
def dispatch
end
end # Generic
# Mutator for root expression regexp wrapper
class RootExpression < Node
handle(:regexp_root_expression)
# Emit mutations for children of root node
#
# @return [undefined]
def dispatch
children.each_index(&method(:mutate_child))
end
end # RootExpression
# Mutator for beginning of line anchor `^`
class BeginningOfLineAnchor < Node
handle(:regexp_bol_anchor)
# Emit mutations
#
# Replace `^` with `\A`
#
# @return [undefined]
def dispatch
emit(s(:regexp_bos_anchor))
end
end # BeginningOfLineAnchor
end # Regexp
end # Node
end # Mutator
end # Mutant

View file

@ -25,7 +25,7 @@ module Mutant
(children - MUTATED_FLAGS)
end
end # Generic
end # Regopt
end # Node
end # Mutator
end # Mutant

View file

@ -1,45 +0,0 @@
Mutant::Meta::Example.add :regexp do
source '/foo/'
singleton_mutations
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source '/#{foo.bar}n/'
singleton_mutations
mutation '/#{foo}n/'
mutation '/#{self.bar}n/'
mutation '/#{nil}n/'
mutation '/#{self}n/'
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source 'true if /foo/'
singleton_mutations
mutation 'false if /foo/'
mutation 'nil if /foo/'
mutation 'true if true'
mutation 'true if false'
mutation 'true if nil'
mutation 'true'
# match all inputs
mutation 'true if //'
# match no input
mutation 'true if /nomatch\A/'
end

106
meta/regexp.rb Normal file
View file

@ -0,0 +1,106 @@
Mutant::Meta::Example.add :regexp do
source '/foo/'
singleton_mutations
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source '/#{foo.bar}n/'
singleton_mutations
mutation '/#{foo}n/'
mutation '/#{self.bar}n/'
mutation '/#{nil}n/'
mutation '/#{self}n/'
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source '/#{foo}/'
singleton_mutations
mutation '/#{self}/'
mutation '/#{nil}/'
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source '/#{foo}#{nil}/'
singleton_mutations
mutation '/#{nil}#{nil}/'
mutation '/#{self}#{nil}/'
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source '//'
singleton_mutations
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source 'true if /foo/'
singleton_mutations
mutation 'false if /foo/'
mutation 'nil if /foo/'
mutation 'true if true'
mutation 'true if false'
mutation 'true if nil'
mutation 'true'
# match all inputs
mutation 'true if //'
# match no input
mutation 'true if /nomatch\A/'
end
Mutant::Meta::Example.add :regexp do
source '/(?(1)(foo)(bar))/'
singleton_mutations
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Pathname
.glob(Pathname.new(__dir__).join('regexp', '*.rb'))
.sort
.each(&Kernel.public_method(:require))
# Re-register examples for all regular expression nodes for node_type `:regexp`
Mutant::Meta::Example::ALL.each do |example|
next unless example.node_type.to_s.start_with?('regexp_')
Mutant::Meta::Example::ALL << example.with(node_type: :regexp)
end

View file

@ -0,0 +1,13 @@
Mutant::Meta::Example.add :regexp_bol_anchor do
source '/^/'
singleton_mutations
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
mutation '/\\A/'
end

View file

@ -0,0 +1,26 @@
Mutant::Meta::Example.add :regexp_bos_anchor do
source '/\A/'
singleton_mutations
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end
Mutant::Meta::Example.add :regexp_bos_anchor do
source '/^#{a}/'
singleton_mutations
mutation '/^#{nil}/'
mutation '/^#{self}/'
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
end

View file

@ -0,0 +1,13 @@
Mutant::Meta::Example.add :regexp_root_expression do
source '/^/'
singleton_mutations
# match all inputs
mutation '//'
# match no input
mutation '/nomatch\A/'
mutation '/\\A/'
end

View file

@ -35,6 +35,7 @@ Gem::Specification.new do |gem|
gem.add_runtime_dependency('equalizer', '~> 0.0.9')
gem.add_runtime_dependency('anima', '~> 0.3.0')
gem.add_runtime_dependency('concord', '~> 0.1.5')
gem.add_runtime_dependency('regexp_parser', '~> 0.3.3')
gem.add_development_dependency('devtools', '~> 0.1.4')
gem.add_development_dependency('bundler', '~> 1.10')

View file

@ -88,6 +88,15 @@
- optional/capi/string_spec.rb
'#<RegexpError: invalid multibyte escape: /\xAA/>':
- language/regexp/escapes_spec.rb
"#<Regexp::Scanner::PrematureEndError: Premature end of pattern at #{str}>":
- language/regexp/interpolation_spec.rb
- name: regexp_parser
namespace: Regexp
repo_uri: 'https://github.com/ammar/regexp_parser.git'
mutation_coverage: false
mutation_generation: true
expect_coverage: 0 # not run
expected_errors: {}
- name: auom
namespace: AUOM
repo_uri: 'https://github.com/mbj/auom.git'

View file

@ -250,7 +250,7 @@ module MutantSpec
def initialize(*error_info)
super(MESSAGE % error_info)
end
end
end # UnnecessaryExpectation
include Concord.new(:map), Adamantium

View file

@ -0,0 +1,14 @@
RSpec.describe Mutant::AST::Regexp, '.supported?' do
subject { described_class.supported?(expression) }
let(:expression) { Regexp::Parser.parse(regexp) }
let(:regexp) { /foo/ }
it { should be(true) }
context 'conditional regular expressions' do
let(:regexp) { /((?(1)(foo)(bar)))/ }
it { should be(false) }
end
end

View file

@ -0,0 +1,19 @@
RSpec.describe Mutant::AST::Regexp::Transformer::LookupTable::Table do
subject { table.lookup(:regexp_fake_thing) }
let(:expression_class) { class_double(Regexp::Expression) }
let(:table) do
described_class.create(
[:regexp_fake_thing, %i[thing fake], expression_class]
)
end
its(:token) { should eql(Regexp::Token.new(:thing, :fake)) }
its(:regexp_class) { should be(expression_class) }
it 'exposes list of types' do
expect(table.types).to eql([:regexp_fake_thing])
end
end

View file

@ -0,0 +1,33 @@
RSpec.describe Mutant::AST::Regexp::Transformer::LookupTable do
subject(:pair) { mapper.new(s(:regexp_fake)).pair }
let(:table) { instance_double(described_class::Table) }
let(:token) { ::Regexp::Token.new }
let(:klass) { ::Regexp::Expression }
let(:mapping) do
described_class::Mapping.new(token, klass)
end
let(:mapper) do
fake_table = table
Class.new do
include Concord.new(:node), Mutant::AST::Regexp::Transformer::LookupTable
const_set(:TABLE, fake_table)
def pair
[expression_token, expression_class]
end
end
end
before do
allow(table).to receive(:lookup).with(:regexp_fake).and_return(mapping)
end
it 'constructs regexp lookup table' do
expect(pair).to eql([token, klass])
end
end

View file

@ -0,0 +1,19 @@
RSpec.describe Mutant::AST::Regexp::Transformer do
before do
stub_const("#{described_class}::REGISTRY", Mutant::Registry.new)
end
it 'registers types to a given class' do
klass = Class.new(described_class) { register(:regexp_bos_anchor) }
expect(described_class.lookup(:regexp_bos_anchor)).to be(klass)
end
it 'rejects duplicate registrations' do
Class.new(described_class) { register(:regexp_bos_anchor) }
expect { Class.new(described_class) { register(:regexp_bos_anchor) } }
.to raise_error(Mutant::Registry::RegistryError)
.with_message('Duplicate type registration: :regexp_bos_anchor')
end
end

View file

@ -0,0 +1,607 @@
module RegexpSpec
class Expression < SimpleDelegator
NO_EXPRESSIONS = Object.new.freeze
include Equalizer.new(:type, :token, :text, :quantifier, :expressions)
def quantifier
return Quantifier::NONE unless quantified?
Quantifier.new(super())
end
def expressions
return NO_EXPRESSIONS if terminal?
super().map(&self.class.public_method(:new))
end
class Quantifier < SimpleDelegator
NONE = Object.new.freeze
include Equalizer.new(:token, :text, :mode, :min, :max)
end # Quantifier
end # Expression
RSpec.shared_context 'regexp transformation' do
let(:parsed) { Regexp::Parser.parse(regexp) }
let(:ast) { Mutant::AST::Regexp.to_ast(parsed) }
let(:expression) { Mutant::AST::Regexp.to_expression(ast) }
def expect_frozen_expression(expression, root = expression)
expect(expression.frozen?).to(
be(true),
"Expected #{root} to be deep frozen"
)
return if expression.terminal?
expression.expressions.each do |subexpression|
expect_frozen_expression(subexpression, root)
end
end
it 'transforms into ast' do
expect(ast).to eql(expected)
end
it 'deep freezes expression mapping' do
expect_frozen_expression(expression)
end
it 'transforms ast back to expression' do
expect(Expression.new(expression)).to eql(Expression.new(parsed))
end
end
RSpec.shared_context 'regexp round trip' do
let(:round_trip) { expression.to_re }
it 'round trips Regexp' do
expect(round_trip).to eql(regexp)
end
end
def self.expect_mapping(regexp, type, &block)
RSpec.describe Mutant::AST::Regexp::Transformer.lookup(type) do
context "when mapping #{regexp.inspect}" do
let(:regexp) { regexp }
let(:expected, &block)
include_context 'regexp transformation'
return if regexp.encoding.name.eql?('ASCII-8BIT')
include_context 'regexp round trip'
end
end
end
end # RegexpSpec
RegexpSpec.expect_mapping(/A/, :regexp_root_expression) do
s(:regexp_root_expression,
s(:regexp_literal_literal, 'A'))
end
RegexpSpec.expect_mapping(/\p{Alpha}/, :regexp_alpha_property) do
s(:regexp_root_expression,
s(:regexp_alpha_property))
end
RegexpSpec.expect_mapping(/foo|bar/, :regexp_alternation_meta) do
s(:regexp_root_expression,
s(:regexp_alternation_meta,
s(:regexp_sequence_expression,
s(:regexp_literal_literal, 'foo')),
s(:regexp_sequence_expression,
s(:regexp_literal_literal, 'bar'))))
end
RegexpSpec.expect_mapping(/(?>a)/, :regexp_atomic_group) do
s(:regexp_root_expression,
s(:regexp_atomic_group,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/\\/, :regexp_backslash_escape) do
s(:regexp_root_expression,
s(:regexp_backslash_escape, '\\\\'))
end
RegexpSpec.expect_mapping(/^/, :regexp_bol_anchor) do
s(:regexp_root_expression,
s(:regexp_bol_anchor))
end
RegexpSpec.expect_mapping(/\A/, :regexp_bos_anchor) do
s(:regexp_root_expression,
s(:regexp_bos_anchor))
end
RegexpSpec.expect_mapping(/(foo)/, :regexp_capture_group) do
s(:regexp_root_expression,
s(:regexp_capture_group,
s(:regexp_literal_literal, 'foo')))
end
RegexpSpec.expect_mapping(/()\1/, :regexp_number_backref) do
s(:regexp_root_expression,
s(:regexp_capture_group),
s(:regexp_number_backref, '\\1'))
end
RegexpSpec.expect_mapping(/(a)*/, :regexp_capture_group) do
s(:regexp_root_expression,
s(:regexp_greedy_zero_or_more, 0, -1,
s(:regexp_capture_group,
s(:regexp_literal_literal, 'a'))))
end
RegexpSpec.expect_mapping(/\r/, :regexp_carriage_escape) do
s(:regexp_root_expression,
s(:regexp_carriage_escape))
end
RegexpSpec.expect_mapping(/\a/, :regexp_bell_escape) do
s(:regexp_root_expression,
s(:regexp_bell_escape))
end
RegexpSpec.expect_mapping(/\?/, :regexp_zero_or_one_escape) do
s(:regexp_root_expression,
s(:regexp_zero_or_one_escape))
end
RegexpSpec.expect_mapping(/\|/, :regexp_alternation_escape) do
s(:regexp_root_expression,
s(:regexp_alternation_escape))
end
RegexpSpec.expect_mapping(/\c2/, :regexp_control_escape) do
s(:regexp_root_expression,
s(:regexp_control_escape, '\\c2'))
end
RegexpSpec.expect_mapping(/\M-B/n, :regexp_meta_sequence_escape) do
s(:regexp_root_expression,
s(:regexp_meta_sequence_escape, '\M-B'))
end
RegexpSpec.expect_mapping(/\K/, :regexp_mark_keep) do
s(:regexp_root_expression,
s(:regexp_mark_keep))
end
RegexpSpec.expect_mapping(/\e/, :regexp_escape_escape) do
s(:regexp_root_expression,
s(:regexp_escape_escape))
end
RegexpSpec.expect_mapping(/\f/, :regexp_form_feed_escape) do
s(:regexp_root_expression,
s(:regexp_form_feed_escape))
end
RegexpSpec.expect_mapping(/\v/, :regexp_vertical_tab_escape) do
s(:regexp_root_expression,
s(:regexp_vertical_tab_escape))
end
RegexpSpec.expect_mapping(/\e/, :regexp_escape_escape) do
s(:regexp_root_expression,
s(:regexp_escape_escape))
end
RegexpSpec.expect_mapping(/[ab]+/, :regexp_character_set) do
s(:regexp_root_expression,
s(:regexp_greedy_one_or_more, 1, -1,
s(:regexp_character_set, 'a', 'b')))
end
RegexpSpec.expect_mapping(/[ab]/, :regexp_character_set) do
s(:regexp_root_expression,
s(:regexp_character_set, 'a', 'b'))
end
RegexpSpec.expect_mapping(/[a-j]/, :regexp_character_set) do
s(:regexp_root_expression,
s(:regexp_character_set, 'a-j'))
end
RegexpSpec.expect_mapping(/\u{9879}/, :regexp_codepoint_list_escape) do
s(:regexp_root_expression,
s(:regexp_codepoint_list_escape, '\\u{9879}'))
end
RegexpSpec.expect_mapping(/(?#foo)/, :regexp_comment_group) do
s(:regexp_root_expression,
s(:regexp_comment_group, '(?#foo)'))
end
RegexpSpec.expect_mapping(/(?x-: # comment
)/, :regexp_comment_free_space) do
s(:regexp_root_expression,
s(:regexp_options_group, {
m: false,
i: false,
x: true,
d: false,
a: false,
u: false
},
s(:regexp_whitespace_free_space, ' '),
s(:regexp_comment_free_space, "# comment\n")))
end
RegexpSpec.expect_mapping(/\d/, :regexp_digit_type) do
s(:regexp_root_expression,
s(:regexp_digit_type))
end
RegexpSpec.expect_mapping(/\./, :regexp_dot_escape) do
s(:regexp_root_expression,
s(:regexp_dot_escape))
end
RegexpSpec.expect_mapping(/.+/, :regexp_dot_meta) do
s(:regexp_root_expression,
s(:regexp_greedy_one_or_more, 1, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/$/, :regexp_eol_anchor) do
s(:regexp_root_expression,
s(:regexp_eol_anchor))
end
RegexpSpec.expect_mapping(/\$/, :regexp_eol_escape) do
s(:regexp_root_expression,
s(:regexp_eol_escape))
end
RegexpSpec.expect_mapping(/\z/, :regexp_eos_anchor) do
s(:regexp_root_expression,
s(:regexp_eos_anchor))
end
RegexpSpec.expect_mapping(/\Z/, :regexp_eos_ob_eol_anchor) do
s(:regexp_root_expression,
s(:regexp_eos_ob_eol_anchor))
end
RegexpSpec.expect_mapping(/a{1,}/, :regexp_greedy_interval) do
s(:regexp_root_expression,
s(:regexp_greedy_interval, 1, -1,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/.{2}/, :regexp_greedy_interval) do
s(:regexp_root_expression,
s(:regexp_greedy_interval, 2, 2,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.{3,5}/, :regexp_greedy_interval) do
s(:regexp_root_expression,
s(:regexp_greedy_interval, 3, 5,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.{,3}/, :regexp_greedy_interval) do
s(:regexp_root_expression,
s(:regexp_greedy_interval, 0, 3,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.+/, :regexp_greedy_one_or_more) do
s(:regexp_root_expression,
s(:regexp_greedy_one_or_more, 1, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/[ab]+/, :regexp_greedy_one_or_more) do
s(:regexp_root_expression,
s(:regexp_greedy_one_or_more, 1, -1,
s(:regexp_character_set, 'a', 'b')))
end
RegexpSpec.expect_mapping(/(a)*/, :regexp_greedy_zero_or_more) do
s(:regexp_root_expression,
s(:regexp_greedy_zero_or_more, 0, -1,
s(:regexp_capture_group,
s(:regexp_literal_literal, 'a'))))
end
RegexpSpec.expect_mapping(/.*/, :regexp_greedy_zero_or_more) do
s(:regexp_root_expression,
s(:regexp_greedy_zero_or_more, 0, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.?/, :regexp_greedy_zero_or_one) do
s(:regexp_root_expression,
s(:regexp_greedy_zero_or_one, 0, 1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/\)/, :regexp_group_close_escape) do
s(:regexp_root_expression,
s(:regexp_group_close_escape))
end
RegexpSpec.expect_mapping(/\(/, :regexp_group_open_escape) do
s(:regexp_root_expression,
s(:regexp_group_open_escape))
end
RegexpSpec.expect_mapping(/\xFF/n, :regexp_hex_escape) do
s(:regexp_root_expression,
s(:regexp_hex_escape, '\\xFF'))
end
RegexpSpec.expect_mapping(/\}/, :regexp_interval_close_escape) do
s(:regexp_root_expression,
s(:regexp_interval_close_escape))
end
RegexpSpec.expect_mapping(/\{/, :regexp_interval_open_escape) do
s(:regexp_root_expression,
s(:regexp_interval_open_escape))
end
RegexpSpec.expect_mapping(/\p{L}/, :regexp_letter_any_property) do
s(:regexp_root_expression,
s(:regexp_letter_any_property))
end
RegexpSpec.expect_mapping(/\-/, :regexp_literal_escape) do
s(:regexp_root_expression,
s(:regexp_literal_escape, '\\-'))
end
RegexpSpec.expect_mapping(/\ /, :regexp_literal_escape) do
s(:regexp_root_expression,
s(:regexp_literal_escape, '\\ '))
end
RegexpSpec.expect_mapping(/\#/, :regexp_literal_escape) do
s(:regexp_root_expression,
s(:regexp_literal_escape, '\\#'))
end
RegexpSpec.expect_mapping(/\:/, :regexp_literal_escape) do
s(:regexp_root_expression,
s(:regexp_literal_escape, '\\:'))
end
RegexpSpec.expect_mapping(/\</, :regexp_literal_escape) do
s(:regexp_root_expression,
s(:regexp_literal_escape, '\\<'))
end
RegexpSpec.expect_mapping(/foo/, :regexp_literal_literal) do
s(:regexp_root_expression,
s(:regexp_literal_literal, 'foo'))
end
RegexpSpec.expect_mapping(/a+/, :regexp_literal_literal) do
s(:regexp_root_expression,
s(:regexp_greedy_one_or_more, 1, -1,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/(?=a)/, :regexp_lookahead_assertion) do
s(:regexp_root_expression,
s(:regexp_lookahead_assertion,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/(?<=a)/, :regexp_lookbehind_assertion) do
s(:regexp_root_expression,
s(:regexp_lookbehind_assertion,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/\G/, :regexp_match_start_anchor) do
s(:regexp_root_expression,
s(:regexp_match_start_anchor))
end
RegexpSpec.expect_mapping(/(?<foo>)/, :regexp_named_group) do
s(:regexp_root_expression,
s(:regexp_named_group, '(?<foo>'))
end
RegexpSpec.expect_mapping(/(?<a>)\g<a>/, :regexp_name_call_backref) do
s(:regexp_root_expression,
s(:regexp_named_group, '(?<a>'),
s(:regexp_name_call_backref, '\\g<a>'))
end
RegexpSpec.expect_mapping(/\n/, :regexp_newline_escape) do
s(:regexp_root_expression,
s(:regexp_newline_escape))
end
RegexpSpec.expect_mapping(/(?!a)/, :regexp_nlookahead_assertion) do
s(:regexp_root_expression,
s(:regexp_nlookahead_assertion,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/(?<!a)/, :regexp_nlookbehind_assertion) do
s(:regexp_root_expression,
s(:regexp_nlookbehind_assertion,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/\D/, :regexp_nondigit_type) do
s(:regexp_root_expression,
s(:regexp_nondigit_type))
end
RegexpSpec.expect_mapping(/\S/, :regexp_nonspace_type) do
s(:regexp_root_expression,
s(:regexp_nonspace_type))
end
RegexpSpec.expect_mapping(/\B/, :regexp_nonword_boundary_anchor) do
s(:regexp_root_expression,
s(:regexp_nonword_boundary_anchor))
end
RegexpSpec.expect_mapping(/\W/, :regexp_nonword_type) do
s(:regexp_root_expression,
s(:regexp_nonword_type))
end
RegexpSpec.expect_mapping(/\+/, :regexp_one_or_more_escape) do
s(:regexp_root_expression,
s(:regexp_one_or_more_escape))
end
RegexpSpec.expect_mapping(/(?i-:a)+/, :regexp_options_group) do
s(:regexp_root_expression,
s(:regexp_greedy_one_or_more, 1, -1,
s(:regexp_options_group,
{
m: false,
i: true,
x: false,
d: false,
a: false,
u: false
},
s(:regexp_literal_literal, 'a'))))
end
RegexpSpec.expect_mapping(/(?x-: #{"\n"} )/, :regexp_whitespace_free_space) do
s(:regexp_root_expression,
s(:regexp_options_group,
{
m: false,
i: false,
x: true,
d: false,
a: false,
u: false
},
s(:regexp_whitespace_free_space, " \n ")))
end
RegexpSpec.expect_mapping(/(?:a)/, :regexp_passive_group) do
s(:regexp_root_expression,
s(:regexp_passive_group,
s(:regexp_literal_literal, 'a')))
end
RegexpSpec.expect_mapping(/.{1,3}+/, :regexp_possessive_interval) do
s(:regexp_root_expression,
s(:regexp_possessive_interval, 1, 3,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.++/, :regexp_possessive_one_or_more) do
s(:regexp_root_expression,
s(:regexp_possessive_one_or_more, 1, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.*+/, :regexp_possessive_zero_or_more) do
s(:regexp_root_expression,
s(:regexp_possessive_zero_or_more, 0, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.?+/, :regexp_possessive_zero_or_one) do
s(:regexp_root_expression,
s(:regexp_possessive_zero_or_one, 0, 1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.{1,3}?/, :regexp_reluctant_interval) do
s(:regexp_root_expression,
s(:regexp_reluctant_interval, 1, 3,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.+?/, :regexp_reluctant_one_or_more) do
s(:regexp_root_expression,
s(:regexp_reluctant_one_or_more, 1, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/.*?/, :regexp_reluctant_zero_or_more) do
s(:regexp_root_expression,
s(:regexp_reluctant_zero_or_more, 0, -1,
s(:regexp_dot_meta)))
end
RegexpSpec.expect_mapping(/\p{Arabic}/, :regexp_script_arabic_property) do
s(:regexp_root_expression,
s(:regexp_script_arabic_property))
end
RegexpSpec.expect_mapping(/\p{Han}/, :regexp_script_han_property) do
s(:regexp_root_expression,
s(:regexp_script_han_property))
end
RegexpSpec.expect_mapping(/\p{Hangul}/, :regexp_script_hangul_property) do
s(:regexp_root_expression,
s(:regexp_script_hangul_property))
end
RegexpSpec.expect_mapping(/\p{Hiragana}/, :regexp_script_hiragana_property) do
s(:regexp_root_expression,
s(:regexp_script_hiragana_property))
end
RegexpSpec.expect_mapping(/\p{Katakana}/, :regexp_script_katakana_property) do
s(:regexp_root_expression,
s(:regexp_script_katakana_property))
end
RegexpSpec.expect_mapping(/foo|bar/, :regexp_sequence_expression) do
s(:regexp_root_expression,
s(:regexp_alternation_meta,
s(:regexp_sequence_expression,
s(:regexp_literal_literal, 'foo')),
s(:regexp_sequence_expression,
s(:regexp_literal_literal, 'bar'))))
end
RegexpSpec.expect_mapping(/\]/, :regexp_set_close_escape) do
s(:regexp_root_expression,
s(:regexp_set_close_escape))
end
RegexpSpec.expect_mapping(/\[/, :regexp_set_open_escape) do
s(:regexp_root_expression,
s(:regexp_set_open_escape))
end
RegexpSpec.expect_mapping(/\s/, :regexp_space_type) do
s(:regexp_root_expression,
s(:regexp_space_type))
end
RegexpSpec.expect_mapping(/\t/, :regexp_tab_escape) do
s(:regexp_root_expression,
s(:regexp_tab_escape, '\\t'))
end
RegexpSpec.expect_mapping(/\b/, :regexp_word_boundary_anchor) do
s(:regexp_root_expression,
s(:regexp_word_boundary_anchor))
end
RegexpSpec.expect_mapping(/\w/, :regexp_word_type) do
s(:regexp_root_expression,
s(:regexp_word_type))
end
RegexpSpec.expect_mapping(/\*/, :regexp_zero_or_more_escape) do
s(:regexp_root_expression,
s(:regexp_zero_or_more_escape))
end