File Coverage

blib/lib/Switch/Back.pm
Criterion Covered Total %
statement 189 487 38.8
branch 44 216 20.3
condition 9 37 24.3
subroutine 37 51 72.5
pod 0 2 0.0
total 279 793 35.1


line stmt bran cond sub pod time code
1             package Switch::Back;
2              
3 2     2   284117 use 5.036;
  2         10  
4             our $VERSION = '0.000005';
5              
6 2     2   1403 use experimental qw< builtin refaliasing try >;
  2         10748  
  2         16  
7 2     2   2133 use builtin qw< true false blessed created_as_number >;
  2         331  
  2         119  
8 2     2   15 use Scalar::Util qw < looks_like_number >;
  2         5  
  2         136  
9              
10 2     2   3207 use Multi::Dispatch;
  2         1535690  
  2         23  
11 2     2   970 use PPR::X;
  2         22  
  2         99  
12 2     2   11 use Carp qw< croak carp >;
  2         5  
  2         257  
13              
14             # Useful patterns...
15 2     2   163 my $OWS; BEGIN { $OWS = q{(?>(?&PerlOWS))}; }
16             my $CONTAINER_VARIABLE;
17 2     2   128289 BEGIN { $CONTAINER_VARIABLE
18             = qr{ \A (?> (?&PerlVariableArray) | (?&PerlVariableHash)
19             | my $OWS (?> (?&PerlVariableArray) | (?&PerlVariableHash) ) $OWS = .*
20             )
21             \z $PPR::GRAMMAR
22             }xms;
23             }
24             my $ARRAY_SLICE;
25 2     2   108301 BEGIN { $ARRAY_SLICE
26             = qr{ \A (?> (?&PerlArrayAccess)
27             | my $OWS (?&PerlArrayAccess) $OWS = .*
28             )
29             \z $PPR::GRAMMAR
30             }xms;
31             }
32             my $HASH_SLICE;
33 2     2   118754 BEGIN { $HASH_SLICE
34             = qr{ \A (?> (?&PerlHashAccess)
35             | my $OWS (?&PerlHashAccess) $OWS = .*
36             )
37             \z $PPR::GRAMMAR
38             }xms;
39             }
40             my $SMARTMATCHABLE;
41 2     2   119781 BEGIN { $SMARTMATCHABLE
42             = qr{ \A
43             (?> \\ $OWS (?&PerlVariableArray)
44             | \\ $OWS (?&PerlVariableHash)
45             | \\ $OWS & (?&PerlQualifiedIdentifier)
46             | (?&PerlPrefixUnaryOperator) (?&PerlScalarAccess)
47             | (?&PerlScalarAccess) (?&PerlPostfixUnaryOperator)?+
48             | (?&PerlAnonymousArray)
49             | (?&PerlAnonymousHash)
50             | (?&PerlAnonymousSubroutine)
51             | (?&PerlString)
52             | (?&PerlNumber)
53             | (?&PerlQuotelikeQR)
54             | (?&PerlBareword)
55             | undef
56             )
57             $OWS
58             \z $PPR::GRAMMAR
59             }xms;
60             }
61              
62             # Install the new keywords, functions, and smartmatching...
63             sub import {
64             # Export replacement keywords...
65 2     2   34 use Keyword::Simple;
  2         11  
  2         198  
66 2     2   41 Keyword::Simple::define given => \&_given_impl;
67 2         65 Keyword::Simple::define when => \&_when_impl;
68 2         35 Keyword::Simple::define default => \&_default_impl;
69              
70             # Outside a given a 'break' is an error; outside a when a 'continue' is too...
71             {
72 2     2   13 no strict 'refs';
  2         3  
  2         115  
  2         28  
73 2     2   9 no warnings qw< redefine >;
  2         4  
  2         417  
74 2         7 *{caller.'::break'} = \&break;
  2         17  
75 2         5 *{caller.'::continue'} = \&continue;
  2         10  
76             }
77              
78 2         5 # Export smartmatch()...
79 2 0 0 2   304326 multi smartmatch :export;
  2 0 50 2   3  
  2 0   33   828  
  2 0       34  
  2 0       5  
  2 50       2263  
  2 50       3  
  2 50       6  
  2 50       3  
  2 50       14  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         6  
  2         8  
  2         16  
  33         240708  
  33         256  
  33         111  
  33         117  
  0         0  
  33         91  
  0         0  
  33         46  
  33         132  
  33         123  
  33         99  
  33         48  
  33         129  
  0         0  
  0         0  
  2         4  
  2         6  
  2         14  
  2         5  
  2         292  
  2         8  
  2         10  
80             }
81              
82             # Detect and rewrite "pure" given blocks (recursively if necessary)...
83 0     0     sub _pure_given_impl { my ($source) = @_;
84              
85             # Recognize a valid "pure" given block (i.e. containing only when and default blocks)...
86 0           state @pure_statements;
87 0           @pure_statements = ();
88             state $VALIDATE_PURE_GIVEN = qr{
89             \A given (? (? $OWS ) \(
90             (? $OWS ) (?>(? (?&PerlExpression)))
91             (? $OWS ) \)
92             (? $OWS \{ $OWS ) (?>(? (?&PureBlock) )) \}
93             )
94             (?>(? .* ))
95              
96             (?(DEFINE)
97             (? # Distinguish "when", "default", and "given" from other statements...
98             (?:
99             when (? $OWS \( $OWS )
100             (? (?>(?&PerlExpression)))
101             (? $OWS \) $OWS )
102             (?>(? (?&PerlBlock) ))
103             (? $OWS )
104 0           (?{ push @pure_statements, { TYPE => 'when', %+ }; })
105             |
106             default (? $OWS )
107             (?>(? (?&PerlBlock) ))
108             (? $OWS )
109 0           (?{ push @pure_statements, { TYPE => 'default', %+ }; })
110             |
111             (?
112             given \b $OWS \(
113             (?: $OWS (?> any | all | none ) $OWS => )?+
114             $OWS (?>(? (?>(?&PerlExpression))))
115             $OWS \)
116             $OWS (?>(? (?&NestedPureBlock) )) $OWS
117             )
118 0           (?{ push @pure_statements, { TYPE => 'given', %+ }; })
119             |
120             (?! $OWS (?> when | default ) \b )
121             (?>(? (?&PerlStatement) $OWS ))
122 0           (?{ push @pure_statements, { TYPE => 'other', %+ }; })
123             )*+
124              
125             # Possible trailing whitespace at the end of the block...
126             ( (?>(? (?&PerlNWS) ))
127 0           (?{ push @pure_statements, { TYPE => 'other', %+ }; })
128             )?+
129             )
130             (? # Non-capturing version of the above
131             \{ $OWS
132             (?:
133             when $OWS \( (?: $OWS (?> any | all | none ) $OWS => )?+
134             $OWS (?>(?&PerlExpression))
135             $OWS \) $OWS (?>(?&PerlBlock)) $OWS
136             |
137             default $OWS (?>(?&PerlBlock)) $OWS
138             |
139             given \b $OWS \( (?: $OWS (?> any | all | none ) $OWS => )?+
140             $OWS (?>(?&PerlExpression))
141             $OWS \)
142             $OWS (?>(?&NestedPureBlock)) $OWS
143             |
144             (?! when \b | default | given \b ) (?>(?&PerlStatement)) $OWS
145             )*+
146             \}
147             )
148             (?
149             # Pure given can't have a continue or break or goto in it...
150             (?: continue | break | goto ) \b (*COMMIT)(*FAIL)
151             |
152             (?&PerlStdBuiltinFunction)
153             )
154             (?
155             # "Pure" given can't have a postfix "when" modifier in it...
156             (?> if | for(?:each)?+ | while | unless | until | when (*COMMIT)(*FAIL) )
157             \b
158             (?>(?&PerlOWS))
159             (?&PerlExpression)
160             ) # End of rule (?)
161             )
162             $PPR::X::GRAMMAR
163 0           }xms;
164              
165             # Generate an optimized given/when implementation if the given is "pure"...
166 2     2   15 no warnings 'once';
  2         24  
  2         3909  
167 0 0         if ($source =~ $VALIDATE_PURE_GIVEN) {
168 0           my %matched = %+;
169 0           my $nesting_depth = 0;
170 0           my $after_a_statement = 0;
171              
172             return
173             "if (1) $matched{ws_post_kw} { local *_ = $matched{ws_pre_expr} \\scalar($matched{EXPR}); $matched{ws_pre_close} if(0) $matched{ws_pre_block} }"
174             . join("", map {
175 0 0         my $PREFIX = $after_a_statement ? 'if(0){}' : q{};
176 0 0         if ($_->{TYPE} eq 'when') {
    0          
    0          
177 0           my $BLOCK = $_->{WHENBLOCK};
178 0           $after_a_statement = 0;
179 0           "$PREFIX elsif $_->{WHENOPEN}" . _apply_when_magic($_->{WHENEXPR}) . " $_->{WHENCLOSE} $BLOCK $_->{WHENPOST}"
180             }
181             elsif ($_->{TYPE} eq 'default') {
182 0           $after_a_statement = 0;
183 0           "$PREFIX elsif (1) $_->{DEFPRE} $_->{DEFBLOCK} $_->{DEFPOST}"
184             }
185             elsif ($_->{TYPE} eq 'given') {
186 0           my $nested = _pure_given_impl($_->{NESTEDGIVEN});
187 0 0         if ($after_a_statement) {
188 0           $nested;
189             }
190             else {
191 0           $after_a_statement = 1;
192 0           $nesting_depth++;
193 0           "else { $nested ";
194             }
195             }
196             else { # Must be a regular statement...
197 0 0         if ($after_a_statement) {
198 0           $_->{STATEMENT};
199             }
200             else {
201 0           $after_a_statement = 1;
202 0           $nesting_depth++;
203 0           "else { $_->{STATEMENT}";
204             }
205             }
206 0 0         } @{[@pure_statements]} )
  0            
207             . (!$after_a_statement ? "else{}" : q{})
208             . ('}' x $nesting_depth)
209             . "}$matched{TRAILING_CODE}";
210             }
211              
212             # Otherwise, fail...
213 0           return;
214             }
215              
216             # Implement "given" keyword...
217 0     0     sub _given_impl { my ($source_ref) = @_; # Has to be this way because of code blocks in regex
218              
219             # First try the "pure" approach (only works on a limited selection of "given" blocks)...
220 0           my $REPLACEMENT_CODE = _pure_given_impl('given ' . ${$source_ref});
  0            
221              
222             # Otherwise recognize a valid general-purpose given block (with a single scalar argument)...
223 0 0         if (!defined $REPLACEMENT_CODE) {
224              
225             # Recognize a valid given block (with a single scalar argument)...
226 0           state $VALIDATE_GIVEN = qr{
227             \A (? $OWS \(
228             $OWS (?>(? (?&PerlExpression)))
229             $OWS \)
230             (?>
231             $OWS (?>(? (?&PerlBlock) ))
232             |
233             (?)
234             )
235             )
236             (?>(? .* ))
237             $PPR::GRAMMAR
238             }xms;
239 0           ${$source_ref} =~ $VALIDATE_GIVEN;
  0            
240              
241             # Extract components...
242 0           my %result = %+;
243              
244             # It's a valid "given"...
245 0 0         if (exists $result{BLOCK}) {
    0          
246 0           my ($GIVEN, $EXPR, $BLOCK, $TRAILING_CODE) = @result{qw< GIVEN EXPR BLOCK TRAILING_CODE >};
247              
248             # Augment the block with control flow and other necessary components...
249 0           $BLOCK = _augment_block(given => "$BLOCK");
250              
251             # Topicalize the "given" argument...
252 0           substr($BLOCK, 1, 0) = qq{local *_ = \\($EXPR);};
253              
254             # Implement "given" as a (trivial) "if" block...
255 0           $REPLACEMENT_CODE = qq{ if (1) $BLOCK };
256              
257             # At what line should the "given" end???
258 0           my $end_line = (caller)[2] + $GIVEN =~ tr/\n//;
259              
260             # Append the trailing code (at the right line number)...
261 0           $REPLACEMENT_CODE .= "\n#line $end_line\n$TRAILING_CODE";
262             }
263              
264             # Otherwise, report the error in context...
265             elsif (exists $result{EXPR}) {
266 0           $REPLACEMENT_CODE = q{ BEGIN { warn q{Invalid code somewhere in "given" block starting} } }
267             . q{ BEGIN { warn qq{(Note: the error reported below may be misleading)\\n}}}
268 0           . qq{ if ${$source_ref} };
269             }
270             }
271              
272             # Install standard code in place of keyword...
273 0           ${$source_ref} = $REPLACEMENT_CODE;
  0            
274             }
275              
276             # Implementation of "when" keyword...
277 0     0     sub _when_impl ($source_ref) {
  0            
  0            
278 0           my ($REPLACEMENT_CODE, $TRAILING_CODE);
279              
280             # What various kinds of "when" look like...
281 0           state $WHEN_CLASSIFIER = qr{
282             \A (? $OWS
283             ( \(
284             $OWS (? (?&PerlExpression))
285             $OWS \)
286             $OWS (?>(? (?&PerlBlock) )
287             | (?)
288             )
289             |
290             (?>(? (?&PerlCommaList)))
291             (?>(?&PerlOWSOrEND)) (?> ; | (?= \} | \z ))
292             |
293             (? \N{0,20} )
294             )
295             )
296             (? .* )
297             $PPR::GRAMMAR
298             }xms;
299              
300             # Classify the type of "when" we're processing...
301 0           ${$source_ref} =~ $WHEN_CLASSIFIER;
  0            
302 0           my %matched = %+;
303              
304             # Handle a valid when block (with a list of scalar arguments)...
305 0 0 0       if (defined $matched{BLOCK} && defined $matched{EXPR}) {
    0          
    0          
306             my ($WHEN, $EXPR, $BLOCK, $TRAILING_CODE)
307 0           = @matched{qw< WHEN EXPR BLOCK TRAILING_CODE>};
308              
309             # Augment the block with control flow and other necessary components...
310 0           $BLOCK = _augment_block(when => "$BLOCK");
311              
312             # Implement the boolean operator magic...
313 0           $EXPR = _apply_when_magic($EXPR);
314              
315             # Implement the "when" as an "if"...
316 0           $REPLACEMENT_CODE = qq{if(1){local \$Switch::Back::when_value = ($EXPR); if(1){if (\$Switch::Back::when_value) $BLOCK }}};
317              
318             # At what line should the "when" end???
319 0           my $end_line = (caller)[2] + $WHEN =~ tr/\n//;
320              
321             # Append the trailing code (at the right line number)...
322 0           $REPLACEMENT_CODE .= "\n#line $end_line\n$TRAILING_CODE";
323             }
324              
325             # Otherwise, reject the "when" with extreme prejudice...
326             elsif (defined $matched{MODIFIER}) {
327 0           $REPLACEMENT_CODE = qq{ BEGIN { die q{Can't specify postfix "when" modifier outside a "given"} } };
328             }
329             elsif (exists $matched{INVALID_BLOCK}) {
330 0           $REPLACEMENT_CODE = qq{ BEGIN { warn q{Invalid code block in "when"} } }
331             . qq{ BEGIN { warn qq{(Note: the error reported below may be misleading)\\n} } }
332 0           . qq{ if ${$source_ref} };
333             }
334             else {
335 0           $REPLACEMENT_CODE = qq{ BEGIN { die q{Incomprehensible "when" (near: $matched{INCOMPREHENSIBLE})} } };
336             }
337              
338             # Install code implementing keyword behaviour...
339 0           ${$source_ref} = $REPLACEMENT_CODE;
  0            
340             }
341              
342 0     0     sub _default_impl ($source_ref) {
  0            
  0            
343 0           state $DEFAULT_CLASSIFIER = qr{
344             (? $OWS (?>(? (?&PerlBlock) )) )
345             (? .* )
346             $PPR::GRAMMAR
347             }xms;
348              
349             # Verify that we match the syntax for a "default" block...
350 0           ${$source_ref} =~ $DEFAULT_CLASSIFIER;
  0            
351 0           my %matched = %+;
352              
353             # Implement the "default" block...
354 0 0         if (defined $matched{BLOCK}) {
355             # Install the necessary extras...
356 0           my $BLOCK = _augment_block(default => $matched{BLOCK});
357              
358             # Build the implementation of the "default"...
359 0           my $REPLACEMENT_CODE = qq{ if (1) $BLOCK };
360              
361             # At what line should the "default" end???
362 0           my $end_line = (caller)[2] + $matched{DEFAULT} =~ tr/\n//;
363              
364             # Append the trailing code (at the right line number)...
365 0           ${$source_ref} = "$REPLACEMENT_CODE\n#line $end_line\n$matched{TRAILING_CODE}";
  0            
366             }
367              
368             # Report the error...
369             else {
370 0           ${$source_ref}
  0            
371             = qq{ BEGIN { die q{Incomprehensible "default" (near: $matched{INCOMPREHENSIBLE})} } };
372             }
373             }
374              
375             # Implement the "continue" command...
376 0     0 0   sub continue () {
  0            
377             # Which "when" block are we in???
378 0           my $AFTERWHEN = (caller 0)[10]{'Switch::Back/Afterwhen'};
379              
380             # Jump out of it, if possible...
381 2     2   17 no warnings;
  2         3  
  2         304  
382 0           eval { goto $AFTERWHEN };
  0            
383              
384             # If not possible, that's fatal...
385 0           croak q{Can't "continue" outside a "when" or "default"};
386             }
387              
388             # Implement the "break" command...
389 0     0 0   sub break () {
  0            
390             # Which "given" block are we in???
391 0           my $AFTERGIVEN = (caller 0)[10]{'Switch::Back/Aftergiven'};
392              
393             # Jump out of it, if possible...
394 2     2   11 no warnings;
  2         4  
  2         4561  
395 0           eval { goto $AFTERGIVEN };
  0            
396              
397             # If we weren't in a "given", can we jump out of a surrounding loop???
398 0           eval { next };
  0            
399              
400             # Otherwise, the "break" was illegal and must be punished...
401 0           croak q{Can't "break" outside a "given"};
402             }
403              
404              
405             # Insert unique identifying information into a "given"/"when"/"default" source code block...
406 0     0     sub _augment_block ($TYPE, $BLOCK) {
  0            
  0            
  0            
407             # Unique identifiers for each type of block...
408 0           state %ID;
409              
410             # Who and what is this block???
411 0 0         my $KIND = $TYPE eq 'default' ? "when" : $TYPE;
412 0           my $NAME = "After$KIND";
413 0           my $ID = $NAME . ++$ID{$KIND};
414              
415             # Give each block a unique name (uses refaliasing to create a lexical constant)...
416 0           substr($BLOCK, 1,0)
417             = qq{ BEGIN { \$^H{'Switch::Back/$NAME'} = '$ID'; } };
418              
419             # A when block auto-breaks at the end of its block...
420 0 0         if ($KIND eq 'when') {
    0          
421 0           my $AFTERGIVEN = $^H{'Switch::Back/Aftergiven'};
422 0 0         substr($BLOCK,-1,0)
423             = ';'
424             . (defined($AFTERGIVEN) ? qq{eval { no warnings; goto $AFTERGIVEN } || } : q{})
425             . qq{eval { no warnings; next } || die q{Can't "$TYPE" outside a topicalizer} };
426             }
427              
428             # Given blocks must to pre-convert postfix "when" modifiers (which can't be keyworded)...
429             # and must also preprocess "continue" to a unpunned name...
430             elsif ($KIND eq 'given') {
431 0           $BLOCK = _convert_postfix_whens($BLOCK);
432             }
433              
434             # Return identified block...
435 0           return "$BLOCK $ID:;";
436             }
437              
438             # Identify and pre-convert "EXPR when EXPR" syntax...
439 0     0     sub _convert_postfix_whens ($BLOCK) {
  0            
  0            
440             # Track locations of "when" modifiers in the block's source...
441 0           my @target_pos;
442              
443             # Extract those locations, whenever a statement has a "when" modifier...
444 0           $BLOCK =~ m{
445             \{ (?&PerlStatementSequence) \}
446              
447             (?(DEFINE)
448             (?
449             (?>
450             (?>(?&PerlPodSequence))
451             (?: (?>(?&PerlLabel)) (?&PerlOWSOrEND) )?+
452             (?>(?&PerlPodSequence))
453              
454             (?> (?&PerlKeyword)
455             | (?&PerlSubroutineDeclaration)
456             | (?&PerlMethodDeclaration)
457             | (?&PerlUseStatement)
458             | (?&PerlPackageDeclaration)
459             | (?&PerlClassDeclaration)
460             | (?&PerlFieldDeclaration)
461             | (?&PerlControlBlock)
462             | (?&PerlFormat)
463             |
464             # POSTFIX when HAS TO BE REWRITTEN BEFORE OTHER POSTFIX MODIFIERS ARE MATCHED...
465             (?
466             (? (?>(?&PerlExpression)) (?>(?&PerlOWS)) )
467             (?= when \b )
468             (? (?&PerlStatementModifier) (?>(?&PerlOWSOrEND)) )
469             (? (?> ; | (?= \} | \z )) )
470             )
471 0           (?{ my $len = length($+{MATCH});
472             unshift @target_pos, {
473             expr => $+{EXPR},
474             mod => substr($+{MOD},4),
475             end => $+{END},
476 0           from => pos() - $len,
477             len => $len,
478             }
479             })
480             |
481             (?>(?&PerlExpression)) (?>(?&PerlOWS))
482             (?&PerlStatementModifier)?+ (?>(?&PerlOWSOrEND))
483             (?> ; | (?= \} | \z ))
484             | (?&PerlBlock)
485             | ;
486             )
487             | # A yada-yada...
488             \.\.\. (?>(?&PerlOWSOrEND))
489             (?> ; | (?= \} | \z ))
490              
491             | # Just a label...
492             (?>(?&PerlLabel)) (?>(?&PerlOWSOrEND))
493             (?> ; | (?= \} | \z ))
494              
495             | # Just an empty statement...
496             (?>(?&PerlOWS)) ;
497             )
498             )
499             )
500             $PPR::X::GRAMMAR
501             }xms;
502              
503             # Replace each postfix "when"...
504 0           for my $pos (@target_pos) {
505             # Unique ID for the "when" (needed by continue())...
506 0           state $ID; $ID++;
  0            
507              
508             # Convert postfix "when" to a postfix "if" (preserving Afterwhen info for continue())...
509             substr($BLOCK, $pos->{from}, $pos->{len})
510             = "BEGIN { \$^H{'Switch::Back/Afterwhenprev'} = \$^H{'Switch::Back/Afterwhen'};"
511             . " \$^H{'Switch::Back/Afterwhen'} = 'Afterpostfixwhen$ID'; }"
512             . "$pos->{expr}, break if " . _apply_when_magic($pos->{mod})
513             . ";Afterpostfixwhen$ID:"
514             . "BEGIN { \$^H{'Switch::Back/Afterwhen'} = \$^H{'Switch::Back/Afterwhenprev'}; }"
515 0           . $pos->{end};
516             }
517              
518 0           return $BLOCK;
519             }
520              
521             # Change the target expression of a "when" to implement all the magic behaviours...
522 0     0     sub _apply_when_magic ($EXPR) {
  0            
  0            
523             # Reduce the expression to what the compiler would see...
524 0           $EXPR = _simplify_expr($EXPR);
525              
526             # Split on low-precedence or...
527 0           my @low_disj = grep { defined }
  0            
528             $EXPR =~ m{ ( (?>(?&PerlLowPrecedenceNotExpression))
529             (?:
530             (?>(?&PerlOWS)) and
531             (?>(?&PerlOWS)) (?&PerlLowPrecedenceNotExpression)
532             )*+
533             )
534             (?>(?&PerlOWS)) (?: or | \z ) (?>(?&PerlOWS))
535              
536             (?(DEFINE)
537             (?
538             (?>(?&PerlAssignment))
539             (?:
540             (?: (?>(?&PerlOWS)) (?>(?&PerlComma)) )++
541             (?>(?&PerlOWS)) (?>(?&PerlAssignment))
542             )*+
543             (?: (?>(?&PerlOWS)) (?>(?&PerlComma)) )*+
544             ) # End of rule (?)
545             )
546              
547             $PPR::GRAMMAR }gxms;
548              
549             # If expression is a low-precedence or, apply any appropriate magic...
550 0 0         if (@low_disj > 1) {
551             # If the left-most operand isn't smartmatchable, the expression as a whole isn't,
552             # so just return it...
553 0           my $low_lhs = shift @low_disj;
554 0           my $magic_lhs = _apply_low_conj_magic($low_lhs);
555 0 0         if ($low_lhs eq $magic_lhs) {
556 0           return $EXPR;
557             }
558              
559             # Otherwise, every operand has magic applied to it...
560             else {
561 0           return join ' or ', $magic_lhs, map { _apply_low_conj_magic($_) } @low_disj;
  0            
562             }
563             }
564              
565             # Otherwise, see if it's a low-precedence conjunction...
566 0           return _apply_low_conj_magic($EXPR);
567             }
568              
569 0     0     sub _apply_low_conj_magic ($EXPR) {
  0            
  0            
570             # Split on low-precedence and...
571 0           my @low_conj = grep { defined }
  0            
572             $EXPR =~ m{ ( (?>(?&PerlLowPrecedenceNotExpression)) )
573             (?>(?&PerlOWS)) (?: and | \z ) (?>(?&PerlOWS))
574              
575             (?(DEFINE)
576             (?
577             (?>(?&PerlAssignment))
578             (?:
579             (?: (?>(?&PerlOWS)) (?>(?&PerlComma)) )++
580             (?>(?&PerlOWS)) (?>(?&PerlAssignment))
581             )*+
582             (?: (?>(?&PerlOWS)) (?>(?&PerlComma)) )*+
583             ) # End of rule (?)
584             )
585              
586             $PPR::GRAMMAR }gxms;
587              
588             # If expression is a low-precedence and, apply any appropriate magic...
589 0 0         if (@low_conj > 1) {
590             # Every operand must be recursively magical, or none of them are...
591 0           my @magic_expr;
592 0           for my $next_operand (@low_conj) {
593 0           my $magic_operand = _apply_high_disj_magic($next_operand);
594              
595             # If any operand isn't smartmatchable, the whole expr isn't magical,
596             # so just smartmatch the entire expression...
597 0 0         if ($magic_operand eq $next_operand) {
598 0           return $EXPR;
599             }
600              
601             # Otherwise, accumulate the magic...
602 0           push @magic_expr, $magic_operand;
603             }
604 0           return join " and ", @magic_expr;
605             }
606              
607             # Otherwise, see if it's a high-precedence disjunction...
608 0           return _apply_high_disj_magic($EXPR);
609             }
610              
611 0     0     sub _apply_high_disj_magic ($EXPR) {
  0            
  0            
612             # Split on high-precedence or...
613 0           my @high_disj = grep { defined }
  0            
614             $EXPR =~ m{ ( (?>(?&PerlBinaryExpression)) )
615             (?>(?&PerlOWS)) ( \|\| | // | \z ) (?>(?&PerlOWS))
616              
617             (?(DEFINE)
618             (?
619             (?! \|\| | // ) (?&PerlStdInfixBinaryOperator)
620             )
621             )
622             $PPR::X::GRAMMAR
623             }gxms;
624              
625             # If expression is a high-precedence || or //, apply any appropriate magic...
626 0 0         if (@high_disj > 1) {
627             # If the left-most operand isn't smartmatchable, the expression as a whole isn't,
628             # so just return it...
629 0           my $high_lhs = shift @high_disj;
630 0           my $magic_expr = _apply_high_conj_magic($high_lhs);
631 0 0         if ($high_lhs eq $magic_expr) {
632 0           return $EXPR;
633             }
634              
635             # Otherwise, every operand has magic applied to it...
636             else {
637 0           while (@high_disj > 1) {
638 0           my $next_operator = shift @high_disj;
639 0           my $next_operand = shift @high_disj;
640 0           $magic_expr .= " $next_operator " . _apply_high_conj_magic($next_operand);
641             }
642 0           return $magic_expr;
643             }
644             }
645              
646             # Otherwise, see if it's a high-precedence conjunction...
647 0           return _apply_high_conj_magic($EXPR);
648             }
649              
650 0     0     sub _apply_high_conj_magic ($EXPR) {
  0            
  0            
651             # Split on high-precedence &&...
652 0           my @high_conj = grep { defined }
  0            
653             $EXPR =~ m{ ( (?>(?&PerlBinaryExpression)) )
654             (?>(?&PerlOWS)) (?: && | \z ) (?>(?&PerlOWS))
655              
656             (?(DEFINE)
657             (?
658             (?! && ) (?&PerlStdInfixBinaryOperator)
659             )
660             )
661             $PPR::X::GRAMMAR
662             }gxms;
663              
664             # If expression is a high-precedence &&, apply any appropriate magic...
665 0 0         if (@high_conj > 1) {
666             # Every operand must be recursively smartmatchable, or none of them are...
667 0           my @magic_expr;
668 0           for my $next_operand (@high_conj) {
669 0           my $magic_operand = _apply_term_magic($next_operand);
670              
671             # If any operand isn't smartmatchable, the whole expr isn't magical,
672             # so just treat the entire expression as a boolean expression...
673 0 0         if ($magic_operand eq $next_operand) {
674 0           return $EXPR;
675             }
676              
677             # Otherwise, accumulate the magic...
678 0           push @magic_expr, $magic_operand;
679             }
680 0           return join " && ", @magic_expr;
681             }
682              
683             # Otherwise, see if it's a magical term...
684 0           return _apply_term_magic($EXPR);
685             }
686              
687             # Detect whether a term in a "when" expression is magical and adjust it accordingly...
688 0     0     sub _apply_term_magic ($EXPR) {
  0            
  0            
689              
690             # An @array or %hash gets enreferenced and then smartmatched...
691 0 0         if ($EXPR =~ $CONTAINER_VARIABLE) {
692 0           return " smartmatch(\$_, \\$EXPR) ";
693             }
694              
695             # An @array[@slice] or %kv[@slice] gets appropriately wrapped and then smartmatched...
696 0 0         if ($EXPR =~ $ARRAY_SLICE) {
697 0           return " smartmatch(\$_, [$EXPR]) ";
698             }
699 0 0         if ($EXPR =~ $HASH_SLICE) {
700 0           return " smartmatch(\$_, {$EXPR}) ";
701             }
702              
703             # Non-magical values get smartmatched...
704 0 0         if ($EXPR =~ $SMARTMATCHABLE) {
705 0           return " smartmatch(\$_, $EXPR) ";
706             }
707              
708             # Anything else is magically NOT smartmatched (it's treated as a simple boolean test)...
709 0           return $EXPR;
710             }
711              
712              
713             # Reduce a compile-time expression to what the compiler actually sees...
714             # (Essential because that's what when() actually sees and how it decides
715             # whether or not smartmatch is magically distributive over a boolean expression)...
716 0     0     sub _simplify_expr ($code) {
  0            
  0            
717 2     2   16 no warnings;
  2         4  
  2         100  
718 2     2   15 use B::Deparse;
  2         5  
  2         443  
719 0           state $deparse = B::Deparse->new;
720 0           return $deparse->coderef2text(eval qq{no strict; sub{ANSWER( $code );DONE()}})
721             =~ s{.* ANSWER \( (.*) \) \s* ; \s* DONE() .* \z}{$1}gxmsr;
722             }
723              
724              
725             # Reimplement the standard smartmatch operator
726             # (This could have been a set of multis, but a single multi is currently much faster)...
727              
728 2 0 0 2   593076 multi smartmatch ($left, $right) {
  2 50 50 2   5  
  2 0   2   1001  
  2 0   2   14  
  2 0   2   5  
  2 0   2   1264  
  2 0   1   14  
  2 50       3  
  2 50       166  
  2 50       11  
  2 50       4  
  2 50       111  
  2         11  
  2         3  
  2         4118  
  2         16  
  0         0  
  2         6  
  2         3  
  2         29  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         3  
  2         10  
  2         15  
  1         2  
  1         8  
  1         6  
  1         4  
  0         0  
  1         5  
  0         0  
  1         2  
  1         5  
  1         5  
  1         10  
  1         3  
  1         4  
  0         0  
  0         0  
  2         8  
  2         13  
  2         347  
  2         9  
  2         38  
  34         90  
  34         70  
  34         83  
  34         167  
  34         86  
  34         129  
729              
730             # Categorize the two args...
731 34         59 my $right_type = ref $right;
  34         75  
732 34         65 my $left_type = ref $left;
733              
734             # Track "use integer" status in original caller (passing it down to nested smartmatches)...
735 34   66     346 local $Switch::Back::_use_integer = $Switch::Back::_use_integer // (caller 0)[8] & 0x1;
736              
737             # 1. Handle RHS undef...
738 34 100       134 if (!defined $right) {
739 8         313 return !defined $left;
740             }
741              
742             # 2. Objects on the RHS can't be handled (at all, because no ~~ overloading available)...
743 26 50 66     104 croak 'Smart matching an object breaks encapsulation'
744             if $right_type ne 'Regexp' && blessed($right);
745              
746             # 3. Array on the RHS..
747 26 100       60 if ($right_type eq 'ARRAY') {
748              
749             # 3a. Array of the LHS too...
750 1 50       12 if ($left_type eq 'ARRAY') {
    50          
    50          
    50          
751             # Match if identical array refs...
752 0 0       0 return true if $left == $right;
753              
754             # Different lengths, so won't match...
755 0 0       0 return false if @{$left} != @{$right};
  0         0  
  0         0  
756              
757             # Handle non-identical self-referential structures...
758 0         0 local %Sm4r7m4tCh::seen = %Sm4r7m4tCh::seen;
759 0 0 0     0 return false if $Sm4r7m4tCh::seen{"L$left"}++ || $Sm4r7m4tCh::seen{"R$right"}++;
760              
761             # Otherwise every pair of elements from the two arrays must smartmatch...
762 0         0 for my $n (keys @{$right}) {
  0         0  
763 0 0       0 return false if !smartmatch($left->[$n], $right->[$n]);
764             }
765 0         0 return true;
766             }
767              
768             # 3b. Hash on the LHS...
769             elsif ($left_type eq 'HASH') {
770             # Matches if any right array element is a left hash key...
771 0         0 for my $r (@{$right}) {
  0         0  
772 0 0       0 if (!defined $r) {
773 0 0       0 carp 'Use of uninitialized value in smartmatch'
774             if warnings::enabled('uninitialized');
775             }
776 0 0 0     0 return true if exists $left->{ $r // q{} };
777             }
778 0         0 return false;
779             }
780              
781             # 3c. Regex on the LHS...
782             elsif ($left_type eq 'Regexp') {
783             # Matches if left arg pattern-matches any element of right array...
784 0         0 for my $r (@{$right}) {
  0         0  
785 0 0       0 return true if $r =~ $left;
786             }
787 0         0 return false;
788             }
789              
790             # 3d. undef on the LHS...
791             elsif (!defined $left) {
792             # Matches if any element of right array is undefined (NON-RECURSIVELY)...
793 0         0 for my $r (@{$right}) {
  0         0  
794 0 0       0 return true if !defined $r;
795             }
796 0         0 return false;
797             }
798              
799             # 3e. Anything else on the LHS...
800             else {
801             # Matches if left arg smartmatches any element of right array...
802 1         2 for my $r (@{$right}) {
  1         3  
803 1 50       4 if (!defined $r) {
804 0 0       0 carp 'Use of uninitialized value in smartmatch'
805             if warnings::enabled('uninitialized');
806             }
807 1 50       6 return true if smartmatch($left, $r);
808             }
809 0         0 return false;
810             }
811             }
812              
813             # 4. Hash on the RHS...
814 25 100       54 if ($right_type eq 'HASH') {
815              
816             # 4a. Hash on the LHS...
817 1 50       9 if ($left_type eq 'HASH') {
    50          
    50          
    50          
818             # Match if they're the same hashref...
819 0 0       0 return true if $left == $right;
820              
821             # Fail to match if they have different numbers of keys...
822 0 0       0 return false if %{$left} != %{$right};
  0         0  
  0         0  
823              
824             # Otherwise, match if all their keys match...
825 0         0 for my $lkey (keys %{$left}) {
  0         0  
826 0 0       0 return false if !exists $right->{$lkey};
827             }
828 0         0 return true;
829             }
830              
831             # 4b. Array on the LHS...
832             elsif ($left_type eq 'ARRAY') {
833              
834             # Handle self-referential structures...
835 0         0 local %Sm4r7m4tCh::seen = %Sm4r7m4tCh::seen;
836 0 0       0 return false if $Sm4r7m4tCh::seen{"L$left"}++;
837              
838             # Match if any top-level array element is (NON-RECURSIVELY) a key in the hash...
839 0         0 for my $l (@{$left}) {
  0         0  
840 0 0       0 if (!defined $l) {
841 0 0       0 carp 'Use of uninitialized value in smartmatch'
842             if warnings::enabled('uninitialized');
843             }
844 0 0 0     0 return true if exists $right->{ $l // q{} };
845             }
846 0         0 return false;
847             }
848              
849             # 4c. Regex on the LHS...
850             elsif ($left_type eq 'Regexp') {
851             # Match if any hash key is matched by the regex...
852 0         0 for my $rkey (keys %{$right}) {
  0         0  
853 0 0       0 return true if $rkey =~ $left;
854             }
855 0         0 return false;
856             }
857              
858             # 4d. undef on the LHS...
859             elsif (!defined $left) {
860             # Hash keys can never be undef...
861 0         0 return false;
862             }
863              
864             # 4e. Anything else on the LHS...
865             else {
866             # Match if the stringified left arg is a key of right hash...
867 1 50       4 if (!defined $left) {
868 0 0       0 carp 'Use of uninitialized value in smartmatch'
869             if warnings::enabled('uninitialized');
870             }
871 1   50     39 return exists $right->{ $left // q{} };
872             }
873             }
874              
875             # 5. Subroutine reference on the RHS...
876 24 100       54 if ($right_type eq 'CODE') {
877              
878             # 5a. Array on the LHS...
879 4 50       14 if ($left_type eq 'ARRAY') {
    50          
880              
881             # Handle self-referential structures...
882 0         0 local %Sm4r7m4tCh::seen = %Sm4r7m4tCh::seen;
883 0 0       0 return false if $Sm4r7m4tCh::seen{"L$left"}++;
884              
885             # Sub must always return true when called on every element of array...
886 0         0 for my $l (@{$left}) {
  0         0  
887 0 0       0 return false if !$right->($l);
888             }
889 0         0 return true;
890             }
891              
892             # 5b. Hash on the LHS...
893             elsif ($left_type eq 'HASH') {
894             # Sub must always return true when called on every key of hash...
895 0         0 for my $lkey (keys %{$left}) {
  0         0  
896 0 0       0 return false if !$right->($lkey);
897             }
898 0         0 return true;
899             }
900              
901             # 5c. Anything else on the LHS...
902             else {
903             # Otherwise, sub must return true when passed left arg...
904 4         13 return !!$right->($left);
905             }
906             }
907              
908             # 6. Regexp on the RHS...
909 20 100       43 if ($right_type eq 'Regexp') {
910              
911             # 6a. Array on the LHS...
912 9 100       33 if ($left_type eq 'ARRAY') {
    100          
913              
914             # Handle self-referential structures...
915 1         4 local %Sm4r7m4tCh::seen = %Sm4r7m4tCh::seen;
916 1 50       9 return false if $Sm4r7m4tCh::seen{"L$left"}++;
917              
918             # Match if any left array element pattern-matches the right regex...
919 1         40 for my $l (@{$left}) {
  1         6  
920 0 0       0 if (!defined $l) {
921 0 0       0 carp 'Use of uninitialized value in smartmatch'
922             if warnings::enabled('uninitialized');
923             }
924 2     2   15 no warnings;
  2         3  
  2         569  
925 0 0       0 return true if $l =~ $right;
926             }
927 1         81 return false;
928             }
929              
930             # 6b. Hash on the LHS...
931             elsif ($left_type eq 'HASH') {
932             # Match if any left key of the hash pattern-matches the right regex...
933 1         3 for my $lkey (keys %{$left}) {
  1         4  
934 0 0       0 return true if $lkey =~ $right;
935             }
936 1         64 return false;
937             }
938              
939             # 6c. Anything else on the LHS...
940             else {
941             # Otherwise, the stringified left arg must pattern-match right regex...
942 7 50       18 if (!defined $left) {
943 0 0       0 carp 'Use of uninitialized value in pattern match (m//)'
944             if warnings::enabled('uninitialized');
945             }
946 2     2   16 no warnings;
  2         4  
  2         270  
947 7         329 return $left =~ $right;
948             }
949             }
950              
951             # 7. Primordial numbers on the RHS attempt numeric matching against LHS values...
952 11 50       34 if (created_as_number($right)) {
953 2     2   12 no warnings;
  2         4  
  2         130  
954 0 0       0 if ($Switch::Back::_use_integer) {
955 2     2   1497 use integer;
  2         35  
  2         12  
956 0   0     0 return defined $left && $left == $right;
957             }
958             else {
959 0   0     0 return defined $left && $left == $right;
960             }
961             }
962              
963             # 8. Primordial numbers on the LHS attempt numeric matching against LHS number-ish values...
964 11 50 66     49 if (created_as_number($left) && looks_like_number($right)) {
965 0 0       0 if ($Switch::Back::_use_integer) {
966 2     2   436 use integer;
  2         4  
  2         13  
967 0         0 return $left == $right;
968             }
969             else {
970 0         0 return $left == $right;
971             }
972             }
973              
974             # 9. If LHS is undef, RHS must be too,
975             # but we know it isn't at this point, because test 1. would have caught that...
976 11 50       47 if (!defined $left) {
977 0         0 return false;
978             }
979              
980             # 10. Otherwise, we just string match...
981             else {
982 2     2   305 no warnings;
  2         41  
  2         577  
983 11         473 return $left eq $right;
984             }
985 2         4 }
  34         510  
986              
987              
988             1; # Magic true value required at end of module
989             __END__