Return-Path: tchrist Sun From: Tom Christiansen cc: merlyn,hal@netcom.com,u810561@Oz.nthu.edu.tw Newsgroups: comp.lang.perl Subject: Far More Than Everything You Ever Wanted To Know About Switch/Case Statements and do{} blocks (was: How to do "Shell case" in perl?) Summary: References: <3ml7fm$sae@news.csie.nctu.edu.tw> Reply-To: tchrist@mox.perl.com (Tom Christiansen) Followup-To: Distribution: Organization: Perl Consulting and Training Keywords: In comp.lang.perl, hal@netcom.com (Hal Wine) writes: :In article <3ml7fm$sae@news.csie.nctu.edu.tw>, u810561@Oz.nthu.edu.tw wrote: : :>I'm a newbie in perl. First, a pet peeve: s/newbie/neophyte/ s/beginner/neophyte/ s/a newbie in/new to/ I find "Newbie" to be a grating, overly-cutsie buzzword (read: obsequious neologism) used by people who are either trying to be overly-cutsie or else who don't know that "neophyte", "beginner" or "I'm new to" work just fine. It sounds like something you'd be likely to find in that offensively entitled line of books called "XXX for Dummies". Anyway, enough of these useless, idiosyncratic word things, or Larry will yell at me. :-) :>I wrote a shell script with :>for $xx in $@ :> do :> case $xx in :> a) do..;; :> b) do..;; :> *) do..;; :> esac :> done : :Based on the Camel book (p 98): : :foreach $xx ($@) { : CASE: { : $_ = $xx; : /^a$/ && do { ...; last CASE; }; : /^b$/ && do { ...; last CASE; }; : ... # *) default case code : } :} :Yes, there could be optimizations, but this is the closest mechanical :change (IMO) to get you going. You'll have to change the regular :expressions from shell style to perl style, too. Um... not quite. Not $@ but @ARGV. But the essential point is correct: since you don't have an official case or switch statement in Perl, you have to construct one on the fly, generally using do{} as a term rather than as a do{}while/until. For a simple operation, you can often in-line it: $FILE = do { if ($opt_f) { $opt_f } elsif (-f STDIN) { '<&STDIN' } else { $DEF_FILE } }; Although plenty of other things are possible, like the plain ole: if ($a == 1) { f1() } elsif ($a == 2) { f2() } elsif ($a == 3) { f3() } elsif ($a == 4) { f4() } else { f5() } Or via a jump table: @codes = ( \&f1, \&f2, \&f3, \&f4 ); $c = $a - 1; if (defined &{ $codes[$c] } ) { &{ $codes[$c] }(); } else { f5(); } Or even as an associative array with anonymous subroutines. This is a much better approach than Randal's case statement >from chapter 5 of the Camel: %cmd_table = ( key1 => sub { # stuff here }, key2 => sub { # stuff here }, key3 => sub { # stuff here }, ); And then to call it if (defined &{ $cmd_table{$cmd} } ) { &{ $cmd_table{$cmd} }(); } Or maybe you want to find the first match: for (keys %cmd_table) { if ( /^$cmd/i ) { &{ $cmd_table{$_} }(); } } Although that has a performance issue with the run-time interpolation of the pattern, and I'm going to avoid discussing it since this article isn't called "Far More Than Everything You Ever Wanted To Know About Regular Expressions".:-) To replicate your example there using the command-line arguments, we often use something like this: SWITCH: for (@ARGV) { /^abc/ && do { $abc = 1; last SWITCH; }; /^def/ && do { $def = 1; last SWITCH; }; /^xyz/ && do { $xyz = 1; last SWITCH; }; $nothing = 1; } Of if $_ already has what you want, and you want to do some fancy stuff: SWITCH: { /foo/ && do { something(); last SWITCH; }; # semi is required /bar/ && do { something_else(); # FALLTHRU! }; /glarch/ && do { if ($xyzzy) { $_ = bizarre(); redo SWITCH; } }; # default code here } Of course, if that kind of thing make you nervous, there's no reason you can't write it with "real" if's: SWITCH: { if (/foo/) { something(); last SWITCH; } if (/bar/) { something_else(); # FALLTHRU to next test } if (/glarch/) { if ($xyzzy) { $_ = bizarre(); redo SWITCH; } } # default code here } This is a strategy people sometimes use with just plain if's, in fact: if ($cond) {{ # kinda confusing # code here last if $i > 10; # other code here }} Which could have been written: IF_BLOCK: { if ($cond) { # code here last IF_BLOCK if $i > 10; # other code here } } Or even: IF: { if ($cond) { # code here last IF if $i > 10; # other code here } } Or even: { if ($cond) { # code here last if $i > 10; # other code here } } In fact, you absolutely must do such a thing if you're trying to get out of a do{}while/until, since last/next/redo don't work there. FIND_J: { do { # code here last FIND_J if $cond; } until $j > 100; } Of course, that doesn't help on the next or redo: do {{ # code here next if $cond; }} until $j > 100; Or more clearly as: do { FIND_J: { # code here next FIND_J if $cond; } } until $j > 100; Or dealing with both of them: OUTER_J: { do { INNER_J: { # code here redo INNER_J if $cond1; # code here next INNER_J if $cond2; # code here last OUTER_J if $cond3; # code here } } until $j > 100; } Hmmm... maybe we might as well just use goto's. :-) It's certainly more than disgusting enough to make you think that just maybe next/last/redo ought to be made to work inside do{}. :-( In parting, I'd like to draw your attention to the trouble I've (usually) gone through to line up parallel constructs throughout this article. It makes things a lot easier to read than if you jamming it all together. Never let the GNU-Emacs/OS's pretty-printer touch this code! :-) --tom .