Is there a neat way of making a case or switch statement in Perl 5?. It seems to me they should include a switch on version 6..
I need this control structure in a script, and I've heard you can import a "switch module". But how can I achieve it without imports to minimize dependencies and acquire portability?
If you are using Perl 5.10 you have given/when which is a switch statement (note, it can do more than compare with regexes, read the linked docs to see its full potential):
#or any of the dozen other ways to tell 5.10 to use its new features
use feature qw/switch/;
given($string) {
when (/^abc/) { $abc = 1; }
when (/^def/) { $def = 1; }
when (/^xyz/) { $xyz = 1; }
default { $nothing = 1; }
}
If you are using Perl 5.8 or earlier you must make do with if/elsif/else statements:
if ($string =~ /^abc/) { $abc = 1; }
elsif ($string =~ /^def/) { $def = 1; }
elsif ($string =~ /^zyz/) { $xyz = 1; }
else { $nothing = 1; }
or nested condition operators (?:):
$string =~ /^abc/ ? $abc = 1 :
$string =~ /^def/ ? $def = 1 :
$string =~ /^xyz/ ? $xyz = 1 :
$nothing = 1;
There is a module in Core Perl (Switch) that gives you fake switch statements via source filters, but it is my understanding that it is fragile:
use Switch;
switch ($string) {
case /^abc/ {
case /^abc/ { $abc = 1 }
case /^def/ { $def = 1 }
case /^xyz/ { $xyz = 1 }
else { $nothing = 1 }
}
or the alternate syntax
use Switch 'Perl6';
given ($string) {
when /^abc/ { $abc = 1; }
when /^def/ { $def = 1; }
when /^xyz/ { $xyz = 1; }
default { $nothing = 1; }
}
The suggestion in Programming Perl is:
for ($string) {
/abc/ and do {$abc = 1; last;};
/def/ and do {$def = 1; last;};
/xyz/ and do {$xyz = 1; last;};
$nothing = 1;
}
Just a short comment about the core Switch module that's been mentioned a couple of times in answers. The module in question relies on source filters. Among other things, that may result in wrong lines reported for errors. It's so bad that none of the core developers really remembers or cares to remember why it was accepted into the perl core in the first place.
Furthermore, Switch.pm will be the first Perl module ever to be removed from the perl core. The next major release of perl, 5.12.0, will still have it, albeit with a deprecation warning. That deprecation warning will go away if you explicitly install Switch.pm from CPAN. (You get what you ask for.) In the next release down the road, 5.14, Switch.pm will be entirely removed from core.
An equivalent solution that I like is a dispatch table.
my $switch = {
'case1' => sub { print "case1"; },
'case2' => sub { print "case2"; },
'default' => sub { print "unrecognized"; }
};
$switch->{$case} ? $switch->{$case}->() : $switch->{'default'}->();
print("OK : 1 - CANCEL : 2\n");
my $value = <STDIN>;
SWITCH: {
($value == 1) && last(SWITCH);
($value == 2) && do {print("Cancelled\n"); exit()};
print("??\n");
}
Related
I'm going through some Perl scripts I wrote some time ago to pseudocode them. A friend helped with writing this script, but I was wondering if someone could tell me the role of the IF (length($_)) statement? Here's the whole script:
#!/usr/bin/perl
use strict;
use warnings;
my $inDataset = 0;
while(<>)
{
chomp;
s/\s*\\$//;
if(/________/)
{
$inDataset = 1;
}
elsif(/-------/)
{
$inDataset = 0;
}
elsif($inDataset == 1)
{
if(length($_))
{
ProcessData($_);
}
}
}
sub ProcessData
{
my ($line) = #_;
my #fields = split(/\s+/,$line);
if($fields[3] =~ /p\.(...)(\d+)(...)/)
{
my $native = $1;
my $resnum = $2;
my $mutant = $3;
print "$fields[1] $native $resnum $mutant\n";
}
}
Thanks in advance for any help!
length($_) returns the length in characters of $_.
if ( length($_) ) evaluates to true if length($_) is defined and non-zero. So the conditional in your code runs ProcessData($_) only if $_ has characters.
This program throws an error. The problem is that I have to use switch case. How can I do this in Perl?
#use strict;
#use warnings;
my $input = print "Enter the number";
$input = <STDIN>;
switch($input){
case "1" {print "UPC"}
case "2" {print "ES"}
case "3" {print "MS"}
else {print "Enter the correct value"}
}
You need to import Switch to use it:
use Switch;
However, Switch has been deprecated. See this question: Why is the Switch module deprecated in Perl?
Some alternatives (and their experimental status) are discussed here: http://perldoc.perl.org/perlsyn.html#Switch-Statements
In summary, if you're using Perl >5.10.1, you can use the following for a non-deprecated, non-experimental switch:
use v5.10.1;
for ($var) {
when (/^abc/) { $abc = 1 }
when (/^def/) { $def = 1 }
when (/^xyz/) { $xyz = 1 }
default { $nothing = 1 }
}
Perl's built in version of the case statement is a little different:
use feature "switch";
given ($foo) {
when (/^abc/) { $abc = 1; }
when (/^def/) { $def = 1; }
when (/^xyz/) { $xyz = 1; }
default { $nothing = 1; }
}
You can add a more traditional case statement with use Switch;, but this is deprecated as RobEarl points out.
Also, never comment out use strict; use warnings; as an attempt to fix problems!
In our company we were using this code (given at the end) for about 10 years and it worked fine.
Some days ago we faced some issues and we had to re-code the complete package, we decided to replace this code with Switch module by Damian (in order to improve the readability of code).
Everything is working fine for us.
Later I found on Perlmonks that Damian had put this module under
Damian modules you shouldn't use in production because their purpose
is to explore and prototype future core language features.
But it is working fine for us because we are not hitting the limitations of this module (I guess).
Now I ask you guys to please have a look at the both implementations (nested if else vs switch) and let me know whether using Switch in the newer implementation is fine or are we creating some future problems for us? Is using Switch in the code given below fine or are there any hidden bugs/problems?
I've already read the bugs and reviews of this module on CPAN and Perlmonks and I guess our code is far away from hitting those bugs (I think so).
We are using Perl 5.8.5.
PS: I know the alternatives of Switch, we have given/when in Perl 5.10, we can use dispatch table and other solutions which are specified here, but right now we just want to compare the new implementation which uses Switch.
Using nested if else
if ($command =~ /^enter$/) {
$self->show_main_frames();
}
elsif ($command =~ /^XYZ_MENU/i) {
$self->show_main_menu($manual, $dbot);
}
elsif ($command =~ /^DBOT/i) {
$dbot->process();
}
# XML is used for the reminders-history: Request 2666
elsif ($command =~ /^XML_DBOT/i) {
$dbot->process();
}
elsif ($command =~ /^UGS/i) {
$ugsui->process();
}
elsif ($command eq "kill") {
my $login = $self->{COMMON_HASH}{login} || "";
my $su_login = $self->{CONF}->get("start", "SU_LOGIN");
if ($login eq $su_login) {
# usually only certain user with certain permission will be
# able to do this.
$self->do_error("Daemon was killed by ".$login);
$self->db_connection->disconnect();
$self->{LOG}->write("User $login killed the daemon", 0);
exit; # this 'exit' actually kill the daemon
}
else {
$self->do_error("User $login tried to kill the daemon. ".
"This incident will be reported");
$self->{LOG}->write("User $login tried to kill the daemon", 2);
}
}
elsif ($command eq "logout") {
# check if we should delete the password cookie
my $forget_me = $self->{CGI}->param("forget_me") || 0;
if ($forget_me) {
$self->{DB_PASSWORD_COOKIE}->delete_cookie();
}
$ugsui->do_logout();
# Cliff edit remove id from logged_in
$session->remove_session($session->login());
# delete the session of the user
delete $self->{SESSIONS}{$session->id()};
if ($self->{CACHE_TO_FILE}) {
my $session_data_path =
XYZ_DIR
."/code/cache/session_data"
.$session->id();
unlink($session_data_path);
}
}
# if we just login we should create all the main frames
elsif ($command eq "login") {
# if extra_param holds "command*XXX" the XXX will be placed instead of
# the command. extra_param holds pairs that are astrics-separated
my $extra_param = $cgi->param("extra_param");
$extra_param = "" if (!defined($extra_param));
$extra_param =~ /command\*([^\*]+)/i;
my $other_command = defined($1) ? $1 : "";
if ($other_command =~ /^dbot/i) { # meanwhile - works only on dbot
# commands
$command = $other_command;
# now we will get the other parameters from the extra_param
# (actually including the command that is still in the
# $extra_param)
while ($extra_param =~ /^\*?([^\*]+)\*([^\*]+)(.*)/) {
$extra_param = $3;
my $name = $1;
my $value = $2;
$cgi->param(-name => $name,
-value => $value);
}#end while
}#end if
else{
$self->show_main_frames();
}
}#end elsif
else {
$self->show_main_frames();
}#end outer else
Using Switch
switch ($command)
{
case /^enter$/ { $self->show_main_frames() }
case /^XYZ_MENU/i { $self->show_main_menu($manual, $dbot) }
case /^DBOT/i { $dbot->process() }
case /^XML_DBOT/i { $dbot->process() }
case /^UGS/i { $ugsui->process() }
case "kill" {
my $login = $self->{COMMON_HASH}{login} || "";
my $su_login = $self->{CONF}->get("start", "SU_LOGIN");
if ($login eq $su_login) {
# usually only certain user with certain permission will be
# able to do this.
$self->do_error("Daemon was killed by ".$login);
$self->db_connection->disconnect();
$self->{LOG}->write("User $login killed the daemon", 0);
exit; # this 'exit' actually kill the daemon
}
else {
$self->do_error("User $login tried to kill the daemon. ".
"This incident will be reported");
$self->{LOG}->write("User $login tried to kill the daemon", 2);
}
}
case "logout" {
# check if we should delete the password cookie
my $forget_me = $self->{CGI}->param("forget_me") || 0;
if ($forget_me) {
$self->{DB_PASSWORD_COOKIE}->delete_cookie();
}
$ugsui->do_logout();
# Cliff edit remove id from logged_in
$session->remove_session($session->login());
# delete the session of the user
delete $self->{SESSIONS}{$session->id()};
if ($self->{CACHE_TO_FILE}) {
my $session_data_path =
XYZ_DIR
."/code/cache/session_data"
.$session->id();
unlink($session_data_path);
}
}
case "login" {
# if extra_param holds "command*XXX" the XXX will be placed instead of
# the command. extra_param holds pairs that are astrics-separated
my $extra_param = $cgi->param("extra_param");
$extra_param = "" if (!defined($extra_param));
$extra_param =~ /command\*([^\*]+)/i;
my $other_command = defined($1) ? $1 : "";
if ($other_command =~ /^dbot/i)
{ # meanwhile - works only on dbot
# commands
$command = $other_command;
# now we will get the other parameters from the extra_param
# (actually including the command that is still in the
# $extra_param)
while ($extra_param =~ /^\*?([^\*]+)\*([^\*]+)(.*)/) {
$extra_param = $3;
my $name = $1;
my $value = $2;
$cgi->param(-name => $name,
-value => $value);
}#end while
}#end if
else {$self->show_main_frames();}
}
else {$self->show_main_frames();}
} # end switch
Switch does its own parsing of the source code. This can lead to hard to diagnose errors in the code that directly uses it. The kind of problems Switch creates are not intermittent, so if your code works, you have nothing to worry about.
But really, it doesn't add much at all.
With Switch:
switch ($command) {
case /^enter$/ { $self->show_main_frames() }
case /^XYZ_MENU/i { $self->show_main_menu($manual, $dbot) }
case /^DBOT/i { $dbot->process() }
case /^XML_DBOT/i { $dbot->process() }
case /^UGS/i { $ugsui->process() }
case "kill" {
my $login = $self->{COMMON_HASH}{login} || "";
Without Switch:
for ($command) {
if (/^enter$/) { $self->show_main_frames() }
elsif (/^XYZ_MENU/i) { $self->show_main_menu($manual, $dbot) }
elsif (/^DBOT/i) { $dbot->process() }
elsif (/^XML_DBOT/i) { $dbot->process() }
elsif (/^UGS/i) { $ugsui->process() }
elsif ($_ eq "kill") {
my $login = $self->{COMMON_HASH}{login} || "";
(elsif (/^kill\z/) would also work.)
Actually Switch module does not provide you any "killer feature"; the same can be done with elsif statement which is secure, stable and does not have drawbacks that Switch does. Here is problems with Switch i got in my project (and i dont use it anymore):
Switch is made throgh Perl filters. This technique have following limits:
Your source code actually rewritten on-the-fly and replaces with
sequent elsif statements.
Some Perl error reports will refer wrong line; some of them showing code you dont have in your source (autogenerated code).
Not filter limit, but limit of module itself:
If the file(.pl or .pm) where you call use Swtich excess 1Mbyte size this can lead to "mysterious errors" (as written in doc). I can confirm these errors do not leading to Switch module and is completely unobivious, so you can have hard debug time after some weeks of coding/documentation.
I recommend to use elsif or given..when statements which is available since Perl 5.10. So if you using perl 5.8.x - use elsif.
Also you can read "Limitations" paragraph for Switch documentation.
Because Switch does own source code parsing, it does not work at all in certain circumstances. For example, it is impossible to use it with mod_perl.
However, if you have Perl 5.10 or later, there is much better replacement with effectively the same functionality: given/when
use v5.10;
given ($var) {
when (/^abc/) { $abc = 1 }
when (/^def/) { $def = 1 }
when (/^xyz/) { $xyz = 1 }
default { $nothing = 1 }
}
given is supported by Perl core (and works everywhere, including mod_perl) - you just use v5.10; and it is instantly available to you.
This question is about Perl DBI (I use it with MySQL).
I want the following code:
{
local $dbh->{AutoCommit} = 0;
...
if(...) {
$dbh->rollback;
}
...
}
Will it work as expected? (I mean no superfluous commit after rollback) Is $dbh->{AutoCommit} compatible with $dbh->begin_work and $dbh->rollback?
Yes, you can do that but why would you want to. Why not just call begin_work and then commit or rollback. They work fine even if AutoCommit is on.
use strict;
use warnings;
use DBI;
use Data::Dumper;
my $h = DBI->connect();
eval {
$h->do(q/drop table mje/);
};
$h->do(q/create table mje (a int)/);
my $s = $h->prepare(q/insert into mje values(?)/);
foreach my $it(1..2) {
{
local $h->{AutoCommit} = 0;
$s->execute($it);
if ($it == 2) {
$h->rollback;
} else {
$h->commit;
}
}
}
my $r = $h->selectall_arrayref(q/select * from mje/);
print Dumper($r);
outputs:
$VAR1 = [
[
1
]
];
but the following looks better to me:
foreach my $it(1..2) {
$h->begin_work;
$s->execute($it);
if ($it == 2) {
$h->rollback;
} else {
$h->commit;
}
}
When parsing text, I frequently need to implement mini-state-machines, in the generic form following the code below.
Is there a CPAN module that's considered "best practice" and well suited to implement state machine logic like this in an easy and elegant way?
I would prefer solutions less complicated than Parse::RecDescent but if none exist and Parse::RecDescent is a lot easier to apply to this problem than I thought, I'm very willing to consider it instead of rolling my own like I've been so far.
Example generic parsing code:
my $state = 1;
while (my $token = get_next_token()) { # Usually next line
if ($state == 1) {
do_state1_processing();
if (token_matches_transition_1_to_2($token)) {
do_state_1_to_2_transition_processing();
$state == 2;
next;
} elsif (token_matches_transition_1_to_4($token)) {
do_state_1_to_4_transition_processing();
$state == 4;
next;
} else {
do_state1_continuation();
next;
}
} elsif ($state == 5) {
do_state5_processing();
if (token_matches_transition_5_to_6($token)) {
do_state_5_to_6_transition_processing();
$state == 6;
next;
} elsif (token_matches_transition_5_to_4($token)) {
do_state_5_to_4_transition_processing();
$state == 4;
next;
} else {
do_state5_continuation();
next;
}
} else {
}
}
I would recommend taking a look at Marpa and Marpa::XS.
Just look at this simple calculator.
my $grammar = Marpa::XS::Grammar->new(
{ start => 'Expression',
actions => 'My_Actions',
default_action => 'first_arg',
rules => [
{ lhs => 'Expression', rhs => [qw'Term'] },
{ lhs => 'Term', rhs => [qw'Factor'] },
{ lhs => 'Factor', rhs => [qw'Number'] },
{ lhs => 'Term', rhs => [qw'Term Add Term'], action => 'do_add' },
{ lhs => 'Factor',
rhs => [qw'Factor Multiply Factor'],
action => 'do_multiply'
},
],
}
);
You will have to implement the tokenizer yourself.
You can use Class::StateMachine:
package Foo;
use parent 'Class::StateMachine';
sub new {
my $class = shift;
Class::StateMachine::bless {}, $class, 'state_1';
}
sub do_state_processing :OnState('state_1') {
my $self = shift;
if (...) { $self->event_1 }
elsif (...) { $self->event_2 }
...
}
sub do_state_processing :OnState('state_2') {
...
}
sub event_1 :OnState('state_1') {
my $self = shift;
$self->state('state_2');
}
sub event_2 :OnState('state_2') {
my $self = shift;
$self->state('state_3');
}
sub enter_state :OnState('state_1') {
print "entering state 1";
...
}
sub enter_state :OnState('state_2') {
...
}
package main;
my $sm = Foo->new;
...
while (my $token = get_next_token()) {
$sm->do_state_processing;
}
Though, a module specific to text processing will probably be more appropriate for your particular case
(With help) I wrote something a few years back called the Perl Formal Language Toolkit so this might serve as some sort of basis, however what I think you really want is a tool like the Ragel Finite State Machine Compiler. Unfortunately, it doesn't output to Perl, and it's a back- burner desire of mine to implement a Perl target for Ragel and to also provide similar (but more Perl oriented) features for my bit-rotting module.
I wrote Parser::MGC mostly because I found trying to get Parse::RecDescent to do proper error reporting was quite difficult, and I disliked its bizarre custom embedded grammar in string quotes that contain perl code, alongside the other perl code. A P::MGC program is just perl code; but written as a recursive descent on the grammar structure similar to P::RD.