marc@athena.mit.edu (07/30/90)
I've added a few new features to my menu package. It's short, so I'm
reposting the whole thing:
--cut--
# package, meant to be do'ed, not run directly
#
# $Id: menu.pl,v 1.2 90/07/29 02:50:42 marc Exp $
#
package menu;
@gencmds = (
"help","__internal","Print the list of commands in this menu",
"?","","help",
"exit","__internal","Exit the current menu",
"quit","__internal","Quit",
);
sub sigint {
$foo = "@_[0..]"; # always generates a perl error at runtime.
}
sub main'menu { # @_ = ($prompt,@cmds)
local($oldsigint,$prompt,@cmds,%fcts,%helps);
$prompt = shift(@_);
@table = (@_,@gencmds);
while (@table > $[) {
$cmd = shift(@table);
$fct = shift(@table);
$help = shift(@table);
if (defined($fcts{$cmd})) {next;}
if ($fct eq "") {
$fct = $fcts{$help};
$help = $helps{$help};
}
push(@cmds,$cmd);
$fcts{$cmd} = $fct;
$helps{$cmd} = $help;
}
while (1) {
print "\n$prompt: ";
if (!($_ = <>)) {
print "eof on input. Aborting...\n";
exit(2);
}
chop;
# strip leading whitespace, get cmd, arg.
/^\s*(\S+)\s*/;
($cmd,$arg) = ($1,$');
# strip trailing whitespace
$arg =~ /\s*$/;
$arg = $`;
if (defined($fct = $fcts{$cmd})) {
if ($fct eq "__internal") { # Magic...
if (($_ eq "help") || ($_ eq "?")) {
foreach (@cmds) {
printf "%-15s%s\n",$_,$helps{$_};
}
} elsif ($cmd eq "exit") {
return;
} elsif ($cmd eq "quit") {
exit(0);
} else {
die "Bogon __internal $_!\n";
}
} else {
if (!($fct =~ /\'/)) {
$fct = "main'".$fct;
}
# exception handling!
$oldsigint = $main'SIG{'INT'};
$main'SIG{'INT'} = "menu'sigint";
eval("&$fct(\$arg);");
$main'SIG{'INT'} = $oldsigint;
}
} else {
print "\"$cmd\" is not a valid command. Type ? for help.\n";
}
}
}
--cut--
Of particular interest are the following lines:
sub sigint {
$foo = "@_[0..]"; # always generates a perl error at runtime.
}
# ...
# exception handling!
$oldsigint = $main'SIG{'INT'};
$main'SIG{'INT'} = "menu'sigint";
eval("&$fct(\$arg);");
$main'SIG{'INT'} = $oldsigint;
I've basically implemented exception handling. Since eval returns at
any fatal error, the menu function can be aborted at any time by a
perl error. In this case, I install a signal handler which causes a
perl error. (Larry, is there any better way to generate a guaranteed
perl error?) It would be pretty trivial to implement throw- and
catch-style semantics given this technique: (Following untested)
sub catch {
local($thrown);
$thrown = 0;
eval($_[0]);
if ($thrown) {
print "Exception caught\n";
}
}
sub throw {
$thrown = 1;
$foo = "@_[0..]";
}
sub main {
&catch("&s1();");
}
sub s1 {
#...
&s2();
#...
}
sub s2 {
#...
&s3();
#...
}
sub s1 {
#...
&throw();
#...
}
With some clever code, you could probably implement real catch and
throw, with different kinds of throws and data.
I think this one can go under "functionality Larry never even dreamed
about." Need another chapter in the book? :-)
Marclwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (07/31/90)
In article <1990Jul29.195437.7608@uvaarpa.Virginia.EDU> marc@mit.edu writes: : Of particular interest are the following lines: : : sub sigint { : $foo = "@_[0..]"; # always generates a perl error at runtime. : } : : # ... : : # exception handling! : : $oldsigint = $main'SIG{'INT'}; : $main'SIG{'INT'} = "menu'sigint"; : : eval("&$fct(\$arg);"); : : $main'SIG{'INT'} = $oldsigint; : : I've basically implemented exception handling. Since eval returns at : any fatal error, the menu function can be aborted at any time by a : perl error. In this case, I install a signal handler which causes a : perl error. (Larry, is there any better way to generate a guaranteed : perl error?) It would be pretty trivial to implement throw- and : catch-style semantics given this technique: (Following untested) You don't need to get so fancy to generate a fatal error. Just use the die operator: sub sigint { die 'Here is a parameter that will be passed back in $@' . "\n"; } : With some clever code, you could probably implement real catch and : throw, with different kinds of throws and data. Seems fairly easy to me. Here's an implementation of typed exception handlers: sub catch { local($typelist, $code) = @_; local($retval); eval $code; if ($@ =~ /^(\w*):/) { $retval = $1; die $@ unless $typelist =~ /\b$retval\b/; # propagate exception $@ =~ s/^\w*://; } elsif ($@ ne '') { die $@; } $retval; } sub throw { local($type, $message) = @_; die "$type:$message\n"; } $except = &catch('INT MATH SYNTAX', '&calc'); if ($except eq 'MATH') {...} sub calc { ... &throw('MATH', "Can't divide by zero") if $denom == 0; ... } Or something like that--I haven't tested it. : I think this one can go under "functionality Larry never even dreamed : about." Don't teach your grandmother to suck eggs. :-) : Need another chapter in the book? :-) Short chapter. :-) Larry