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