Article 4654 of comp.lang.perl:
Xref: feenix.metronet.com comp.lang.perl:4654
Newsgroups: comp.lang.perl
Path: feenix.metronet.com!news.ecn.bgu.edu!usenet.ins.cwru.edu!howland.reston.ans.net!darwin.sura.net!uvaarpa!mmdf
From: William Hails <
[email protected]>
Subject: menu utility and perl-byacc eg
Message-ID: <
[email protected]>
Sender:
[email protected] (Mail System)
Reply-To:
[email protected]
Organization: The Internet
Date: Thu, 29 Jul 1993 18:16:12 GMT
Lines: 725
If you haven't got perl-byacc yet, it's worth a look. Available from
ftp.sterling.com [192.124.9.1] in /local/perl-byacc1.8.2.tar.Z (info
amended from perl FAQ).
Although I first thought the idea was more than a bit OTT, after
playing with it for less than a day I'd come up with a (IMHO)
really neat menu system for perl, which I'm posting both as an
example of perl-byacc and as a useful utility in its own right.
Basically you can write:
require('menu.pl');
$result = &menu(<<'EOMENU');
MENU main "Main Menu"
"Sub Menu" MENU sub1
"Do Some Perl" & some perl &
END
MENU sub1 'A Sub-Menu'
...
END
...
EOMENU
eval($result) if $result;
and see:
Main Menu
---------
What do you want to do?
a) Sub Menu
b) Do Some Perl
x) exit
Select an option[x]:
A re-working for curseperl could be very interesting, but perhaps the
most exciting thing (well .. I'm easily excited :-) is that since
everything is run-time, menus can be constructed on the fly.
As not everyone has perl-byacc I'm including the yacc output as
menu2.pl - COPY to menu.pl and *DON'T RUN MAKE*.
8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of shell archive."
# Contents: Makefile menu.doc menu.y menu2.pl testmenu
# Wrapped by bill@devone on Thu Jul 29 18:04:12 1993
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Makefile' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(92 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# makefile for perl menu system
X
Xmenu.pl: menu.y
X rm -f $@
X byacc -P menu.y
X mv y.tab.pl $@
END_OF_FILE
if test 92 -ne `wc -c <'Makefile'`; then
echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'menu.doc' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'menu.doc'\"
else
echo shar: Extracting \"'menu.doc'\" \(993 characters\)
sed "s/^X//" >'menu.doc' <<'END_OF_FILE'
XThe &menu() function takes an argument string specifying the menu
Xstructure according to the following grammar:
X
Xmenus :1 menu
X |2 menus menu
X ;
X
Xmenu :3 MENU NAME STRING options END
X ;
X
Xoptions :4 option
X |5 options option
X ;
X
Xoption :6 STRING action
X |7 '*' STRING action
X |8 IF NAME STRING action
X ;
X
Xaction :9 MENU NAME
X |10 PERL
X ;
X
XIt translates this into a string of perl which can then be eval'd
X(or written to a file or whatever).
X
XEND - keyword 'END'
XIF - keyword 'IF'
XMENU - keyword 'MENU'
XNAME - a name (/^[a-zA-Z_][a-zA-Z0-9_]*$/) not a keyword
XPERL - raw perl code, delimited by any two matching non-alphanumeric
X non-whitespace characters which cannot occur, even escaped
X (sorry) in the quoted code.
XSTRING - a string, delimeted by matching ' or "
X
Xa '*' in front of a menu option specifies the default option if the
Xuser just hits return. The IF NAME construct means that unless
X$NAME is true when the generated code is eval'd that menu option
Xwill not appear.
END_OF_FILE
if test 993 -ne `wc -c <'menu.doc'`; then
echo shar: \"'menu.doc'\" unpacked with wrong size!
fi
# end of 'menu.doc'
fi
if test -f 'menu.y' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'menu.y'\"
else
echo shar: Extracting \"'menu.y'\" \(3849 characters\)
sed "s/^X//" >'menu.y' <<'END_OF_FILE'
X%{ package menu;
X$menuident = "$Id: menu.y,v 1.4 1993/07/28 14:13:22 bill Exp $";
X%}
X%token MENU NAME STRING END PERL STAR IF
X
X%start menus
X
X%%
X
Xmenus : menu
X | menus menu
X ;
X
Xmenu : MENU NAME STRING options END
X {
X ++$topstate;
X ++$menus{$2};
X $statedecls .= "\$" . $2 . " = $topstate;\n";
X $defaultact = ' || $ans eq "\n"' unless $selectopt;
X $selectopt = '[x]' unless $selectopt;
X $main_loop .= "\n if (\$NEXTSTATE == \$" . $2 .
X ") {\n\tsystem 'clear';\n" .
X "\tprint q@" . "\n" . $3 . "\n" .
X '-' x length($3) ."\n\n" .
X " What do you want to do?\n\n@;\n" .
X $option_strings .
X "\tprint \"\\tx) exit\\n\\n\";\n" .
X "\n\tprint \" Select an option" . $selectopt .
X ": \";\n\n\t" .
X '($ans = substr(<STDIN>,0,1)) =~ y/A-Z/a-z/;' .
X "\n\t" . 'exit(0) if $ans eq "x" || $ans eq ""' .
X $defaultact . ';' .
X "\n" . $selectactions . "\n" .
X "\tnext LOOP;\n }\n";
X $option_strings = $selectactions =
X $selectopt = $defaultact = '';
X $optionchar = 'a';
X }
X ;
X
Xoptions : option
X | options option
X ;
X
Xoption : STRING action
X {
X $option_strings .= "\tprint \"\\t$optionchar) " .
X $1 . "\\n\";\n";
X $selectactions .= "\tif (\$ans eq '$optionchar') { " .
X $2 . " }\n";
X ++$optionchar;
X }
X | STAR STRING action
X {
X $option_strings .= "\tprint \"\\t$optionchar) " . $2 .
X "\\n\";\n";
X $selectactions .=
X "\tif (\$ans eq '$optionchar' || \$ans eq \"\\n\") { " .
X $3 . " }\n";
X $selectopt = "[$optionchar]";
X ++$optionchar;
X }
X | IF NAME STRING action
X {
X $option_strings .= "\tprint \"\\t$optionchar) " .
X $3 . "\\n\" if \$" . $2 . ";\n";
X $selectactions .= "\tif (\$" . $2 .
X " && \$ans eq '$optionchar') { " . $4 . " }\n";
X ++$optionchar;
X }
X ;
X
Xaction : MENU NAME
X {
X push(@called_menus, $2);
X $$ = "\$NEXTSTATE = \$" . $2 . ";";
X }
X | PERL
X { $$ = $1; }
X ;
X
X%%
X
X# end of grammar
X
Xsub main'menu {
X ($text) = @_;
X @text = split("\n", $text);
X $optionchar = 'a';
X $main_loop = '';
X $line = '';
X $option_strings = $selectactions = '';
X $selectopt = $defaultact = '';
X $resultmenu = '';
X $lineno = 0;
X
X &yyparse;
X
X foreach $menu (@called_menus) {
X &yyerror("menu $menu used but not defined")
X unless $menus{$menu};
X }
X
X $resultmenu = $statedecls . "\n" .
X "LOOP: for (\$NEXTSTATE = 1;;) {\n" .
X $main_loop . "}\n";
X
X $errors ? undefined : $resultmenu;
X}
X
Xsub yyerror {
X print @_, " in menu at line $lineno, token: '$yylval'\n";
X ++$errors;
X}
X
Xsub yylex {
X TOKEN: for (;;) {
X $line =~ s/^[ \t\f\r\v]*//;
X print "Line is: '$line'\n" if $yydebug;
X if ($line eq '') {
X $line = shift(@text);
X ++$lineno;
X return(0) if !defined $line;
X next TOKEN;
X } elsif ($line =~ /^#/) {
X $line = '';
X next TOKEN;
X } elsif ($line =~ /^(['"])/) {
X $char = $1;
X if ($line =~ s/^$char([^$char]*)$char//) {
X ($yylval = $1) =~ s/@/\\@/g;
X } else {
X &yyerror('unterminated string');
X $yylval = '';
X }
X return $STRING;
X } elsif ($line =~ s/^(MENU)\b//) {
X $yylval = $1; # for errors
X return $MENU;
X } elsif ($line =~ s/^(END)\b//) {
X $yylval = $1;
X return $END;
X } elsif ($line =~ s/^(IF)\b//) {
X $yylval = $1;
X return $IF;
X } elsif ($line =~ s/^(\w+)//) {
X $yylval = $1;
X return $NAME;
X } elsif ($line =~ s/^(\*)//) {
X $yylval = $1;
X return $STAR;
X } else {
X $line =~ s/^(.)//;
X $sep = "\\" . $1;
X print "Sep is: '$sep'\n" if $yydebug;
X until ($line =~ /$sep/) {
X $nextline = shift(@text);
X ++$lineno;
X last TOKEN if !defined $nextline;
X $line .= "\xFF" . $nextline;
X }
X $yylval = $line;
X $line =~ s/.*$sep//;
X $yylval =~ s/$sep.*//;
X $yylval =~ s/\xFF/\n/g;
X print "Collected: $yylval" if $yydebug;
X return $PERL;
X }
X }
X}
X
X1;
END_OF_FILE
if test 3849 -ne `wc -c <'menu.y'`; then
echo shar: \"'menu.y'\" unpacked with wrong size!
fi
# end of 'menu.y'
fi
if test -f 'menu2.pl' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'menu2.pl'\"
else
echo shar: Extracting \"'menu2.pl'\" \(8158 characters\)
sed "s/^X//" >'menu2.pl' <<'END_OF_FILE'
X$yysccsid = "@(#)yaccpar 1.8 (Berkeley) 01/20/91 (Perl 2.0 12/31/92)";
X
X package menu;
X$menuident = "$Id: menu.y,v 1.4 1993/07/28 14:13:22 bill Exp $";
X$MENU=257;
X$NAME=258;
X$STRING=259;
X$END=260;
X$PERL=261;
X$STAR=262;
X$IF=263;
X$YYERRCODE=256;
X@yylhs = ( -1,
X 0, 0, 1, 2, 2, 3, 3, 3, 4, 4,
X);
X@yylen = ( 2,
X 1, 2, 5, 1, 2, 2, 3, 4, 2, 1,
X);
X@yydefred = ( 0,
X 0, 0, 1, 0, 2, 0, 0, 0, 0, 0,
X 4, 0, 10, 6, 0, 0, 3, 5, 9, 7,
X 0, 8,
X);
X@yydgoto = ( 2,
X 3, 10, 11, 14,
X);
X@yysindex = ( -254,
X -245, -254, 0, -250, 0, -252, -249, -244, -242, -258,
X 0, -241, 0, 0, -249, -240, 0, 0, 0, 0,
X -249, 0,
X);
X@yyrindex = ( 0,
X 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
X 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
X 0, 0,
X);
X@yygindex = ( 0,
X 12, 0, 8, -15,
X);
X$YYTABLESIZE=19;
X@yytable = ( 20,
X 7, 17, 1, 8, 9, 22, 7, 12, 6, 8,
X 9, 13, 4, 5, 15, 16, 19, 18, 21,
X);
X@yycheck = ( 15,
X 259, 260, 257, 262, 263, 21, 259, 257, 259, 262,
X 263, 261, 258, 2, 259, 258, 258, 10, 259,
X);
X$YYFINAL=2;
X
X
X
X$YYMAXTOKEN=263;
X
Xsub yyclearin { $yychar = -1; }
Xsub yyerrok { $yyerrflag = 0; }
X$YYSTACKSIZE = $YYSTACKSIZE || $YYMAXDEPTH || 500;
X$YYMAXDEPTH = $YYMAXDEPTH || $YYSTACKSIZE || 500;
X$yyss[$YYSTACKSIZE] = 0;
X$yyvs[$YYSTACKSIZE] = 0;
Xsub YYERROR { ++$yynerrs; &yy_err_recover; }
Xsub yy_err_recover
X{
X if ($yyerrflag < 3)
X {
X $yyerrflag = 3;
X while (1)
X {
X if (($yyn = $yysindex[$yyss[$yyssp]]) &&
X ($yyn += $YYERRCODE) >= 0 &&
X $yycheck[$yyn] == $YYERRCODE)
X {
X
X $yyss[++$yyssp] = $yystate = $yytable[$yyn];
X $yyvs[++$yyvsp] = $yylval;
X next yyloop;
X }
X else
X {
X
X return(1) if $yyssp <= 0;
X --$yyssp;
X --$yyvsp;
X }
X }
X }
X else
X {
X return (1) if $yychar == 0;
X
X $yychar = -1;
X next yyloop;
X }
X0;
X} # yy_err_recover
X
Xsub yyparse
X{
X
X if ($yys = $ENV{'YYDEBUG'})
X {
X $yydebug = int($1) if $yys =~ /^(\d)/;
X }
X
X
X $yynerrs = 0;
X $yyerrflag = 0;
X $yychar = (-1);
X
X $yyssp = 0;
X $yyvsp = 0;
X $yyss[$yyssp] = $yystate = 0;
X
Xyyloop: while(1)
X {
X yyreduce: {
X last yyreduce if ($yyn = $yydefred[$yystate]);
X if ($yychar < 0)
X {
X if (($yychar = &yylex) < 0) { $yychar = 0; }
X
X }
X if (($yyn = $yysindex[$yystate]) && ($yyn += $yychar) >= 0 &&
X $yycheck[$yyn] == $yychar)
X {
X
X $yyss[++$yyssp] = $yystate = $yytable[$yyn];
X $yyvs[++$yyvsp] = $yylval;
X $yychar = (-1);
X --$yyerrflag if $yyerrflag > 0;
X next yyloop;
X }
X if (($yyn = $yyrindex[$yystate]) && ($yyn += $yychar) >= 0 &&
X $yycheck[$yyn] == $yychar)
X {
X $yyn = $yytable[$yyn];
X last yyreduce;
X }
X if (! $yyerrflag) {
X &yyerror('syntax error');
X ++$yynerrs;
X }
X return(1) if &yy_err_recover;
X } # yyreduce
X
X $yym = $yylen[$yyn];
X $yyval = $yyvs[$yyvsp+1-$yym];
X switch:
X {
Xif ($yyn == 3) {
X{
X ++$topstate;
X ++$menus{$yyvs[$yyvsp-3]};
X $statedecls .= "\$" . $yyvs[$yyvsp-3] . " = $topstate;\n";
X $defaultact = ' || $ans eq "\n"' unless $selectopt;
X $selectopt = '[x]' unless $selectopt;
X $main_loop .= "\n if (\$NEXTSTATE == \$" . $yyvs[$yyvsp-3] .
X ") {\n\tsystem 'clear';\n" .
X "\tprint q@" . "\n" . $yyvs[$yyvsp-2] . "\n" .
X '-' x length($yyvs[$yyvsp-2]) ."\n\n" .
X " What do you want to do?\n\n@;\n" .
X $option_strings .
X "\tprint \"\\tx) exit\\n\\n\";\n" .
X "\n\tprint \" Select an option" . $selectopt .
X ": \";\n\n\t" .
X '($ans = substr(<STDIN>,0,1)) =~ y/A-Z/a-z/;' .
X "\n\t" . 'exit(0) if $ans eq "x" || $ans eq ""' .
X $defaultact . ';' .
X "\n" . $selectactions . "\n" .
X "\tnext LOOP;\n }\n";
X $option_strings = $selectactions =
X $selectopt = $defaultact = '';
X $optionchar = 'a';
X
Xlast switch;
X} }
Xif ($yyn == 6) {
X{
X $option_strings .= "\tprint \"\\t$optionchar) " .
X $yyvs[$yyvsp-1] . "\\n\";\n";
X $selectactions .= "\tif (\$ans eq '$optionchar') { " .
X $yyvs[$yyvsp-0] . " }\n";
X ++$optionchar;
X
Xlast switch;
X} }
Xif ($yyn == 7) {
X{
X $option_strings .= "\tprint \"\\t$optionchar) " . $yyvs[$yyvsp-1] .
X "\\n\";\n";
X $selectactions .=
X "\tif (\$ans eq '$optionchar' || \$ans eq \"\\n\") { " .
X $yyvs[$yyvsp-0] . " }\n";
X $selectopt = "[$optionchar]";
X ++$optionchar;
X
Xlast switch;
X} }
Xif ($yyn == 8) {
X{
X $option_strings .= "\tprint \"\\t$optionchar) " .
X $yyvs[$yyvsp-1] . "\\n\" if \$" . $yyvs[$yyvsp-2] . ";\n";
X $selectactions .= "\tif (\$" . $yyvs[$yyvsp-2] .
X " && \$ans eq '$optionchar') { " . $yyvs[$yyvsp-0] . " }\n";
X ++$optionchar;
X
Xlast switch;
X} }
Xif ($yyn == 9) {
X{
X push(@called_menus, $yyvs[$yyvsp-0]);
X $yyval = "\$NEXTSTATE = \$" . $yyvs[$yyvsp-0] . ";";
X
Xlast switch;
X} }
Xif ($yyn == 10) {
X{ $yyval = $yyvs[$yyvsp-0];
Xlast switch;
X} }
X } # switch
X $yyssp -= $yym;
X $yystate = $yyss[$yyssp];
X $yyvsp -= $yym;
X $yym = $yylhs[$yyn];
X if ($yystate == 0 && $yym == 0)
X {
X
X $yystate = $YYFINAL;
X $yyss[++$yyssp] = $YYFINAL;
X $yyvs[++$yyvsp] = $yyval;
X if ($yychar < 0)
X {
X if (($yychar = &yylex) < 0) { $yychar = 0; }
X
X }
X return(0) if $yychar == 0;
X next yyloop;
X }
X if (($yyn = $yygindex[$yym]) && ($yyn += $yystate) >= 0 &&
X $yyn <= $#yycheck && $yycheck[$yyn] == $yystate)
X {
X $yystate = $yytable[$yyn];
X } else {
X $yystate = $yydgoto[$yym];
X }
X
X $yyss[++$yyssp] = $yystate;
X $yyvs[++$yyvsp] = $yyval;
X } # yyloop
X} # yyparse
X
X
X
Xsub main'menu {
X ($text) = @_;
X @text = split("\n", $text);
X $optionchar = 'a';
X $main_loop = '';
X $line = '';
X $option_strings = $selectactions = '';
X $selectopt = $defaultact = '';
X $resultmenu = '';
X $lineno = 0;
X
X &yyparse;
X
X foreach $menu (@called_menus) {
X &yyerror("menu $menu used but not defined")
X unless $menus{$menu};
X }
X
X $resultmenu = $statedecls . "\n" .
X "LOOP: for (\$NEXTSTATE = 1;;) {\n" .
X $main_loop . "}\n";
X
X $errors ? undefined : $resultmenu;
X}
X
Xsub yyerror {
X print @_, " in menu at line $lineno, token: '$yylval'\n";
X ++$errors;
X}
X
Xsub yylex {
X TOKEN: for (;;) {
X $line =~ s/^[ \t\f\r\v]*//;
X print "Line is: '$line'\n" if $yydebug;
X if ($line eq '') {
X $line = shift(@text);
X ++$lineno;
X return(0) if !defined $line;
X next TOKEN;
X } elsif ($line =~ /^#/) {
X $line = '';
X next TOKEN;
X } elsif ($line =~ /^(['"])/) {
X $char = $1;
X if ($line =~ s/^$char([^$char]*)$char//) {
X ($yylval = $1) =~ s/@/\\@/g;
X } else {
X &yyerror('unterminated string');
X $yylval = '';
X }
X return $STRING;
X } elsif ($line =~ s/^(MENU)\b//) {
X $yylval = $1; # for errors
X return $MENU;
X } elsif ($line =~ s/^(END)\b//) {
X $yylval = $1;
X return $END;
X } elsif ($line =~ s/^(IF)\b//) {
X $yylval = $1;
X return $IF;
X } elsif ($line =~ s/^(\w+)//) {
X $yylval = $1;
X return $NAME;
X } elsif ($line =~ s/^(\*)//) {
X $yylval = $1;
X return $STAR;
X } else {
X $line =~ s/^(.)//;
X $sep = "\\" . $1;
X print "Sep is: '$sep'\n" if $yydebug;
X until ($line =~ /$sep/) {
X $nextline = shift(@text);
X ++$lineno;
X last TOKEN if !defined $nextline;
X $line .= "\xFF" . $nextline;
X }
X $yylval = $line;
X $line =~ s/.*$sep//;
X $yylval =~ s/$sep.*//;
X $yylval =~ s/\xFF/\n/g;
X print "Collected: $yylval" if $yydebug;
X return $PERL;
X }
X }
X}
X
X1;
END_OF_FILE
if test 8158 -ne `wc -c <'menu2.pl'`; then
echo shar: \"'menu2.pl'\" unpacked with wrong size!
fi
# end of 'menu2.pl'
fi
if test -f 'testmenu' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'testmenu'\"
else
echo shar: Extracting \"'testmenu'\" \(750 characters\)
sed "s/^X//" >'testmenu' <<'END_OF_FILE'
X#!/usr/local/bin/perl
X
Xrequire("menu.pl");
X
X$wantother = $ARGV[0] eq '-o';
X
X$result = &menu(<<'EOMENU');
X
X MENU main "Main Menu"
X "Sub Menu" MENU sub1
X IF wantother "Other Sub Menu" MENU sub2
X "ls" &
X system "ls";
X print "hit return:";
X local($ret);
X $ret = <STDIN>;
X &
X END
X
X MENU sub1 "Sub Menu"
X "ls" & system "ls"; &
X "perl eval" | &evalstr; |
X * "Back to main menu" MENU main
X END
X
X MENU sub2 "Other Sub Menu"
X "ls" @system "ls";@
X * "Back to main menu" MENU main
X END
X
XEOMENU
X
Xeval($result) if $result;
Xwarn "$@" if $@;
X
Xsub evalstr {
X print "enter a line of perl: ";
X eval <stdin>;
X warn "$@" if $@;
X print "hit return: ";
X local($ret);
X $ret = <STDIN>;
X}
END_OF_FILE
if test 750 -ne `wc -c <'testmenu'`; then
echo shar: \"'testmenu'\" unpacked with wrong size!
fi
chmod +x 'testmenu'
# end of 'testmenu'
fi
echo shar: End of shell archive.
exit 0
===========================================================================
| Bill Hails <
[email protected]> | |
| C.L.I. Connect Ltd. | README: permission denied |
| 19, Quarry St., Guildford, Surrey | |
| GU1 3UY. Tel (UK) 0483 300 200 | |
===========================================================================