# frozen_string_literal: truemoduleMutantclassMutatorclassRegexp<selfdefself.regexp_body(node)*body,_options=node.childrenbody.mapdo|child|returnunlesschild.type.equal?(:str)child.childrenend.joinendclassRegistryincludeAnima.new(:contents)definitializesuper(contents: {})enddefregister(expression_class,mutator_class)(contents[expression_class]||=[])<<mutator_classselfenddeflookup(expression_class)contents.fetch(expression_class,[]).push(Quantifier)endend# RegistryREGISTRY=Registry.new# mutant:disable - boot time codedefself.handle(*types)types.eachdo|type|self::REGISTRY.register(type,self)endendprivate_class_method:handledefself.mutate(expression)REGISTRY.lookup(expression.class).map{|mutator|mutator.call(input: expression,parent: nil)}.reduce(&:merge)endprivatedefsubexpressionsinput.expressionsenddefmk_dupMarshal.load(Marshal.dump(input))enddefemit_expression(klass:,text:)emit(klass.construct(text:).tapdo|new|subexpressions.eachdo|expression|new<<Marshal.load(Marshal.dump(expression))endend)enddefemit_passive_groupemit_expression(klass: ::Regexp::Expression::Group::Passive,text: '(?:')endclassAlternation<selfhandle(::Regexp::Expression::Alternation)defdispatchsubexpressions.each_indexdo|index|emit(mk_dup.tap{|new|new.expressions.delete_at(index)})endendendclassQuantifier<selfMAP={'*'=>'+','*+'=>'++','*?'=>'+?'}.freezedefdispatchreturnunlessinput.quantifieremit_removalemit_replacementenddefemit_removalemit(mk_dup.tap{|new|new.quantifier=nil})enddefemit_replacementnew_text=MAP[input.quantifier.text]orreturnemit(mk_dup.tap{|new|new.quantifier.text=new_text})endendclassReplacement<selfns=::Regexp::Expressiondual_table=[[ns::Anchor::WordBoundary,'\\b',ns::Anchor::NonWordBoundary,'\\B'],[ns::CharacterType::Digit,'\\d',ns::CharacterType::NonDigit,'\\D'],[ns::CharacterType::ExtendedGrapheme,'\\X',ns::CharacterType::Linebreak,'\\R'],[ns::CharacterType::Hex,'\\h',ns::CharacterType::NonHex,'\\H'],[ns::CharacterType::Space,'\\s',ns::CharacterType::NonSpace,'\\S'],[ns::CharacterType::Word,'\\w',ns::CharacterType::NonWord,'\\W']]MAP=dual_table.flat_mapdo|(left_key,left_text,right_key,right_text)|[[left_key,[right_key,right_text]],[right_key,[left_key,left_text]]]end.to_hMAP[ns::Anchor::BeginningOfLine]=[ns::Anchor::BeginningOfString,'\\A']MAP[ns::Anchor::EndOfLine]=[ns::Anchor::EndOfString,'\\z']MAP[ns::Anchor::EndOfStringOrBeforeEndOfLine]=[ns::Anchor::EndOfString,'\\z']MAP.freezehandle(*MAP.keys)defdispatchklass,text=MAP.fetch(input.class)emit(klass.construct(text:).tap{|new|new.quantifier=input.quantifier})endendclassGroupCapturePositional<selfhandle(::Regexp::Expression::Group::Capture)privatedefdispatchemit_passive_groupunlesssubexpressions.empty?endendclassGroupCaptureNamed<selfhandle(::Regexp::Expression::Group::Named)privatedefdispatchreturnifinput.name.start_with?('_')||subexpressions.empty?emit_passive_groupemit_expression(klass: ::Regexp::Expression::Group::Named,text: "(?<_#{input.name}>")endendclassRecurse<selfns=::Regexp::Expression# This list of nodes with subexessions is not yet complete.handle(ns::Assertion::Base)handle(ns::Assertion::Lookahead)handle(ns::Assertion::Lookbehind)handle(ns::Assertion::NegativeLookahead)handle(ns::Assertion::NegativeLookbehind)handle(ns::Alternative)handle(ns::Alternation)handle(ns::Sequence)handle(ns::CharacterSet)handle(ns::CharacterSet::Range)handle(ns::Conditional::Branch)handle(ns::Conditional::Expression)handle(ns::Group::Absence)handle(ns::Group::Atomic)handle(ns::Group::Capture)handle(ns::Group::Comment)handle(ns::Group::Named)handle(ns::Group::Options)handle(ns::Group::Passive)handle(ns::Root)privatedefdispatchsubexpressions.each_with_indexdo|expression,index|self.class.mutate(expression).eachdo|new_expression|emit(mk_dup.tap{|new|new.expressions[index]=new_expression})endendendendend# Regexpend# Mutatorend# Mutant