Switch-Right

 view release on metacpan or  search on metacpan

lib/Switch/Right.pm  view on Meta::CPAN

                    |   (?&PerlFieldDeclaration)
                    |   (?&PerlControlBlock)
                    |   (?&PerlFormat)
                    |
                        # POSTFIX when HAS TO BE REWRITTEN BEFORE OTHER POSTFIX MODIFIERS ARE MATCHED...
                        (?<MATCH>
                            (?<EXPR> (?>(?&PerlExpression))    (?>(?&PerlOWS))      )
                                     (?= when \b )
                            (?<MOD>  (?&PerlStatementModifier) (?>(?&PerlOWSOrEND)) )
                            (?<END>  (?> ; | (?= \} | \z ))                         )
                        )
                        (?{ my $len = length($+{MATCH});
                            unshift @target_pos, {
                                expr        => $+{EXPR},
                                mod         => substr($+{MOD},4),
                                end         => $+{END},
                                from        => pos() - $len,
                                len         => $len,
                            }
                        })
                    |
                        (?>(?&PerlExpression))          (?>(?&PerlOWS))
                        (?&PerlStatementModifier)?+     (?>(?&PerlOWSOrEND))
                        (?> ; | (?= \} | \z ))
                    |   (?&PerlBlock)
                    |   ;
                    )
                | # A yada-yada...
                    \.\.\. (?>(?&PerlOWSOrEND))
                    (?> ; | (?= \} | \z ))

                | # Just a label...
                    (?>(?&PerlLabel)) (?>(?&PerlOWSOrEND))
                    (?> ; | (?= \} | \z ))

                | # Just an empty statement...
                    (?>(?&PerlOWS)) ;
                )
            )
        )
        $PPR::X::GRAMMAR
    }xms;

    # Replace each postfix "when"...
    for my $pos (@target_pos) {
        # Unique ID for the "when" (needed by continue())...
        state $ID; $ID++;
        state $JUNCTIVE_EXPR = qr{
            $OWS  (?>(?<JUNC> (?: any | all | none ) $OWS => $OWS | ))  (?<EXPR> .* )
            $PPR::GRAMMAR
        }xms;

        # Unpack and enchant the "when" expression...
        my ($JUNCTIVE, $MOD_EXPR) = (q{}, $pos->{mod});
        if ($MOD_EXPR =~ $JUNCTIVE_EXPR) {
            ($JUNCTIVE, $MOD_EXPR) = ( $+{JUNC}, _apply_term_magic($+{EXPR}) );
        }

        # Convert postfix "when" to a postfix "if" (preserving Afterwhen info for continue())...
        substr($BLOCK, $pos->{from}, $pos->{len})
            = "BEGIN { \$^H{'Switch::Right/Afterwhenprev'} = \$^H{'Switch::Right/Afterwhen'};"
            . "        \$^H{'Switch::Right/Afterwhen'} = 'Afterpostfixwhen$ID'; }"
            . "$pos->{expr}, break if smartmatch(\$_, $JUNCTIVE scalar($MOD_EXPR))"
            . ";Afterpostfixwhen$ID:"
            . "BEGIN { \$^H{'Switch::Right/Afterwhen'} = \$^H{'Switch::Right/Afterwhenprev'}; }"
            . $pos->{end};
    }

    return $BLOCK;
}

# Change the target expression of a "when" to implement all the magic behaviours...
sub _apply_term_magic ($EXPR) {

    # Apply compile-time expression folding...
    $EXPR = _simplify_expr($EXPR);

    # Adjust flip..flips to canonical booleans...
    if ($EXPR =~ /\.\./ && $EXPR =~ $FLIP_FLOP) {
        return "!!($EXPR)";
    }

    # An @array or %hash gets enreferenced and then smartmatched.
    # An @array[@slice] or %kv[@slice] gets appropriately wrapped and then smartmatched.
    # Anything else is evaluated as-is...
    return ($EXPR =~ /[\@%]/ && $EXPR =~ $CONTAINER_VARIABLE) ? "\\$EXPR"
         : ($EXPR =~ /[\@]/  && $EXPR =~ $ARRAY_SLICE)        ?  "[$EXPR]"
         : ($EXPR =~ /[\%]/  && $EXPR =~ $HASH_SLICE)         ?  "{$EXPR}"
         :                                                         $EXPR;
}


# Reduce a compile-time expression to what the compiler actually sees...
# (Essential because that's what when() actually sees and how it decides
#  whether or not smartmatch is magically distributive over a boolean expression)...
sub _simplify_expr ($code) {
    no warnings;
    use B::Deparse;
    use builtin qw<true false>;
    state $deparse = B::Deparse->new;
    return $deparse->coderef2text(eval qq{no strict; sub{ANSWER( scalar($code) );DONE()}})
                =~ s{.* ANSWER \( \s* scalar \s* (.*) \) \s* ; \s* DONE() .* \z}{$1}gxmsr;
}


# Implement the new simpler, but shinier smartmatch operator...
# (Every one of the following four variants could each have been a set of multiple variants,
#  but this way is currently still significantly faster)...

multi smartmatch ($left, $right) {
    # The standard error message for args that are objects (and which shouldn't be)...
    state $OBJ_ARG = "Smartmatching an object breaks encapsulation";

    # Track "use integer" status in original caller (passing it down to nested smartmatches)...
    local $Switch::Right::_use_integer = $Switch::Right::_use_integer // (caller 0)[8] & 0x1;

    # RHS undef only matches LHS undef...
    return !defined $left if !defined $right;

    # RHS distinguished boolean always returns RHS value...
    return $right if is_bool($right);

    # RHS objects use their SMARTMATCH method (if they have one)...
    my $right_type  = reftype($right) // 'VAL';
    if ($right_type ne 'REGEXP' && blessed $right) {



( run in 0.545 second using v1.01-cache-2.11-cpan-524268b4103 )