Reputation: 594
I am writing a small perl script, primarily to learn the language. Basically it has an action dispatch table. Based on the user input any one of the target actions will be invoked. Each action is a small, independent utility function (say print time), working on which allows me to explore and learn different aspects of perl.
I have run into a problem with the dispatch mechanism. The script runs in a continuous loop, each time taking a user request for an action. This input is compared against the regular expressions of each available action. If there is a match, that action is executed and it breaks out of the match loop to read user's next request. The problem I am facing is that, if I request for the same action twice, it is not matching the second time. If I print the dispatch table immediately after the match, the matched action entry seems to be missing. If I continuously request for same action, it works only on alternate invocations. If I avoid the "last LABEL", it works without any issues.
Perl version is 5.12.4 (on Fedora 15, 32 bit). Below is a simplified but complete example. I am still a beginner in perl. Please excuse if it doesn't meet the standards of a monk :) Kindly help in figuring out the issue with this code. Your help is much appreciated.
#!/bin/env perl
use strict ;
use warnings ;
use Text::ParseWords ;
my @Actions ;
my $Quit ;
sub main
{
# Dispatch table
# Each row has [syntax, help, {RegExp1 => Function1, RegExp2 => Function2,...}]
# There can be multiple RegExps depending on optional arguments in user input
@Actions =
(
['first <value>', 'Print first', {'first (.*)' => \&PrintFirst} ],
['second <value>', 'Print second', {'second (.*)' => \&PrintSecond} ],
['quit', 'Exits the script', {'quit' => \&Quit} ]
) ;
my $CommandLine ;
my @Captures ;
my $RegEx ;
my $Function ;
while(!$Quit)
{
# Get user input, repeat until there is some entry
while(!$CommandLine)
{
print "\$ " ;
my $argline = <STDIN> ;
my @args = shellwords($argline) ;
$CommandLine = join (" ", grep { length() } @args) ;
}
# Find and execute the action matching given input
# For each entry in the @Actions array
ExecAction: foreach my $Action (@Actions)
{
# For each RegExp for that action (only 1 per action in this example)
while (($RegEx, $Function) = each %{@$Action[2]})
{
if (@Captures = $CommandLine =~ m/^$RegEx$/i)
{
print "Match : $RegEx\n" ;
&$Function(@Captures) ;
last ExecAction ; # Works if this line is commented
}
}
}
$CommandLine = undef ;
}
}
sub PrintFirst { print "first $_[0]\n" ; }
sub PrintSecond { print "second $_[0]\n" ; }
sub Quit { $Quit = 1 ; }
main ;
Upvotes: 1
Views: 1781
Reputation: 386676
You need to reset the hash's iterator if you're going to break out of an each
loop
$ perl -E'
%h=(a=>4,b=>5,c=>6,d=>7);
while (my ($k, $v) = each %h) { last if ++$i == 2 }
keys %h if $ARGV[0];
while (my ($k, $v) = each %h) { say "$k:$v"; }
' 0
b:5
d:7
$ perl -E'
%h=(a=>4,b=>5,c=>6,d=>7);
while (my ($k, $v) = each %h) { last if ++$i == 2 }
keys %h if $ARGV[0];
while (my ($k, $v) = each %h) { say "$k:$v"; }
' 1
c:6
a:4
b:5
d:7
Upvotes: 5
Reputation: 118695
You have stumbled across some subtle behavior of the each
operator. By breaking out of the loop (with last ExecAction
) before the each
operator has exhausted the key-value pairs of %{@$Action[2]}
, the next call to each %{@$Action[2]}
will attempt to retrieve a different key-value pair. Since there isn't one (there is only one key-value pair defined for each element of the @Action
data structure), each
returns an empty list, and the contents of the while
loop are skipped.
The simplest workaround is to reset the "iterator" associated with each
before each use, say, by calling keys
:
keys %{@$Action[2]};
while (($RegEx, $Function) = each %{@$Action[2]})
{
...
}
I think explicitly copying the hash to a temporary variable would work, too:
my %action_hash = %{@$Action[2]};
while (($RegEx, $Function) = each %action_hash) {
...
}
Upvotes: 5