File Coverage

blib/lib/Switch/Right.pm
Criterion Covered Total %
statement 395 653 60.4
branch 107 258 41.4
condition 37 105 35.2
subroutine 56 66 84.8
pod 0 2 0.0
total 595 1084 54.8


line stmt bran cond sub pod time code
1             package Switch::Right;
2              
3 7     7   1497299 use 5.036;
  7         26  
4             our $VERSION = '0.000006';
5              
6 7     7   2530 use experimental qw< builtin refaliasing try >;
  7         20289  
  7         52  
7 7     7   4587 use builtin qw< true false is_bool blessed created_as_number reftype >;
  7         567  
  7         445  
8 7     7   45 use Scalar::Util qw < looks_like_number >;
  7         17  
  7         426  
9 7     7   80 use overload;
  7         15  
  7         56  
10              
11 7     7   10228 use Multi::Dispatch;
  7         4714247  
  7         115  
12 7     7   3572 use PPR::X;
  7         27  
  7         253  
13 7     7   53 use Carp qw< croak carp >;
  7         16  
  7         1080  
14              
15             # Useful patterns...
16 7     7   558 my $OWS; BEGIN { $OWS = q{(?>(?&PerlOWS))}; }
17             my $CONTAINER_VARIABLE;
18 7     7   451165 BEGIN { $CONTAINER_VARIABLE
19             = qr{ \A (?> (?&PerlVariableArray) | (?&PerlVariableHash)
20             | my $OWS (?> (?&PerlVariableArray) | (?&PerlVariableHash) ) $OWS = .*
21             )
22             \z $PPR::GRAMMAR
23             }xms;
24             }
25             my $ARRAY_SLICE;
26 7     7   429565 BEGIN { $ARRAY_SLICE
27             = qr{ \A (?> (?&PerlArrayAccess)
28             | my $OWS (?&PerlArrayAccess) $OWS = .*
29             )
30             \z $PPR::GRAMMAR
31             }xms;
32             }
33             my $HASH_SLICE;
34 7     7   367309 BEGIN { $HASH_SLICE
35             = qr{ \A (?> (?&PerlHashAccess)
36             | my $OWS (?&PerlHashAccess) $OWS = .*
37             )
38             \z $PPR::GRAMMAR
39             }xms;
40             }
41             my $FLIP_FLOP;
42 7     7   510035 BEGIN { $FLIP_FLOP
43             = qr{ \A (?&FlipFlop) \z
44             (?(DEFINE)
45             (?
46             \( (?>(?&PerlOWS)) (?&FlipFlop) (?>(?&PerlOWS)) \)
47             |
48             (?>(?&PerlBinaryExpression)) (?>(?&PerlOWS))
49             \.\.\.? (?>(?&PerlOWS))
50             (?>(?&PerlBinaryExpression))
51             )
52             (?
53             (?> [=!][~=] | <= >?+ | >=
54             | cmp | [lg][te] | eq | ne
55             | [+] (?! [+=] )
56             | - (?! [-=] )
57             | [.%x] (?! [=] )
58             | [&|^][.] (?! [=] )
59             | [<>*&|/]{1,2}+ (?! [=] )
60             | \^ (?! [=] )
61             | ~~ | isa
62             )
63             )
64             )
65             $PPR::GRAMMAR
66             }xms;
67             }
68              
69             # Install the new keywords, functions, and smartmatching...
70             sub import {
71             # Export replacement keywords...
72 7     7   117 use Keyword::Simple;
  7         26  
  7         734  
73 7     7   244 Keyword::Simple::define given => \&_given_impl;
74 7         271 Keyword::Simple::define when => \&_when_impl;
75 7         267 Keyword::Simple::define default => \&_default_impl;
76              
77             # Outside a given a 'break' is an error; outside a when a 'continue' is too...
78             {
79 7     7   41 no strict 'refs';
  7         12  
  7         374  
  7         123  
80 7     7   35 no warnings qw< redefine >;
  7         13  
  7         1802  
81 7         20 *{caller.'::break'} = \&break;
  7         71  
82 7         20 *{caller.'::continue'} = \&continue;
  7         78  
83             }
84              
85 7         17 # Export smartmatch()...
86 7 0 0 7   1015379 multi smartmatch :export;
  7 0 50 7   18  
  7 0   1173   2799  
  7 0       53  
  7 0       33  
  7 50       8533  
  7 100       15  
  7 100       19  
  7 100       15  
  7 50       74  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         19  
  7         47  
  7         62  
  1173         2637126  
  1173         8093  
  1173         4038  
  4090         10321  
  2474         5950  
  1616         4281  
  93         306  
  1523         2169  
  1523         5788  
  1523         8186  
  1173         2651  
  1173         1601  
  1173         4697  
  0         0  
  0         0  
  7         16  
  7         27  
  7         53  
  7         18  
  7         5034  
  7         29  
  7         38  
87             }
88              
89             # Error messages shared by *_given_impl()...
90             my $WHENTRUEMSG = q{BEGIN{warn q{"when (true) {...}" better written as "default {...}"}}};
91             my $WHENFALSEMSG = q{BEGIN{warn q{Useless use of "when (false)"}}};
92              
93 0     0     sub _pure_given_impl { my ($source) = @_;
94              
95             # Recognize a valid "pure" given block (i.e. containing only when and default blocks)...
96 0           state @pure_statements;
97 0           @pure_statements = ();
98             state $VALIDATE_PURE_GIVEN = qr{
99             \A given (? (? $OWS ) \(
100             (? (?: $OWS (?> any | all | none ) $OWS => )?+ )
101             (? $OWS ) (?>(? (?&PerlExpression)))
102             (? $OWS ) \)
103             (? $OWS \{ $OWS ) (?>(? (?&PureBlock) )) \}
104             )
105             (?>(? .* ))
106              
107             (?(DEFINE)
108             (? # Distinguish "when", "default", and "given" from other statements...
109             (?:
110             when (? $OWS \( $OWS )
111             (?>
112             (? (? true )
113             | (? false )
114             ) \b
115             |
116             (? (?: (?> any | all | none ) $OWS => $OWS )?+ )
117             (? (?>(?&PerlExpression)))
118             )
119             (? $OWS \) $OWS )
120             (?>(? (?&PerlBlock) ))
121             (? $OWS )
122 0           (?{ push @pure_statements, { TYPE => 'when', %+ }; })
123             |
124             default (? $OWS )
125             (?>(? (?&PerlBlock) ))
126             (? $OWS )
127 0           (?{ push @pure_statements, { TYPE => 'default', %+ }; })
128             |
129             (?
130             given \b $OWS \(
131             (?: $OWS (?> any | all | none ) $OWS => )?+
132             $OWS (?>(? (?>(?&PerlExpression))))
133             $OWS \)
134             $OWS (?>(? (?&NestedPureBlock) )) $OWS
135             )
136 0           (?{ push @pure_statements, { TYPE => 'given', %+ }; })
137             |
138             (?! $OWS (?> when | default ) \b )
139             (?>(? (?&PerlStatement) $OWS ))
140 0           (?{ push @pure_statements, { TYPE => 'other', %+ }; })
141             )*+
142              
143             # Possible trailing whitespace at the end of the block...
144             ( (?>(? (?&PerlNWS) ))
145 0           (?{ push @pure_statements, { TYPE => 'other', %+ }; })
146             )?+
147             )
148             (? # Non-capturing version of the above
149             \{ $OWS
150             (?:
151             when $OWS \( (?: $OWS (?> any | all | none ) $OWS => )?+
152             $OWS (?>(?&PerlExpression))
153             $OWS \) $OWS (?>(?&PerlBlock)) $OWS
154             |
155             default $OWS (?>(?&PerlBlock)) $OWS
156             |
157             given \b $OWS \( (?: $OWS (?> any | all | none ) $OWS => )?+
158             $OWS (?>(?&PerlExpression))
159             $OWS \)
160             $OWS (?>(?&NestedPureBlock)) $OWS
161             |
162             (?! when \b | default | given \b ) (?>(?&PerlStatement)) $OWS
163             )*+
164             \}
165             )
166             (?
167             # Pure given can't have a continue or break or goto in it...
168             (?: continue | break | goto ) \b (*COMMIT)(*FAIL)
169             |
170             (?&PerlStdBuiltinFunction)
171             )
172             (?
173             # "Pure" given can't have a postfix "when" modifier in it...
174             (?> if | for(?:each)?+ | while | unless | until | when (*COMMIT)(*FAIL) )
175             \b
176             (?>(?&PerlOWS))
177             (?&PerlExpression)
178             ) # End of rule (?)
179             )
180             $PPR::X::GRAMMAR
181 0           }xms;
182              
183             # Generate an optimized given/when implementation if the given is "pure"...
184 7     7   71 no warnings 'once';
  7         12  
  7         16433  
185 0 0         if ($source =~ $VALIDATE_PURE_GIVEN) {
186 0           my %matched = %+;
187 0           my $nesting_depth = 0;
188 0           my $after_a_statement = 0;
189 0           my $GIVEN_EXPR = _apply_term_magic($matched{EXPR});
190              
191             return
192             "if (1) $matched{ws_post_kw} { local *_ = $matched{ws_pre_expr} \\scalar($GIVEN_EXPR); $matched{ws_pre_close} if(0) $matched{ws_pre_block} }"
193             . join("", map {
194 0 0         my $PREFIX = $after_a_statement ? 'if(0){}' : q{};
195 0 0         if ($_->{TYPE} eq 'when') {
    0          
    0          
196 0           my $BLOCK = $_->{WHENBLOCK};
197 0 0         if ($_->{WHENTRUE}) { substr($BLOCK,1,0) = $WHENTRUEMSG; }
  0 0          
198 0           elsif ($_->{WHENFALSE}) { substr($BLOCK,1,0) = $WHENFALSEMSG; }
199 0   0       my $JUNC = $_->{WHENJUNC} // q{};
200 0           $after_a_statement = 0;
201             "$PREFIX elsif $_->{WHENOPEN} smartmatch($matched{JUNC} \$_, $JUNC scalar("
202 0           . _apply_term_magic($_->{WHENEXPR}) . ")) $_->{WHENCLOSE} $BLOCK $_->{WHENPOST}"
203             }
204             elsif ($_->{TYPE} eq 'default') {
205 0           $after_a_statement = 0;
206 0           "$PREFIX elsif (1) $_->{DEFPRE} $_->{DEFBLOCK} $_->{DEFPOST}"
207             }
208             elsif ($_->{TYPE} eq 'given') {
209 0           my $nested = _pure_given_impl($_->{NESTEDGIVEN});
210 0 0         if ($after_a_statement) {
211 0           $nested;
212             }
213             else {
214 0           $after_a_statement = 1;
215 0           $nesting_depth++;
216 0           "else { $nested ";
217             }
218             }
219             else { # Must be a regular statement...
220 0 0         if ($after_a_statement) {
221 0           $_->{STATEMENT};
222             }
223             else {
224 0           $after_a_statement = 1;
225 0           $nesting_depth++;
226 0           "else { $_->{STATEMENT}";
227             }
228             }
229 0 0         } @{[@pure_statements]} )
  0            
230             . (!$after_a_statement ? "else{}" : q{})
231             . ('}' x $nesting_depth)
232             . "}$matched{TRAILING_CODE}";
233             }
234              
235             # Otherwise, fail...
236 0           return;
237             }
238              
239             # Implement "given" keyword...
240 0     0     sub _given_impl { my ($source_ref) = @_; # Has to be this way because of code blocks in regex
241              
242             # First try the "pure" approach (only works on a limited selection of "given" blocks)...
243 0           my $REPLACEMENT_CODE = _pure_given_impl('given ' . ${$source_ref});
  0            
244              
245             # Otherwise recognize a valid general-purpose given block (with a single scalar argument)...
246 0 0         if (!defined $REPLACEMENT_CODE) {
247 0           state $VALIDATE_GIVEN = qr{
248             \A (?
249             $OWS \(
250             (? (?: $OWS (?> any | all | none ) $OWS => )?+ )
251             $OWS (?>(? (?&PerlExpression)))
252             $OWS \)
253             (?>
254             $OWS (?>(? (?&PerlBlock) ))
255             |
256             (?)
257             )
258             )
259             (?>(? .* ))
260             $PPR::GRAMMAR
261             }xms;
262 0           ${$source_ref} =~ $VALIDATE_GIVEN;
  0            
263              
264             # Extract components...
265 0           my %result = %+;
266              
267             # It's a valid "given"...
268 0 0         if (exists $result{BLOCK}) {
    0          
269             my ($GIVEN, $JUNC, $EXPR, $BLOCK, $TRAILING_CODE)
270 0           = @result{qw< GIVEN JUNC EXPR BLOCK TRAILING_CODE >};
271 0   0       $JUNC //= q{};
272              
273             # Augment the block with control flow and other necessary components...
274 0           $BLOCK = _augment_block(given => "$BLOCK", $JUNC);
275              
276             # Topicalize the "given" argument...
277 0           $EXPR = _apply_term_magic($EXPR);
278 0           substr($BLOCK, 1, 0) = qq{local *_ = \\($EXPR);};
279              
280             # Implement "given" as a (trivial) "if" block...
281 0           $REPLACEMENT_CODE = qq{ if (1) $BLOCK };
282              
283             # At what line should the "given" end???
284 0           my $end_line = (caller)[2] + $GIVEN =~ tr/\n//;
285              
286             # Append the trailing code (at the right line number)...
287 0           $REPLACEMENT_CODE .= "\n#line $end_line\n$TRAILING_CODE";
288             }
289              
290             # Otherwise, report the error in context...
291             elsif (exists $result{EXPR}) {
292 0           $REPLACEMENT_CODE = q{ BEGIN { warn q{Invalid code somewhere in "given" block starting} } }
293             . q{ BEGIN { warn qq{(Note: the error reported below may be misleading)\\n}}}
294 0           . qq{ if ${$source_ref} };
295             }
296             }
297              
298             # Install standard code in place of keyword...
299 0           ${$source_ref} = $REPLACEMENT_CODE;
  0            
300             }
301              
302              
303              
304             # Implementation of "when" keyword...
305 0     0     sub _when_impl ($source_ref) {
  0            
  0            
306 0           my ($REPLACEMENT_CODE, $TRAILING_CODE);
307              
308             # What various kinds of "when" look like...
309 0           state $WHEN_CLASSIFIER = qr{
310             \A (? $OWS
311             ( \(
312             (?:
313             $OWS (? (? true )
314             | (? false )
315             ) \b
316             |
317             (? (?: $OWS (?> any | all | none ) $OWS => )?+ )
318             $OWS (? (?&PerlExpression))
319             )
320             $OWS \)
321             $OWS (?>(? (?&PerlBlock) )
322             | (?)
323             )
324             |
325             (?>(? (?&PerlCommaList)))
326             (?>(?&PerlOWSOrEND)) (?> ; | (?= \} | \z ))
327             |
328             (? \N{0,20} )
329             )
330             )
331             (? .* )
332             $PPR::GRAMMAR
333             }xms;
334              
335             # Classify the type of "when" we're processing...
336 0           ${$source_ref} =~ $WHEN_CLASSIFIER;
  0            
337 0           my %matched = %+;
338              
339             # Handle a valid when block (with a list of scalar arguments)...
340 0 0 0       if (defined $matched{BLOCK} && defined $matched{EXPR}) {
    0          
    0          
341             my ($WHEN, $JUNC, $EXPR, $WHENTRUE, $WHENFALSE, $BLOCK, $TRAILING_CODE)
342 0           = @matched{qw< WHEN JUNC EXPR WHENTRUE WHENFALSE BLOCK TRAILING_CODE>};
343 0   0       $JUNC //= q{};
344              
345             # Adjust when's expression appropriately...
346 0           $EXPR = _apply_term_magic($EXPR);
347              
348             # Augment the block with control flow and other necessary components...
349 0           $BLOCK = _augment_block(when => "$BLOCK");
350 0 0         if ($WHENTRUE) { substr($BLOCK, 1, 0) = $WHENTRUEMSG; }
  0 0          
351 0           elsif ($WHENFALSE) { substr($BLOCK, 1, 0) = $WHENFALSEMSG; }
352              
353             # Is the current "given" junctive???
354 0   0       my $given_junc = $^H{'Switch::Right/GivenJunctive'} // q{};
355              
356             # Implement the "when" as an "if"...
357 0           $REPLACEMENT_CODE = qq{if(1)\{local \$Switch::Right::when_value = }
358             . qq{smartmatch($given_junc \$_, $JUNC scalar($EXPR));}
359             . qq{if(1){if (\$Switch::Right::when_value) $BLOCK }\}};
360              
361             # At what line should the "when" end???
362 0           my $end_line = (caller)[2] + $WHEN =~ tr/\n//;
363              
364             # Append the trailing code (at the right line number)...
365 0           $REPLACEMENT_CODE .= "\n#line $end_line\n$TRAILING_CODE";
366             }
367              
368             # Otherwise, reject the "when" with extreme prejudice...
369             elsif (defined $matched{MODIFIER}) {
370 0           $REPLACEMENT_CODE = qq{ BEGIN { die q{Can't specify postfix "when" modifier outside a "given"} } };
371             }
372             elsif (exists $matched{INVALID_BLOCK}) {
373 0           $REPLACEMENT_CODE = qq{ BEGIN { warn q{Invalid code block in "when"} } }
374             . qq{ BEGIN { warn qq{(Note: the error reported below may be misleading)\\n} } }
375 0           . qq{ if ${$source_ref} };
376             }
377             else {
378 0           $REPLACEMENT_CODE = qq{ BEGIN { die q{Incomprehensible "when" (near: $matched{INCOMPREHENSIBLE})} } };
379             }
380              
381             # Install code implementing keyword behaviour...
382 0           ${$source_ref} = $REPLACEMENT_CODE;
  0            
383             }
384              
385 0     0     sub _default_impl ($source_ref) {
  0            
  0            
386 0           state $DEFAULT_CLASSIFIER = qr{
387             (? $OWS (?>(? (?&PerlBlock) )) )
388             (? .* )
389             $PPR::GRAMMAR
390             }xms;
391              
392             # Verify that we match the syntax for a "default" block...
393 0           ${$source_ref} =~ $DEFAULT_CLASSIFIER;
  0            
394 0           my %matched = %+;
395              
396             # Implement the "default" block...
397 0 0         if (defined $matched{BLOCK}) {
398             # Install the necessary extras...
399 0           my $BLOCK = _augment_block(default => $matched{BLOCK});
400              
401             # Build the implementation of the "default"...
402 0           my $REPLACEMENT_CODE = qq{ if (1) $BLOCK };
403              
404             # At what line should the "default" end???
405 0           my $end_line = (caller)[2] + $matched{DEFAULT} =~ tr/\n//;
406              
407             # Append the trailing code (at the right line number)...
408 0           ${$source_ref} = "$REPLACEMENT_CODE\n#line $end_line\n$matched{TRAILING_CODE}";
  0            
409             }
410              
411             # Report the error...
412             else {
413 0           ${$source_ref}
  0            
414             = qq{ BEGIN { die q{Incomprehensible "default" (near: $matched{INCOMPREHENSIBLE})} } };
415             }
416             }
417              
418             # Implement the "continue" command...
419 0     0 0   sub continue () {
  0            
420             # Which "when" block are we in???
421 0           my $AFTERWHEN = (caller 0)[10]{'Switch::Right/Afterwhen'};
422              
423             # Jump out of it, if possible...
424 7     7   60 no warnings;
  7         14  
  7         1082  
425 0           eval { goto $AFTERWHEN };
  0            
426              
427             # If not possible, that's fatal...
428 0           croak q{Can't "continue" outside a "when" or "default"};
429             }
430              
431             # Implement the "break" command...
432 0     0 0   sub break () {
  0            
433             # Which "given" block are we in???
434 0           my $AFTERGIVEN = (caller 0)[10]{'Switch::Right/Aftergiven'};
435              
436             # Jump out of it, if possible...
437 7     7   64 no warnings;
  7         12  
  7         9513  
438 0           eval { goto $AFTERGIVEN };
  0            
439              
440             # If we weren't in a "given", can we jump out of a surrounding loop???
441 0           eval { next };
  0            
442              
443             # Otherwise, the "break" was illegal and must be punished...
444 0           croak q{Can't "break" outside a "given"};
445             }
446              
447              
448             # Insert unique identifying information into a "given"/"when"/"default" source code block...
449 0     0     sub _augment_block ($TYPE, $BLOCK, $JUNC = q{}) {
  0            
  0            
  0            
  0            
450             # Unique identifiers for each type of block...
451 0           state %ID;
452              
453             # Who and what is this block???
454 0 0         my $KIND = $TYPE eq 'default' ? "when" : $TYPE;
455 0           my $NAME = "After$KIND";
456 0           my $ID = $NAME . ++$ID{$KIND};
457              
458             # Give each block a unique name (uses refaliasing to create a lexical constant)...
459 0           substr($BLOCK, 1,0)
460             = qq{ BEGIN { \$^H{'Switch::Right/$NAME'} = '$ID'; } };
461              
462             # A when block auto-breaks at the end of its block...
463 0 0         if ($KIND eq 'when') {
    0          
464 0           my $AFTERGIVEN = $^H{'Switch::Right/Aftergiven'};
465 0 0         substr($BLOCK,-1,0)
466             = ';'
467             . (defined($AFTERGIVEN) ? qq{eval { no warnings; goto $AFTERGIVEN } || } : q{})
468             . qq{eval { no warnings; next } || die q{Can't "$TYPE" outside a topicalizer} };
469             }
470              
471             elsif ($KIND eq 'given') {
472             # Remember whether (and how) given was junctive...
473 0           substr($BLOCK, 1,0) = qq{ BEGIN { \$^H{'Switch::Right/GivenJunctive'} = '$JUNC'; } };
474              
475             # Given blocks must to pre-convert postfix "when" modifiers (which can't be keyworded)...
476 0           $BLOCK = _convert_postfix_whens($BLOCK);
477             }
478              
479             # Return identified block...
480 0           return "$BLOCK $ID:;";
481             }
482              
483             # Identify and pre-convert "EXPR when EXPR" syntax...
484 0     0     sub _convert_postfix_whens ($BLOCK) {
  0            
  0            
485             # Track locations of "when" modifiers in the block's source...
486 0           my @target_pos;
487              
488             # Extract those locations, whenever a statement has a "when" modifier...
489 0           $BLOCK =~ m{
490             \{ (?&PerlStatementSequence) \}
491              
492             (?(DEFINE)
493             (?
494             (?>
495             (?>(?&PerlPodSequence))
496             (?: (?>(?&PerlLabel)) (?&PerlOWSOrEND) )?+
497             (?>(?&PerlPodSequence))
498              
499             (?> (?&PerlKeyword)
500             | (?&PerlSubroutineDeclaration)
501             | (?&PerlMethodDeclaration)
502             | (?&PerlUseStatement)
503             | (?&PerlPackageDeclaration)
504             | (?&PerlClassDeclaration)
505             | (?&PerlFieldDeclaration)
506             | (?&PerlControlBlock)
507             | (?&PerlFormat)
508             |
509             # POSTFIX when HAS TO BE REWRITTEN BEFORE OTHER POSTFIX MODIFIERS ARE MATCHED...
510             (?
511             (? (?>(?&PerlExpression)) (?>(?&PerlOWS)) )
512             (?= when \b )
513             (? (?&PerlStatementModifier) (?>(?&PerlOWSOrEND)) )
514             (? (?> ; | (?= \} | \z )) )
515             )
516 0           (?{ my $len = length($+{MATCH});
517             unshift @target_pos, {
518             expr => $+{EXPR},
519             mod => substr($+{MOD},4),
520             end => $+{END},
521 0           from => pos() - $len,
522             len => $len,
523             }
524             })
525             |
526             (?>(?&PerlExpression)) (?>(?&PerlOWS))
527             (?&PerlStatementModifier)?+ (?>(?&PerlOWSOrEND))
528             (?> ; | (?= \} | \z ))
529             | (?&PerlBlock)
530             | ;
531             )
532             | # A yada-yada...
533             \.\.\. (?>(?&PerlOWSOrEND))
534             (?> ; | (?= \} | \z ))
535              
536             | # Just a label...
537             (?>(?&PerlLabel)) (?>(?&PerlOWSOrEND))
538             (?> ; | (?= \} | \z ))
539              
540             | # Just an empty statement...
541             (?>(?&PerlOWS)) ;
542             )
543             )
544             )
545             $PPR::X::GRAMMAR
546             }xms;
547              
548             # Replace each postfix "when"...
549 0           for my $pos (@target_pos) {
550             # Unique ID for the "when" (needed by continue())...
551 0           state $ID; $ID++;
  0            
552 0           state $JUNCTIVE_EXPR = qr{
553             $OWS (?>(? (?: any | all | none ) $OWS => $OWS | )) (? .* )
554             $PPR::GRAMMAR
555             }xms;
556              
557             # Unpack and enchant the "when" expression...
558 0           my ($JUNCTIVE, $MOD_EXPR) = (q{}, $pos->{mod});
559 0 0         if ($MOD_EXPR =~ $JUNCTIVE_EXPR) {
560 0           ($JUNCTIVE, $MOD_EXPR) = ( $+{JUNC}, _apply_term_magic($+{EXPR}) );
561             }
562              
563             # Convert postfix "when" to a postfix "if" (preserving Afterwhen info for continue())...
564             substr($BLOCK, $pos->{from}, $pos->{len})
565             = "BEGIN { \$^H{'Switch::Right/Afterwhenprev'} = \$^H{'Switch::Right/Afterwhen'};"
566             . " \$^H{'Switch::Right/Afterwhen'} = 'Afterpostfixwhen$ID'; }"
567             . "$pos->{expr}, break if smartmatch(\$_, $JUNCTIVE scalar($MOD_EXPR))"
568             . ";Afterpostfixwhen$ID:"
569             . "BEGIN { \$^H{'Switch::Right/Afterwhen'} = \$^H{'Switch::Right/Afterwhenprev'}; }"
570 0           . $pos->{end};
571             }
572              
573 0           return $BLOCK;
574             }
575              
576             # Change the target expression of a "when" to implement all the magic behaviours...
577 0     0     sub _apply_term_magic ($EXPR) {
  0            
  0            
578              
579             # Apply compile-time expression folding...
580 0           $EXPR = _simplify_expr($EXPR);
581              
582             # Adjust flip..flips to canonical booleans...
583 0 0 0       if ($EXPR =~ /\.\./ && $EXPR =~ $FLIP_FLOP) {
584 0           return "!!($EXPR)";
585             }
586              
587             # An @array or %hash gets enreferenced and then smartmatched.
588             # An @array[@slice] or %kv[@slice] gets appropriately wrapped and then smartmatched.
589             # Anything else is evaluated as-is...
590 0 0 0       return ($EXPR =~ /[\@%]/ && $EXPR =~ $CONTAINER_VARIABLE) ? "\\$EXPR"
    0 0        
    0 0        
591             : ($EXPR =~ /[\@]/ && $EXPR =~ $ARRAY_SLICE) ? "[$EXPR]"
592             : ($EXPR =~ /[\%]/ && $EXPR =~ $HASH_SLICE) ? "{$EXPR}"
593             : $EXPR;
594             }
595              
596              
597             # Reduce a compile-time expression to what the compiler actually sees...
598             # (Essential because that's what when() actually sees and how it decides
599             # whether or not smartmatch is magically distributive over a boolean expression)...
600 0     0     sub _simplify_expr ($code) {
  0            
  0            
601 7     7   63 no warnings;
  7         14  
  7         342  
602 7     7   45 use B::Deparse;
  7         194  
  7         487  
603 7     7   42 use builtin qw;
  7         11  
  7         1550  
604 0           state $deparse = B::Deparse->new;
605 0           return $deparse->coderef2text(eval qq{no strict; sub{ANSWER( scalar($code) );DONE()}})
606             =~ s{.* ANSWER \( \s* scalar \s* (.*) \) \s* ; \s* DONE() .* \z}{$1}gxmsr;
607             }
608              
609              
610             # Implement the new simpler, but shinier smartmatch operator...
611             # (Every one of the following four variants could each have been a set of multiple variants,
612             # but this way is currently still significantly faster)...
613              
614 7 0 0 7   1742653 multi smartmatch ($left, $right) {
  7 50 50 7   28  
  7 0   7   3504  
  7 0   7   55  
  7 0   7   14  
  7 0   7   4216  
  7 0   201244   72  
  7 50       13  
  7 100       645  
  7 50       43  
  7 50       11  
  7 50       396  
  7         33  
  7         10  
  7         4662  
  7         6672  
  0         0  
  7         22  
  7         12  
  7         98  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         37  
  7         46  
  7         64  
  201244         294904  
  201244         700098  
  201244         477389  
  804976         1609189  
  603732         1308771  
  201244         406970  
  0         0  
  201244         280973  
  201244         426197  
  201244         433303  
  201244         364800  
  201244         276895  
  201244         496876  
  0         0  
  0         0  
  7         29  
  7         48  
  7         1048  
  7         30  
  7         136  
  201939         320010  
  201939         265514  
  201939         482782  
  201939         473387  
  201939         371287  
  201939         433853  
615             # The standard error message for args that are objects (and which shouldn't be)...
616 201939 100       291872 state $OBJ_ARG = "Smartmatching an object breaks encapsulation";
  201939         335635  
  201939         424366  
617              
618             # Track "use integer" status in original caller (passing it down to nested smartmatches)...
619 201939   66     425651 local $Switch::Right::_use_integer = $Switch::Right::_use_integer // (caller 0)[8] & 0x1;
620              
621             # RHS undef only matches LHS undef...
622 201939 100       400084 return !defined $left if !defined $right;
623              
624             # RHS distinguished boolean always returns RHS value...
625 201786 100       423857 return $right if is_bool($right);
626              
627             # RHS objects use their SMARTMATCH method (if they have one)...
628 200365   100     630813 my $right_type = reftype($right) // 'VAL';
629 200365 100 100     633162 if ($right_type ne 'REGEXP' && blessed $right) {
630 98         161 try { return $right->SMARTMATCH($left) }
  98         1890  
631 92         14018 catch ($ERR) { croak "$OBJ_ARG ($ERR)" }
632             }
633              
634             # Otherwise, branch to the appropriate comparator (if any)...
635 200267   100     562422 my $left_type = reftype($left) // 'VAL';
636 200267   100     535610 my $left_is_obj = $left_type ne 'REGEXP' && blessed($left);
637 200267 100       312521 eval { goto ($left_is_obj ? 'OBJECT' : $left_type) . $right_type };
  200267         785721  
638              
639             # Otherwise, a RHS subref (with any non-subref LHS) acts like a boolean-returning test...
640 542 100       5145 return $right->($left) if $right_type eq 'CODE';
641              
642             # At thi spoint, no other combination of arguments will ever match...
643 281         8615 return false;
644              
645             # Objects can be used as LHS when matching an RHS value, but must be preprocessed...
646             OBJECTVAL:
647 4 50       19 if (created_as_number($right) ) {
648 0 0       0 croak $OBJ_ARG if !overload::Method($left, '0+');
649 0         0 $left = 0+$left;
650             }
651             else {
652 4 50       29 croak $OBJ_ARG if !overload::Method($left, q{""});
653 4         378 $left = "$left";
654             }
655              
656             # Compare two scalar values (or a suitably overloaded LHS object and an RHS value)...
657 199374 100       407345 VALVAL:
658             # 1. undef doesn't match a number or a string...
659             return false if !defined $left;
660              
661             # 2. Match primordial RHS numbers using == (respecting any ambient "use integer")...
662 199289 100       469094 if (created_as_number($right) ) {
663 778 100       3388 if (!looks_like_number($left)) { return false; }
  14 50       308  
664 7     7   4798 elsif ($Switch::Right::_use_integer) { use integer; return $left == $right; }
  7         124  
  7         44  
  0         0  
665 764         16157 else { return $left == $right; }
666             }
667              
668             # 3. Otherwise just use string equality...
669 198511         2726181 return $left eq $right;
670              
671             # RHS regexes match any defined non-ref value via =~ pattern-matching...
672 95   66     3047 VALREGEXP:
673             return defined($left) && $left =~ $right;
674              
675             # Compare two refs of the same type...
676 12         332 CODECODE:
677             return $left == $right;
678              
679 2   66     143 REGEXPREGEXP:
680             return $left == $right || $left eq $right;
681              
682 133 100       597 ARRAYARRAY:
683             return true if $left == $right; # ...they're the same array
684 122 100       185 return false if @{$left} != @{$right}; # ...different lengths so their contents can't match
  122         295  
  122         757  
685              
686             # Handle non-identical self-referential structures...
687 108         349 local %Sm4r7m4tCh::seen = %Sm4r7m4tCh::seen;
688 108 100 66     1314 return false if $Sm4r7m4tCh::seen{"L$left"}++ || $Sm4r7m4tCh::seen{"R$right"}++;
689              
690             # Otherwise, corresponding pairs of array elements must all smartmatch...
691 105         157 for my $n (keys @{$right}) {
  105         321  
692 447 100       1578 return false if !smartmatch($left->[$n], $right->[$n]);
693             }
694 76         1994 return true;
695              
696 100 100       756 HASHHASH:
697             return true if $left == $right; # ...they're the same hash
698 80 100       124 return false if keys %{$left} != keys %{$right}; # ...different numbers of keys, can't match
  80         213  
  80         990  
699              
700             # Handle non-identical self-referential structures...
701 58         429 local %Sm4r7m4tCh::seen = %Sm4r7m4tCh::seen;
702 58 50 33     600 return false if $Sm4r7m4tCh::seen{"L$left"}++ || $Sm4r7m4tCh::seen{"R$right"}++;
703              
704             # Otherwise, are they identical is structure???
705 58         96 for my $key (keys %{$left}) {
  58         719  
706             return false if !exists $right->{$key} # ...must have same keys
707 1408 100 100     5046 || !smartmatch($left->{$key}, $right->{$key}); # ...every value must match
708             }
709 43         1705 return true;
710              
711             # Every other REF/REF comparison, just checks for the same address..
712 4         4 FORMATFORMAT:;
713 4         4 IOIO:;
714 6         5 SCALARSCALAR:;
715 8         7 VSTRINGVSTRING:;
716 11         8 REFREF:;
717 13         13 GLOBGLOB:;
718 15         12 LVALUELVALUE:;
719 15         274 return $left == $right;
720 7         13 }
  201939         1014832  
721              
722             # Junctive smartmatching of the RHS list...
723 7 0 0 7   763764 multi smartmatch ($left, $junction =~ /^(?:any|all|none)$/, \@right) {
  7 50 0 7   39  
  7 0 100 7   3709  
  7 0 66 7   60  
  7 50   7   17  
  7 50   7   4830  
  7 50   7   71  
  7 50       14  
  7 0       689  
  7 0       55  
  7 0       17  
  7 50       511  
  7 100       67  
  7 100       17  
  7         1200  
  7         53  
  7         18  
  7         4405  
  7         77  
  0         0  
  7         23  
  7         17  
  7         56  
  7         50  
  7         72  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         26  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         1847  
  7         34  
  7         159  
  389         904  
  389         2806  
  318         593  
  318         451  
  318         1642  
  318         568  
  318         2000  
  300         1184  
  300         652  
  300         876  
724              
725             # Track "use integer" status in original caller (passing it down to nested smartmatches)...
726 300   33     411 local $Switch::Right::_use_integer = $Switch::Right::_use_integer // (caller 0)[8] & 0x1;
  300         2514  
727              
728             # Select junctive behaviour...
729 300         1238 goto $junction;
730              
731             # Disjunction...
732 146         304 any: for my $rval (@right) {
733 238 100       581 return true if smartmatch($left, $rval);
734             }
735 56         1570 return false;
736              
737             # Conjunction...
738 89         199 all: for my $rval (@right) {
739 153 100       312 return false if !smartmatch($left, $rval);
740             }
741 41         1312 return true;
742              
743             # Injunction...
744 65         148 none: for my $rval (@right) {
745 133 100       356 return false if smartmatch($left, $rval);
746             }
747 62         1332 return true;
748 7         18 }
  300         2202  
749              
750             # Junctive smartmatching of the LHS list...
751 7 0 0 7   701367 multi smartmatch ($junction =~ /^(?:any|all|none)$/, \@left, $right) {
  7 50 0 7   17  
  7 0 50 7   3099  
  7 0 33 7   70  
  7 50   7   11  
  7 50   7   4522  
  7 50   7   54  
  7 50       12  
  7 0       550  
  7 0       37  
  7 0       12  
  7 50       305  
  7 50       38  
  7 50       34  
  7         1139  
  7         44  
  7         10  
  7         4110  
  7         57  
  0         0  
  7         21  
  7         14  
  7         31  
  7         29  
  7         93  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         26  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         2392  
  7         70  
  7         199  
  89         167  
  89         544  
  89         177  
  89         132  
  89         404  
  89         261  
  89         656  
  89         344  
  89         230  
  89         283  
752             # Track "use integer" status in original caller (passing it down to nested smartmatches)...
753 89   33     133 local $Switch::Right::_use_integer = $Switch::Right::_use_integer // (caller 0)[8] & 0x1;
  89         797  
754              
755             # Dispatch on junctive type...
756 89         450 goto $junction;
757              
758             # Disjunction...
759 24         85 any: for my $lval (@left) {
760 58 100       157 return true if smartmatch($lval, $right);
761             }
762 8         147 return false;
763              
764             # Conjunction...
765 31         71 all: for my $lval (@left) {
766 57 100       759 return false if !smartmatch($lval, $right);
767             }
768 19         603 return true;
769              
770             # Injunction:
771 34         81 none: for my $lval (@left) {
772 57 100       145 return false if smartmatch($lval, $right);
773             }
774 31         575 return true;
775 7         12 }
  89         658  
776              
777              
778             # Junctive smartmatching of both LHS and RHS lists...
779 7 0 0 7   863843 multi smartmatch (
  7 50 0 7   17  
  7 0 50 7   3557  
  7 0 33 7   59  
  7 50 50 7   14  
  7 50 33 7   4342  
  7 50   7   54  
  7 50   7   15  
  7 0       580  
  7 0       43  
  7 0       14  
  7 50       373  
  7 50       40  
  7 50       13  
  7 50       1038  
  7 50       41  
  7         13  
  7         947  
  7         126  
  7         55  
  7         8310  
  7         131  
  0         0  
  7         42  
  7         95  
  7         56  
  7         29  
  7         65  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         20  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         2914  
  7         30  
  7         163  
  78         137  
  78         789  
  78         145  
  78         440  
  78         189  
  78         112  
  78         478  
  78         321  
  78         588  
  78         138  
  78         337  
  78         360  
  78         273  
  78         292  
780             $ljunction =~ m/^(?:any|all|none)$/, \@left,
781 78   33     126 $rjunction =~ m/^(?:any|all|none)$/, \@right
  78         715  
782             ) {
783             # Track "use integer" status in original caller (passing it down to nested smartmatches)...
784 78         477 local $Switch::Right::_use_integer = $Switch::Right::_use_integer // (caller 0)[8] & 0x1;
785              
786             # Dispatch according to the combination of junctive types...
787 25         62 goto "$ljunction$rjunction";
788 256         532  
789 573 100       1287 # The nine combinations...
790             anyany: for my $lval (@left) {
791 6         442 for my $rval (@right) {
792             return true if smartmatch($lval, $rval); # ...because any match is sufficient
793 7         20 }}
794 16         33 return false; # ...because no LHS value matched any RHS value
795 25 100       185  
796             anyall: for my $lval (@left) {
797 5         157 for my $rval (@right) {
798             next anyall if !smartmatch($lval, $rval); # ...because not all RHS vals match LHS
799 2         40 }
800             return true; # ...because all RHS values have matched some LHS value
801 3         6 }
802             return false; # ...because no LHS value matched all RHS values
803              
804             nonenone:; # This one's tricky: it means there isn't an LHS elem that matches no RHS elem,
805 17         47 # which is the same as all LHS elems matching at least one (i.e. any) RHS elem
806 922         2485 # so we just fall through to...
807 197991 100       465145  
808             allany: for my $lval (@left) {
809 2         109 for my $rval (@right) {
810             next allany if smartmatch($lval, $rval); # ...because at least 1 RHS value matched
811 15         966 }
812             return false; # ...because no RHS value matched the current LHS value
813 7         21 }
814 13         97 return true; # ...because every RHS value matched at least one RHS value
815 28 100       77  
816             allall: for my $lval (@left) {
817 5         168 for my $rval (@right) {
818             return false if !smartmatch($lval, $rval); # ...because a single mismatch is failure
819 4         11 }}
820 6         12 return true; # ...because every possible LHS/RHS combination matched
821 16 100       40  
822             noneany: for my $lval (@left) {
823 1         22 for my $rval (@right) {
824             return false if smartmatch($lval, $rval); # ...because a single match is failure
825 5         15 }}
826 13         28 return true; # ...because every LHS value failed to match any RHS value
827              
828 22 100       142 noneall: for my $lval (@left) {
829             for my $rval (@right) {
830             # This left elem is okay if it doesn't match at least one right elem...
831 2         39 next noneall if !smartmatch($lval, $rval);
832             # ...because every LHS value must mismatch at least one RHS value
833 3         120 }
834             return false; # ...because an LHS value did match all RHS values
835 8         22 }
836 13         122 return true; # ...because every LHS value failed to match at least one RHS value
837 24 100       118  
838             anynone: for my $lval (@left) {
839             for my $rval (@right) {
840 6         265 next anynone if smartmatch($lval, $rval);
841             # ...because this left elem matched an RHS value, so it can't be the chosen one
842 2         45 }
843             return true; # ...because an LHS did match none of the RHS values
844 5         14 }
845 11         25 return false; # ...because we didn''t find an LHS value that matched no RHS value
846 21 100       82  
847             allnone: for my $lval (@left) {
848 3         59 for my $rval (@right) {
849 7         13 return false if smartmatch($lval, $rval); # ...because any match is disqualifying
  78         815  
850             }}
851             return true; # ...because every LHS/RHS combination has now failed to match
852             }
853              
854             1; # Magic true value required at end of module
855             __END__