Merge pull request #565 from mbj/feature/regexp_parser
Mutate regexp body
This commit is contained in:
commit
2b96c184d8
30 changed files with 1769 additions and 52 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 16
|
||||
total_score: 1174
|
||||
total_score: 1280
|
||||
|
|
|
@ -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
40
lib/mutant/ast/regexp.rb
Normal 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
|
185
lib/mutant/ast/regexp/transformer.rb
Normal file
185
lib/mutant/ast/regexp/transformer.rb
Normal 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
|
39
lib/mutant/ast/regexp/transformer/alternative.rb
Normal file
39
lib/mutant/ast/regexp/transformer/alternative.rb
Normal 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
|
46
lib/mutant/ast/regexp/transformer/character_set.rb
Normal file
46
lib/mutant/ast/regexp/transformer/character_set.rb
Normal 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
|
97
lib/mutant/ast/regexp/transformer/direct.rb
Normal file
97
lib/mutant/ast/regexp/transformer/direct.rb
Normal 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
|
66
lib/mutant/ast/regexp/transformer/options_group.rb
Normal file
66
lib/mutant/ast/regexp/transformer/options_group.rb
Normal 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
|
112
lib/mutant/ast/regexp/transformer/quantifier.rb
Normal file
112
lib/mutant/ast/regexp/transformer/quantifier.rb
Normal 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
|
50
lib/mutant/ast/regexp/transformer/recursive.rb
Normal file
50
lib/mutant/ast/regexp/transformer/recursive.rb
Normal 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
|
29
lib/mutant/ast/regexp/transformer/root.rb
Normal file
29
lib/mutant/ast/regexp/transformer/root.rb
Normal 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
|
56
lib/mutant/ast/regexp/transformer/text.rb
Normal file
56
lib/mutant/ast/regexp/transformer/text.rb
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
44
lib/mutant/mutator/node/regexp.rb
Normal file
44
lib/mutant/mutator/node/regexp.rb
Normal 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
|
|
@ -25,7 +25,7 @@ module Mutant
|
|||
(children - MUTATED_FLAGS)
|
||||
end
|
||||
|
||||
end # Generic
|
||||
end # Regopt
|
||||
end # Node
|
||||
end # Mutator
|
||||
end # Mutant
|
||||
|
|
|
@ -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
106
meta/regexp.rb
Normal 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
|
13
meta/regexp/regexp_bol_anchor.rb
Normal file
13
meta/regexp/regexp_bol_anchor.rb
Normal 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
|
26
meta/regexp/regexp_bos_anchor.rb
Normal file
26
meta/regexp/regexp_bos_anchor.rb
Normal 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
|
13
meta/regexp/regexp_root_expression.rb
Normal file
13
meta/regexp/regexp_root_expression.rb
Normal 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
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -250,7 +250,7 @@ module MutantSpec
|
|||
def initialize(*error_info)
|
||||
super(MESSAGE % error_info)
|
||||
end
|
||||
end
|
||||
end # UnnecessaryExpectation
|
||||
|
||||
include Concord.new(:map), Adamantium
|
||||
|
||||
|
|
14
spec/unit/mutant/ast/regexp/supported_predicate_spec.rb
Normal file
14
spec/unit/mutant/ast/regexp/supported_predicate_spec.rb
Normal 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
|
|
@ -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
|
33
spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb
Normal file
33
spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb
Normal 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
|
19
spec/unit/mutant/ast/regexp/transformer_spec.rb
Normal file
19
spec/unit/mutant/ast/regexp/transformer_spec.rb
Normal 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
|
607
spec/unit/mutant/ast/regexp_spec.rb
Normal file
607
spec/unit/mutant/ast/regexp_spec.rb
Normal 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
|
Loading…
Add table
Reference in a new issue