NAME
Switch::Plain - a simple switch statement for Perl
SYNOPSIS
use Switch::Plain;
# string version
sswitch (get_me_a_string()) {
# return value of get_me_a_string() is bound to $_ in this block
case 'foo': {
# runs if $_ equals 'foo'
}
case 'bar': {
# runs if $_ equals 'bar'
}
case 'melonlord' if $DEBUG: {
# runs if $_ equals 'melonlord' and $DEBUG is true
}
default if $VERBOSE > 1: {
# runs if nothing else matched so far and $VERBOSE is greater than 1
}
default: {
# runs if nothing else matched so far
}
}
# number version
nswitch (get_me_a_number()) {
# return value of get_me_a_number() is bound to $_ in this block
case 1: {
# runs if $_ equals 1
}
case 2: {
# runs if $_ equals 2
}
case 99 if $DEBUG: {
# runs if $_ equals 99 and $DEBUG is true
}
default if $VERBOSE > 1: {
# runs if nothing else matched so far and $VERBOSE is greater than 1
}
default: {
# runs if nothing else matched so far
}
}
DESCRIPTION
This module provides (yet another) switch statement for Perl. Differences between this module and Switch include:
It's not a source filter. (It uses perl's pluggable keywords instead.)
It generates non-horrible code. If you want to see this for yourself, run some sample code through
perl -MO=Deparse
.It doesn't try to be smart about matching fancy data structures; it only does simple string or numeric equality tests. This also sets it apart from perl's built in
given
statement and smartmatch operator~~
.
Syntax
This module understands the following grammar:
switch_statement := switch_keyword switch_scrutinee switch_body
switch_keyword := 'sswitch' | 'nswitch'
switch_scrutinee := '(' EXPR ')'
switch_body := '{' case_clause* '}'
case_clause := case_pattern+ BLOCK
case_pattern := case_keyword case_modifier? ':'
case_keyword := 'default' | 'case' EXPR
case_modifier := 'if' EXPR | 'unless' EXPR
*
, +
, and ?
have their usual regex meaning; BLOCK
and EXPR
represent standard Perl blocks and expressions, respectively.
Semantics
The meaning of a switch statement is given by the following translation rules:
sswitch (FOO) { ... }
andnswitch (FOO) { ... }
turn intodo { local *_ = \FOO; ... };
That is, they alias
$_
toFOO
within the body of the switch statement.A series of case clauses in the switch body turns into a single
if
/elsif
chain. That is, the first clause becomes anif
; every subsequent clause becomes anelsif
.case FOO:
becomesif ($_ eq FOO)
forsswitch
andif ($_ == FOO)
fornswitch
.default:
becomesif (1)
.case FOO if BAR:
becomesif ($_ eq FOO && BAR)
forsswitch
andif ($_ == FOO && BAR)
fornswitch
.default if BAR:
becomesif (BAR)
.... unless BAR
works similarly, but with the condition inverted (!BAR
).If there are multiple
case
/default
s before a single block, their conditions are combined with||
.
Here's an example demonstrating all combinations:
sswitch (SCRUTINEE) {
case FOO0: {
BODY0
}
case FOO1:
case FOO2 if BAR1:
case FOO3 unless BAR2:
default if BAR3:
default unless BAR4: {
BODY1
}
default: {
BODY2
}
}
This is equivalent to:
do {
# temporarily alias $_ to SCRUTINEE within this block:
local *_ = \SCRUTINEE;
if ($_ eq FOO0) {
BODY0
}
elsif (
$_ eq FOO1 ||
($_ eq FOO2 && BAR1) ||
($_ eq FOO3 && !BAR2) ||
BAR3 ||
!BAR4
) {
BODY1
}
elsif (1) {
BODY2
}
};
Differences between Switch::Plain
and C's switch
C's
switch
is limited to integer scrutinees.Switch::Plain
supports any number or string.C's
case
labels must be compile-time constants.Switch::Plain
allows any expression and even additional arbitrary conditions via theif
/unless
case modifiers.C's
case
labels are actual labels in that they can appear anywhere within theswitch
statement's body (even nested). WithSwitch::Plain
allcase
s must be at the top level.In C the order of the
case
s does not matter since they're all known at compile time and guaranteed to be distinct.Switch::Plain
evaluates them in the order they're written: If you putdefault: { ... }
in the middle of aswitch
, it will intercept all values, and any followingcase
s will be ignored (this is like writing... elsif (1) { ... } else { ... }
).Since C's
case
labels are actual labels and C'sswitch
is effectively a dynamicgoto
, C actually has no concept of a "case clause" or a "case block".switch
simply transfers control to one of thecase
s and that's it. This has the side effect of "fallthrough" behavior if you want to use C'sswitch
to check for multiple distinct cases; that is, you must insert an explicitbreak;
to leave theswitch
statement when you're done with your case.Switch::Plain
has nothing of the kind. Because it turns into a singleif
/elsif
chain and every case block is clearly delimited, execution ofsswitch
/nswitch
stops as soon one case pattern matches. However, it is possible to attach multiple case patterns to a single block:case 2: case 3: case 5: { ... }
This trivial case works the same way as fallthrough would in C (any value of 2, 3, or 5 is accepted).
Since C's
break
refers to the innermost enclosingswitch
or loop, you can't use it inswitch
to leave a surrounding loop (you have to usegoto
instead). This particular problem would be avoidable in Perl thanks to loop labels; however, this isn't even necessary becausesswitch
/nswitch
work likeif
: They don't count as loops andlast
/next
/redo
ignore them.
Scoping
This module is a lexical pragma, i.e. the effects of use Switch::Plain
(turning sswitch
and nswitch
into keywords) are scoped to the innermost enclosing block (or the whole file if there is no enclosing block).
If you are a module author who wants to wrap Switch::Plain
from another module, simply call Switch::Plain->import
from your own import
method. It will affect whatever scope is currently being compiled (i.e. your caller).
AUTHOR
Lukas Mai, <l.mai at web.de>
COPYRIGHT & LICENSE
Copyright 2012-2013 Lukas Mai.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.